Procedural Asset Management in Unity
We’ve looked previously at adding our own tools to Unity’s editor; now, in this short tutorial, I’ll introduce you to handling the assets by script in Unity. We’ll manage paths, create prefab files, generate a texture and save it to an image. Finally we’ll also create a material file that uses the generated image, and all this will be done by code.
Final Result Preview
Let’s take a look at the final result we will be working towards:

Step 1: Set Up the Project
Create an empty project; we won’t be using anything fancy here so we shouldn’t bother to import anything at all. Once that’s done, create an editor script. Unity will let us use its editor classes only if we place our script in a folder named Editor. Since that doesn’t exist in our project yet, we need to create it.

Now let’s create a script inside it.

Step 2: Add a MenuItem
Let’s clean up our script. Aside from the basic functionality, we also want to be able to use the editor classes. We need to be using UnityEditor and our script’s class should extend the Editor class instead of MonoBehaviour like normal game objects do.
using UnityEngine;
using System.Collections;
using UnityEditor;
public class Examples : Editor
{
}
In our first function we’ll be working with prefabs, let’s call it a PrefabRoutine.
public class Examples : Editor
{
void PrefabRoutine()
{
}
}
To easly execute this function from the editor, let’s add it as a MenuItem.
public class Examples : Editor
{
[MenuItem ("Examples/Prefab Routine")]
void PrefabRoutine()
{
}
}
Aside from letting the unity know that we want this function to be executable from the Examples->Prefab Routine”, we also need to make this function static.
public class Examples : Editor
{
[MenuItem ("Examples/Prefab Routine")]
static void PrefabRoutine()
{
}
}
If you go back to the editor now (and refresh the menu), you’ll notice that there’s a new menu named Examples there.

If you select the Prefab Routine nothing will happen since our function is empty.
Step 3: Create a Folder
To shape our project the way we want we need to know how to create folders so we can move stuff around. Creating a folder from the script is very straightforward, all we need to do is to let unity know where the folder should be placed. To create a folder we need to use AssetDatabase class.
[MenuItem ("Examples/Prefab Routine")]
static void PrefabRoutine()
{
AssetDatabase.CreateFolder("Assets", "Prefab Folder");
}
“Assets” is the name of the parent folder of the directory we want to create. In our case it’s the main project folder where all our assets are imported/created.
Note that you can also use the .NET Directory class. This will also let you delete, move or access the directories’ files. To use this class you need to be using System.IO.
Each time you select the Prefab Routine from the editor, a new folder should be created and be visible in the project view.

Step 4: Create a Prefab
To create a prefab we need to call EditorUtility.CreateEmptyPrefab(). The function takes the prefab’s path as an argument.
[MenuItem ("Examples/Prefab Routine")]
static void PrefabRoutine()
{
AssetDatabase.CreateFolder("Assets", "Prefab Folder");
Object prefab = EditorUtility.CreateEmptyPrefab("Assets/Prefab Folder/obj.prefab");
}
Don’t forget about the extension! Also, after we create the file we need to call AssetDatabase.Refresh(), so the unity is able to see it.
[MenuItem ("Examples/Prefab Routine")]
static void PrefabRoutine()
{
AssetDatabase.CreateFolder("Assets", "Prefab Folder");
Object prefab = EditorUtility.CreateEmptyPrefab("Assets/Prefab Folder/obj.prefab");
AssetDatabase.Refresh();
}
If we leave a constant path as an argument, each time we select our routine a new empty prefab will replace the old one. Let’s assign each prefab to separate folder to counter that. To do this we need to save the most recently created folder to a string so we can use it as a path argument later. The CreateFolder function returns a GUID, which basically is the file’s (or directory’s) ID. There’s a function that retrieves the path if we submit this ID. It’s called GUIDToAssetPath; let’s use it to get our folder’s path.
[MenuItem ("Examples/Prefab Routine")]
static void PrefabRoutine()
{
string path = AssetDatabase.GUIDToAssetPath(AssetDatabase.CreateFolder("Assets", "Prefab Folder"));
Object prefab = EditorUtility.CreateEmptyPrefab("Assets/Prefab Folder/obj.prefab");
AssetDatabase.Refresh();
}
Now let’s use the path to direct the prefabs we are going to create to the most recently created folder.
[MenuItem ("Examples/Prefab Routine")]
static void PrefabRoutine()
{
string path = AssetDatabase.GUIDToAssetPath(AssetDatabase.CreateFolder("Assets", "Prefab Folder"));
Object prefab = EditorUtility.CreateEmptyPrefab(path + "/obj.prefab");
AssetDatabase.Refresh();
}
You can test whether the created empty prefabs are packed in folders now.

Step 5: Set the Prefab
If you create a prefab then you probably don’t want to leave it empty because in that case it’s pretty much useless. Let’s set our prefab if there’s any game object selected while our routine is executing. We’ll the prefab to the selected object. To get the currently selected object we can use the Selection class which has a reference to it. To set the prefab we need to call ReplacePrefab().
[MenuItem ("Examples/Prefab Routine")]
static void PrefabRoutine()
{
string path = AssetDatabase.GUIDToAssetPath(AssetDatabase.CreateFolder("Assets", "Prefab Folder"));
Object prefab = EditorUtility.CreateEmptyPrefab(path + "/obj.prefab");
AssetDatabase.Refresh();
if (Selection.activeObject)
EditorUtility.ReplacePrefab(Selection.activeGameObject, prefab);
}
If you run the routine with any game object selected now then you’ll notice that the created prefab is automatically set.

That’s it, we have created a custom routine for prefab creation, it’s not very useful but you should be able to know how to do that now if there will be a need for such a thing in your project.
At the end I also want to mention that AssetDatabase also lets you move assets around, move them to trash or delete them by calling AssetDatabase.MoveAsset(), AssetDatabase.MoveAssetToTrash() and AssetDatabase.DeleteAsset() respectively. The rest of the functionality can be found at the AssetDatabase script reference page.
Step 6: Add Another Menu Item
Let’s go to another example, this time we’ll create a texture and a material programmatically. Let’s call this menu item Material Routine.
[MenuItem ("Examples/Prefab Routine")]
static void PrefabRoutine()
{
string path = AssetDatabase.GUIDToAssetPath(AssetDatabase.CreateFolder("Assets", "Prefab Folder"));
Object prefab = EditorUtility.CreateEmptyPrefab(path + "/obj.prefab");
AssetDatabase.Refresh();
if (Selection.activeObject)
EditorUtility.ReplacePrefab(Selection.activeGameObject, prefab);
}
[MenuItem ("Examples/Material Routine")]
static void MaterialRoutine()
{
}
Now we have two items to choose from in the Examples menu.
Step 7: Create a Texture
Let’s create a Texture2D and set its size to (256, 256) for this example.
[MenuItem ("Examples/Material Routine")]
static void MaterialRoutine()
{
Texture2D tex = new Texture2D(256, 256);
}
Now we shouldn’t let all those pixels go to waste, so let’s set the texture’s pixels according to some kind of thought-up formula. For that we’ll need two for loops to go through every pixel. To set the each pixel’s color we need to call SetPixel() which takes the position of the pixel on a texture and its color as the arguments.
[MenuItem ("Examples/Material Routine")]
static void MaterialRoutine()
{
Texture2D tex = new Texture2D(256, 256);
for (int y = 0; y < 256; ++y)
{
for (int x = 0; x < 256; ++x)
tex.SetPixel(x, y, new Color());
}
}
To assign the color we’ll use the Mathf.Sin() function. The Color class can be initialized with three floats, corresponding to the red, green and blue color components, respectively. The max allowed value is 1 and min is 0, so the Sin() function suits our needs perfectly.
for (int y = 0; y < 256; ++y)
{
for (int x = 0; x < 256; ++x)
tex.SetPixel(Mathf.Sin(x*y), Mathf.Sin(x*y), Mathf.Sin(x*y)));
}
It doesn’t matter what we submit to the Sin() function, but to get something more interesting we should give a value that changes for each pixel.
Step 8: Create an Image
Now let’s create an image from the texture we just created. Since we’ll be writing to a file in binary mode, we need to be using System.IO, so let’s add it to the top of our script.
using UnityEngine; using System.Collections; using UnityEditor; using System.IO; public class Examples : Editor
To save our texture as a PNG image we first need to call EncodeToPNG() which will return an array of bytes that the PNG file consists of.
for (int y = 0; y < 256; ++y)
{
for (int x = 0; x < 256; ++x)
tex.SetPixel(x, y, new Color(Mathf.Sin(x*y), Mathf.Sin(x*y), Mathf.Sin(x*y)));
}
byte[] pngData = tex.EncodeToPNG();
Now that we’ve got our pngData we can write it to a file and create a PNG image in this way.
byte[] pngData = tex.EncodeToPNG();
if(pngData != null)
File.WriteAllBytes("Assets/texture.png", pngData);
Since we create the file at a constant path, each time we’ll run MaterialRoutine(), the texture will get overwritten.
And since we’ve got our image, we don’t need the generated texture anymore as it won’t be referencing an image anyway. Let’s destroy it.
byte[] pngData = tex.EncodeToPNG();
if(pngData != null)
File.WriteAllBytes("Assets/texture.png", pngData);
DestroyImmediate(tex);
Also, we need to let Unity update the project view and file references; to do that we need to call AssetDatabase.Refresh().
byte[] pngData = tex.EncodeToPNG();
if(pngData != null)
File.WriteAllBytes("Assets/texture.png", pngData);
DestroyImmediate(tex);
AssetDatabase.Refresh();
Let’s test whether the texture gets created when we execute our routine.

Step 9: Create a Material
We’ve got an image and now we can create a material that uses it as a texture. Let’s create a new Material.
AssetDatabase.Refresh();
new Material(Shader.Find("Diffuse"));
The created material will use a Diffuse shader. To save this material to the file, we can call AssetDatabase.CreateAsset(). This function takes an asset as the first argument, and the path as the second one.
AssetDatabase.Refresh();
AssetDatabase.CreateAsset(new Material(Shader.Find("Diffuse")), "Assets/New Material.mat");
If you run our routine now, you’ll see that the material is created.

As you can see everything is correct, its name is New Material and it uses Diffuse shader, but there’s no texture assigned to it.
Step 10: Assign the Texture
First we need to get a reference to the material we just created. We can get that by calling AssetDatabase.LoadAssetAtPath() which loads the asset and returns its reference.
AssetDatabase.CreateAsset(new Material(Shader.Find("Diffuse")), "Assets/New Material.mat");
Material material = (Material) (AssetDatabase.LoadAssetAtPath("Assets/New Material.mat",typeof(Material)));
Now let’s assign our generated texture as the main texture of the material. We can get the texture reference of the generated texture in the same way we got the material reference.
Material material = (Material) (AssetDatabase.LoadAssetAtPath("Assets/New Material.mat",typeof(Material)));
material.mainTexture = (Texture2D) (AssetDatabase.LoadAssetAtPath("Assets/texture.png", typeof(Texture2D)));
To see the results, run the Material Routine.

As you can see, the material has the texture assigned now.
Conclusion
That’s the end of the introduction to manage your assets using scripts. If you want to expand your knowladge on the topic you can visit the Unity Editor Classes reference page, particularly the AssetDatabase script reference is worth looking into. If you need to work at a low level, you should also read the docs on System.IO to get more information on its classes and how you can use them. Thanks for your time!
View full post on Activetuts+

We’ve looked previously at adding our own tools to Unity’s editor; now, in this short tutorial, I’ll introduce you to handling the assets by script in Unity. We’ll manage paths, create prefab files, generate a texture and save it to an image. Finally we’ll also create a material file that uses the generated image, and all this will be done by code.
Final Result Preview
Let’s take a look at the final result we will be working towards:
Step 1: Set Up the Project
Create an empty project; we won’t be using anything fancy here so we shouldn’t bother to import anything at all. Once that’s done, create an editor script. Unity will let us use its editor classes only if we place our script in a folder named Editor. Since that doesn’t exist in our project yet, we need to create it.
Now let’s create a script inside it.
Step 2: Add a MenuItem
Let’s clean up our script. Aside from the basic functionality, we also want to be able to use the editor classes. We need to be
using UnityEditorand our script’s class should extend theEditorclass instead ofMonoBehaviourlike normal game objects do.using UnityEngine; using System.Collections; using UnityEditor; public class Examples : Editor { }In our first function we’ll be working with prefabs, let’s call it a
PrefabRoutine.public class Examples : Editor { void PrefabRoutine() { } }To easly execute this function from the editor, let’s add it as a
MenuItem.public class Examples : Editor { [MenuItem ("Examples/Prefab Routine")] void PrefabRoutine() { } }Aside from letting the unity know that we want this function to be executable from the Examples->Prefab Routine”, we also need to make this function static.
public class Examples : Editor { [MenuItem ("Examples/Prefab Routine")] static void PrefabRoutine() { } }If you go back to the editor now (and refresh the menu), you’ll notice that there’s a new menu named Examples there.
If you select the Prefab Routine nothing will happen since our function is empty.
Step 3: Create a Folder
To shape our project the way we want we need to know how to create folders so we can move stuff around. Creating a folder from the script is very straightforward, all we need to do is to let unity know where the folder should be placed. To create a folder we need to use
AssetDatabaseclass.[MenuItem ("Examples/Prefab Routine")] static void PrefabRoutine() { AssetDatabase.CreateFolder("Assets", "Prefab Folder"); }“Assets” is the name of the parent folder of the directory we want to create. In our case it’s the main project folder where all our assets are imported/created.
Note that you can also use the .NET
Directoryclass. This will also let you delete, move or access the directories’ files. To use this class you need to beusing System.IO.Each time you select the Prefab Routine from the editor, a new folder should be created and be visible in the project view.
Step 4: Create a Prefab
To create a prefab we need to call
EditorUtility.CreateEmptyPrefab(). The function takes the prefab’s path as an argument.[MenuItem ("Examples/Prefab Routine")] static void PrefabRoutine() { AssetDatabase.CreateFolder("Assets", "Prefab Folder"); Object prefab = EditorUtility.CreateEmptyPrefab("Assets/Prefab Folder/obj.prefab"); }Don’t forget about the extension! Also, after we create the file we need to call
AssetDatabase.Refresh(), so the unity is able to see it.[MenuItem ("Examples/Prefab Routine")] static void PrefabRoutine() { AssetDatabase.CreateFolder("Assets", "Prefab Folder"); Object prefab = EditorUtility.CreateEmptyPrefab("Assets/Prefab Folder/obj.prefab"); AssetDatabase.Refresh(); }If we leave a constant path as an argument, each time we select our routine a new empty prefab will replace the old one. Let’s assign each prefab to separate folder to counter that. To do this we need to save the most recently created folder to a string so we can use it as a path argument later. The
CreateFolderfunction returns aGUID, which basically is the file’s (or directory’s) ID. There’s a function that retrieves the path if we submit this ID. It’s calledGUIDToAssetPath; let’s use it to get our folder’s path.[MenuItem ("Examples/Prefab Routine")] static void PrefabRoutine() { string path = AssetDatabase.GUIDToAssetPath(AssetDatabase.CreateFolder("Assets", "Prefab Folder")); Object prefab = EditorUtility.CreateEmptyPrefab("Assets/Prefab Folder/obj.prefab"); AssetDatabase.Refresh(); }Now let’s use the
pathto direct the prefabs we are going to create to the most recently created folder.[MenuItem ("Examples/Prefab Routine")] static void PrefabRoutine() { string path = AssetDatabase.GUIDToAssetPath(AssetDatabase.CreateFolder("Assets", "Prefab Folder")); Object prefab = EditorUtility.CreateEmptyPrefab(path + "/obj.prefab"); AssetDatabase.Refresh(); }You can test whether the created empty prefabs are packed in folders now.
Step 5: Set the Prefab
If you create a prefab then you probably don’t want to leave it empty because in that case it’s pretty much useless. Let’s set our prefab if there’s any game object selected while our routine is executing. We’ll the prefab to the selected object. To get the currently selected object we can use the
Selectionclass which has a reference to it. To set the prefab we need to callReplacePrefab().[MenuItem ("Examples/Prefab Routine")] static void PrefabRoutine() { string path = AssetDatabase.GUIDToAssetPath(AssetDatabase.CreateFolder("Assets", "Prefab Folder")); Object prefab = EditorUtility.CreateEmptyPrefab(path + "/obj.prefab"); AssetDatabase.Refresh(); if (Selection.activeObject) EditorUtility.ReplacePrefab(Selection.activeGameObject, prefab); }If you run the routine with any game object selected now then you’ll notice that the created prefab is automatically set.
That’s it, we have created a custom routine for prefab creation, it’s not very useful but you should be able to know how to do that now if there will be a need for such a thing in your project.
At the end I also want to mention that
AssetDatabasealso lets you move assets around, move them to trash or delete them by callingAssetDatabase.MoveAsset(), AssetDatabase.MoveAssetToTrash()andAssetDatabase.DeleteAsset()respectively. The rest of the functionality can be found at theAssetDatabasescript reference page.Step 6: Add Another Menu Item
Let’s go to another example, this time we’ll create a texture and a material programmatically. Let’s call this menu item Material Routine.
[MenuItem ("Examples/Prefab Routine")] static void PrefabRoutine() { string path = AssetDatabase.GUIDToAssetPath(AssetDatabase.CreateFolder("Assets", "Prefab Folder")); Object prefab = EditorUtility.CreateEmptyPrefab(path + "/obj.prefab"); AssetDatabase.Refresh(); if (Selection.activeObject) EditorUtility.ReplacePrefab(Selection.activeGameObject, prefab); } [MenuItem ("Examples/Material Routine")] static void MaterialRoutine() { }Now we have two items to choose from in the Examples menu.
Step 7: Create a Texture
Let’s create a
Texture2Dand set its size to(256, 256)for this example.[MenuItem ("Examples/Material Routine")] static void MaterialRoutine() { Texture2D tex = new Texture2D(256, 256); }Now we shouldn’t let all those pixels go to waste, so let’s set the texture’s pixels according to some kind of thought-up formula. For that we’ll need two
forloops to go through every pixel. To set the each pixel’s color we need to callSetPixel()which takes the position of the pixel on a texture and its color as the arguments.[MenuItem ("Examples/Material Routine")] static void MaterialRoutine() { Texture2D tex = new Texture2D(256, 256); for (int y = 0; y < 256; ++y) { for (int x = 0; x < 256; ++x) tex.SetPixel(x, y, new Color()); } }To assign the color we’ll use the
Mathf.Sin()function. TheColorclass can be initialized with three floats, corresponding to the red, green and blue color components, respectively. The max allowed value is1and min is0, so theSin()function suits our needs perfectly.for (int y = 0; y < 256; ++y) { for (int x = 0; x < 256; ++x) tex.SetPixel(Mathf.Sin(x*y), Mathf.Sin(x*y), Mathf.Sin(x*y))); }It doesn’t matter what we submit to the
Sin()function, but to get something more interesting we should give a value that changes for each pixel.Step 8: Create an Image
Now let’s create an image from the texture we just created. Since we’ll be writing to a file in binary mode, we need to be
using System.IO, so let’s add it to the top of our script.To save our texture as a PNG image we first need to call
EncodeToPNG()which will return an array of bytes that the PNG file consists of.for (int y = 0; y < 256; ++y) { for (int x = 0; x < 256; ++x) tex.SetPixel(x, y, new Color(Mathf.Sin(x*y), Mathf.Sin(x*y), Mathf.Sin(x*y))); } byte[] pngData = tex.EncodeToPNG();Now that we’ve got our
pngDatawe can write it to a file and create a PNG image in this way.byte[] pngData = tex.EncodeToPNG(); if(pngData != null) File.WriteAllBytes("Assets/texture.png", pngData);Since we create the file at a constant path, each time we’ll run
MaterialRoutine(), the texture will get overwritten.And since we’ve got our image, we don’t need the generated texture anymore as it won’t be referencing an image anyway. Let’s destroy it.
byte[] pngData = tex.EncodeToPNG(); if(pngData != null) File.WriteAllBytes("Assets/texture.png", pngData); DestroyImmediate(tex);Also, we need to let Unity update the project view and file references; to do that we need to call
AssetDatabase.Refresh().byte[] pngData = tex.EncodeToPNG(); if(pngData != null) File.WriteAllBytes("Assets/texture.png", pngData); DestroyImmediate(tex); AssetDatabase.Refresh();Let’s test whether the texture gets created when we execute our routine.
Step 9: Create a Material
We’ve got an image and now we can create a material that uses it as a texture. Let’s create a
new Material.AssetDatabase.Refresh(); new Material(Shader.Find("Diffuse"));The created material will use a Diffuse shader. To save this material to the file, we can call
AssetDatabase.CreateAsset(). This function takes an asset as the first argument, and the path as the second one.AssetDatabase.Refresh(); AssetDatabase.CreateAsset(new Material(Shader.Find("Diffuse")), "Assets/New Material.mat");If you run our routine now, you’ll see that the material is created.
As you can see everything is correct, its name is New Material and it uses Diffuse shader, but there’s no texture assigned to it.
Step 10: Assign the Texture
First we need to get a reference to the material we just created. We can get that by calling
AssetDatabase.LoadAssetAtPath()which loads the asset and returns its reference.AssetDatabase.CreateAsset(new Material(Shader.Find("Diffuse")), "Assets/New Material.mat"); Material material = (Material) (AssetDatabase.LoadAssetAtPath("Assets/New Material.mat",typeof(Material)));Now let’s assign our generated texture as the main texture of the material. We can get the texture reference of the generated texture in the same way we got the material reference.
Material material = (Material) (AssetDatabase.LoadAssetAtPath("Assets/New Material.mat",typeof(Material))); material.mainTexture = (Texture2D) (AssetDatabase.LoadAssetAtPath("Assets/texture.png", typeof(Texture2D)));To see the results, run the Material Routine.
As you can see, the material has the texture assigned now.
Conclusion
That’s the end of the introduction to manage your assets using scripts. If you want to expand your knowladge on the topic you can visit the Unity Editor Classes reference page, particularly the AssetDatabase script reference is worth looking into. If you need to work at a low level, you should also read the docs on System.IO to get more information on its classes and how you can use them. Thanks for your time!
In the previous video I showed you how to implement basic playback controls. Today I am going to talk about using labels to mark and navigate to precise locations in a TimelineLite. Labels in TimelineLite work similarly to how frame labels work in Flash IDE timelines. I’ll be showing you multiple ways to add labels and some clever ways of using them. We’ll also take a little look at some features exclusive to TimelineMax.
TimelineLite in Action
Let’s take a look at the example we’ll be building in the video:
You can find all the files used to create the SWF above in the source files for this tutorial.
Watch the Screencast
Don’t like ads? Download the screencast, or subscribe to Activetuts+ screencasts via iTunes!
Adding Labels to a TimelineLite
There are two methods that you can use for adding labels to a TimelineLite
addLabel(label:String, time:Number):voidAdds a label at a particular time. It is most common to pass in the current duration of the timeline as the time.
tl.append( TweenMax.to( align_mc, 1, { x:endX } ) ); tl.append( TweenMax.to( align_mc, .2, { autoAlpha:0 } ) ); // add a label named transform immediately after the previous tween is finished. tl.addLabel("transform", tl.duration) tl.append( TweenMax.to( transform_mc, 1, { y:endY } ) );insert(tween:TweenCore, timeOrLabel:* = 0):TweenCoreWhen using
insert()to insert a tween, the tween will be inserted at the time or label specified in the second parameter. If you insert at a label that doesn’t exist yet, it will automatically place that label at the end of the timeline and then insert the tween. This technique makesinsert()act like anappend()with the added value of creating a label.tl.append( TweenMax.to( align_mc, 1, { x:endX } ) ); tl.append( TweenMax.to( align_mc, .2, { autoAlpha:0 } ) ); //insert a tween and the transform label immediately after the previous tween is finished. tl.insert( TweenMax.to( transform_mc, 1, { y:endY } ), "transform" );Navigating to Labels
TimelineLite’s intuitive
gotoAndPlay()andgotoAndStop()methods work exactly as those same methods of the MovieClip object. Although this video focuses on using gotoAndPlay with a label, you can also pass in a time as well.//jump to the color label and play tl.gotoAndPlay("color"); //jump 1 second into the timeline and stop tl.gotoAndStop(1);TimelineMax gives us the unique ability to play to a particular label with the
tweenTo()method. If MovieClips had such a method it would be calledplayTo(). At the end of this series I will be showing you how to add an ease to atweenTo()as well as a number of other tricks.Due to the introductory nature of this series there are some label-related features of TimelineLite/Max that I did not get to cover. Advanced users may want to read up on the optional
suppressEventsparameter that can be used withgotoAndPlay()andgotoAndStop()in the documentation.TimelineMax’s Label Helpers
TimelineMax has a number of features for figuring out the names of labels based on their relation to the current position of the playhead or a specified time. These properties and methods make it possible to dynamically calculate what the nearest label is in any direction.
Property
currentLabel– The closest label that is at or before the current time.Methods
getLabelBefore(time:Number)– Returns the previous label (if any) that occurs before the time parameter. If you do not pass a time in, the currentTime will be used.getLabelAfter(time:Number)– Returns the next label (if any) that occurs AFTER the time parameter. If you do not pass a time in, thecurrentTimewill be used.Conclusion
Due to the powerful properties and methods of TimelineLite/Max it is extremely easy to navigate and control your script-based timelines. The label-related features that we have discussed today really just scratch the surface of what can be done. Once you get a handle on these basic techniques you will find yourself creating timelines with more and more dynamic features. Suppose you want to prevent a user from clicking the “library” button once they are in the library section. You could simply add logic to the button’s click handler that considers the following:
Feel free to convert that to ActionScript if you would like some extra credit
In the next tutorial I am going to be showing advanced techniques for adding tons of tweens to a timeline with very little code and extreme precision.
If you have any questions or comments on this tutorial simply post a comment below.
Thanks for watching!
The shell game is a cliché of street corners in black and white movies and modern cities: the hustler puts a pea under one of three shells, and swaps the shells around rapidly, challenging you to pick the shell with the pea underneath. The punter will typically get it correct… right up to the point where big money is at stake, or when the punter isn’t one of the hustler’s cronies.
In this tutorial, exclusive to Premium members, you’ll learn how to create your own version of the shell game – except without the cheating!
Preview
Let’s take a look at the final result we will be working towards:
The tutorial covers both the design and the coding of the game, and uses TweenMax to make the shells move along a curved path.
Active Premium Membership
We run a Premium membership system which periodically gives members access to extra tutorials, like this one! You’ll also get access to Psd Premium, Vector Premium, Audio Premium, Net Premium, Ae Premium, Cg Premium, Photo Premium, and the new Mobile Premium too. If you’re a Premium member, you can log in and download the tutorial. If you’re not a member, you can of course join today!
Also, don’t forget to follow @envatoactive on twitter and grab the Activetuts+ RSS Feed to stay up to date with the latest tutorials and articles.
We’re excited to let you know that now, in addition to Twitter and Facebook, you can get involved with the Activetuts+ community over at Google+! We’ll be using Google+ to let you know about our latest tutorials, competitions, and Tuts+ news. Read on to find out more…
What to Expect on Google+
Through Google+, we’ll be publishing links to our new tutorials and articles, industry news, graphics and examples from our community, and lots more! It’s going to be an extension of the Activetuts+ site and community, in the same way we treat Twitter and Facebook.
Don’t have a Google+ account? You can head over and create one here, or find out a little more about how it works.
Add Activetuts+ to Your Circles
Just use the button above to add Activetuts+ to your circles, so you don’t miss out on any of our new content. We look forward to seeing you on Google+!
It’s that time of month again! If you’re a fan of the Activetuts+ Facebook page, you can now access a new bonus tutorial. In this month’s Facebook Fan Bonus, you’ll learn how to create a dynamic “Accordion” menu.
Final Result Preview
Here’s a look at the type of menu you’ll learn to build in the tutorial:
Obviously, in your own game, you’ll add other elements to the menu’s different screens.
As Tyler Seitz, the author, describes it:
Download This Fan Bonus Now!
All you have to do is Like us…
Not On Facebook?
Don’t worry, the tutorial will be posted on Activetuts+ in a month’s time!
Today, you will learn how to remove the background color from a sprite sheet using AS3, and blit the result to a bitmap canvas. Read on to learn more!
Final Result Preview
Let’s take a look at the final result we will be working towards:
Step 1: Drawing the Spritesheet
So, it is time to draw your spritesheet. Open up your favourite ‘pixel-art’ program and create an image of 128×128 and give it a background colour of ‘#e731f2′ which is a nice purple colour!
This is my amazing artwork:
Save your image somewhere organised and let us continue!
Step 2: Importing the Sprite Sheet
Now, I’m call this a sprite sheet even though it is just one image. A ‘sprite sheet’ usually consists of more than one sprite but we can imagine we have more, right?
Anyway, if you are using Flash CS4 or higher, simply import your image via File | Import | Import to Library:
If you are using any other AS3 IDE, I have included the SWC file so you should probably skip this step. If you wish to embed your own images, I’m sure that your IDE of choice will have this feature.
Step 3: Exporting the Sprite Sheet
We have now got our sprite sheet in the Library; we should really make it into a
Class.Right-click the image in the library and select ‘Properties’. Give the image the following properties:
Hit OK. If you get a warning, just ignore it; it does not matter here.
Step 4: Creating the Document Class
If you’re not familiar with the concept of a document class, check out this Quick Tip before reading further.
Create a new ‘ActionScript 3.0 Class’ and give it the name ‘Main’. When the file has been created, save it as ‘Main.as’.
This code should be placed in our new Class:
package { import flash.display.MovieClip; public class Main extends MovieClip { public function Main() { // constructor code } } }We are not done yet, however! If you are using the Flash IDE, navigate to the ‘Properties Panel’ and set the ‘Document Class’ to ‘Main’. If you are wondering what that does, it means that when your application/game is run by the Flash Player,
Main.aswill be the class that’s linked to the SWF itself. Cool, huh?Run the program; if you get no errors then you should be good to go!
Step 5: Creating the Canvas
Before we do any blitting, we will first need to make a canvas to blit onto. If you are unsure of the term Blitting or would like to learn more about it, please take a look at this tutorial.
Now, we will declare a new Bitmap variable, to which we will blit (copy) the image.
After we have done this, we will add a
functioncalledInitialize()which will allow us to set everything up neatly:public function Main() { Initialize(); }Let us create the function now:
private function Initialize():void { canvas = new Bitmap( new BitmapData( 550, 400, false, 0x000000 ) ); //Sets the Canvas to Black. stage.addChild( canvas ); //Adds the canvas to the stage. }We are still not finished however, as we still have to add the
imports:Run the program; if it has a black background, it worked!
Step 6: Initializing the SpriteSheet
Firstly, we will need to make a new variable of type
SpriteSheet– which was the Class for the image we imported earlier, remember?We shall then initialize it:
private function Initialize():void { canvas = new Bitmap( new BitmapData( 550, 400, false, 0x000000 ) ); //Sets the Canvas to Black. spriteSheet = new SpriteSheet(); //Sets spriteSheet to hold an instance of the image that we made. stage.addChild( canvas ); //Adds the canvas to the stage. }Run the program and you should see nothing; let’s fix that right away!
Step 7: Updating the Program
Now we need to add an
ENTER_FRAMEevent. This will allow the program to update 24 times a second (24 FPS) in our case.In the
Main()function, add the following line:public function Main() { Initialize(); stage.addEventListener( Event.ENTER_FRAME, Update ); }Now we need to make the
Update()function. Add this function after the other functions:private function Update(e:Event):void { }Don’t forget the
imports:Now we are ready to do some blitting!
Step 8: Blitting
Here comes the interesting part!
Alright, so what we want to do is:
In the update function, type the following code:
private function Update(e:Event):void { canvas.bitmapData.lock(); canvas.bitmapData.fillRect( new Rectangle( 0,0,stage.width, stage.height ), 0x000000 ); canvas.bitmapData.copyPixels( spriteSheet, new Rectangle( 0,0,128,128 ), new Point( 100, 100 ) ); canvas.bitmapData.unlock(); }If you run this, you will get your image on the canvas! However, this is not just what we are aiming for as we wish to remove that background colour from the image.
I shall explain some of the code above first:
canvas.bitmapData.lock();– This line optimizes the blitting and it is a good habit to type it most of the time!canvas.bitmapData.fillRect();– This line clears the canvas by filling it with a Black colour.canvas.bitmapData.copyPixels();– Not very useful in our situation but copies all the pixels from part of an image.canvas.bitmapData.unlock();– This works withlock()to optimize the process.Now you should have this on the screen…
Yes, I know, you are probably right. I think we should get rid of the purple too…
Step 9: Removing the Colour
Finally, it’s time to remove the purple colour!
What we want to do is check through every pixel; if the pixel is purple, we simply do not copy it to the canvas. To do this, we will make our own function.
Change
Update()to the following:private function Update(e:Event):void { canvas.bitmapData.lock(); canvas.bitmapData.fillRect( new Rectangle( 0,0,stage.width, stage.height ), 0x000000 ); CustomBlitting( spriteSheet, new Rectangle( 0,0,128,128 ), new Point( 100, 100 ), 0xE730F2 ); canvas.bitmapData.unlock(); }Our new function (
CustomBlitting(), which we have not written yet) takes most of the parameters that copyPixels does, along with an extra one: the colour we wish to remove.Time to write the function. This code may look complicated if you have never done a
nested for-loopbefore. The way this loop works is basically:private function CustomBlitting( src:BitmapData, srcRect:Rectangle, destPoint:Point, color:uint ):void { for( var i:int = 0; i < srcRect.height; i++ ) { for( var j:int = 0; j < srcRect.width; j++ ) { var currentPixel:uint = src.getPixel( srcRect.x + j, srcRect.y + i ); if( currentPixel != color ) { canvas.bitmapData.setPixel( destPoint.x + j, destPoint.y + i, currentPixel ); } } } }Let me explain the getPixel and setPixel, although they should probably be self-explanatory:
getPixel( x, y );– This returns the colour of a pixel at the X,Y location.setPixel( x, y, color );– This sets the colour of a pixel tocolorat the X,Y location of the canvas.Now if you run the program, it works!
Step 10: Challenges
I only have one challenge for you to do for this tutorial:
Accept an Array of colours as a parameter and remove any colours from the image that are in the array.
Good luck!
Conclusion
I hope you have enjoyed this tutorial and have learnt something new today. If you’d like to show me your SWF with the completed challenges, leave a comment below!
The Starling Framework is an AS3 API that mimics the display list, with one crucial difference: it uses Flash Player 11′s new features to render all content by the GPU. In this tutorial, you will learn to add multiple animations to a sprite using the Starling Framework. It assumes a basic understanding of using spritesheets with Starling.
Final Result Preview
Click here to view the demo we’ll be working towards. Click left or right of the monkey to make it walk in that direction; notice how it displays a walking animation when moving and a static frame when standing still.
Initial Code
The entire demo’s source files (in both the initial and final states) are in the source download. However, we’ll mainly be working with one file,
MonkeySprite.as, so here are the contents, for reference:package { import flash.utils.getTimer; import starling.animation.Juggler; import starling.core.Starling; import starling.display.MovieClip; import starling.display.Sprite; import starling.events.Event; import starling.textures.Texture; import starling.textures.TextureAtlas; public class MonkeySprite extends Sprite { private var standMovie:MovieClip; // standing animation private var mouseX:Number = -1; // location of mouse click on x axis private var lastTime:int; // last onFrame movement private var movieVector:Vector.<MovieClip>; public function MonkeySprite() { var atlas:TextureAtlas = Assets.getTextureAtlas(); var standFrames:Vector.<Texture> = atlas.getTextures("monkey_1"); standMovie = new MovieClip(standFrames, 7 ); standMovie.loop = true; addChild(standMovie); pivotX = this.width * 0.5; this.addEventListener(Event.ENTER_FRAME, onFrame); } public function onFrame(e:Event):void { if(mouseX == -1) return; // snap if(this.x <= mouseX+5 && this.x >= mouseX-5) { this.x = mouseX; } else { var timeDiff:int = getTimer()-lastTime; lastTime += timeDiff; if(mouseX > this.x) { this.x += timeDiff*.15; } else { this.x -= timeDiff*.15; } } } public function moveToPoint(pointX:Number):void { mouseX = pointX; lastTime = getTimer(); } } }Watch the Screencast
Don’t like ads? Download the screencast, or subscribe to Activetuts+ screencasts via iTunes!
Here are the URLs mentioned in the video:
Want More Starling Tutorials?
Let us know in the comments!
In the last tutorial, we introduced FlashPunk and its capabilities. Now it’s time to build a game with it! We’ll build a top-down, mouse-controlled shoot-’em-up, with a title screen and a game over screen. Read on to learn more…
Final Result Preview
Let’s take a look at the final result we will be working towards:
Step 1: The Player Ship
As always, first we need a clean project. Grab the latest FlashPunk build from the official site. Create a new AS3 project in FlashDevelop and put FlashPunk’s source in the source folder of the project. The game will have the following dimensions: 400×500 px.
We will begin our game by adding a ship – the player ship – on the screen. For that, we will need a new world, called
GameWorld, and an image:The player ship will be an entity. Create one, embed the image in it and put it on the screen. If you’re feeling lost, below is the code for the player ship (if you’re really feeling lost, I recommend reading the first tutorial again). I think that creating the world won’t be a problem for you.
package { import net.flashpunk.Entity; import net.flashpunk.graphics.Image; public class PlayerShip extends Entity { [Embed(source = '../img/PlayerShipImage.png')] private const IMAGE:Class; public function PlayerShip() { graphic = new Image(IMAGE); graphic.x = -27.5; graphic.y = -30; x = 200; y = 400; } } }Add the player ship into the world and make it the current world when FlashPunk’s
Enginestarts. You’ll get the following:Step 2: Movement
With the player ship on the screen, we need to make it move. Just like every shoot-’em-up game, the player ship will be able to move across all the screen. There’s one thing left to decide before coding the movement: we will be using frame-based movement (the reason for this is in the next step). That means changing (if needed) the
super()call to FlashPunk’sEngineand NOT using theFP.elapsedproperty.The code for the player ship movement is below. The movement is mouse-based:
private var _currentDistanceX:Number; private var _currentDistanceY:Number; private const SPEED:int = 3; override public function update():void { calculateDistances(); if ((_currentDistanceX * _currentDistanceX) + (_currentDistanceY * _currentDistanceY) < SPEED * SPEED) { x = Input.mouseX; y = Input.mouseY; } else { x += Math.cos(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED; y += Math.sin(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED; } } private function calculateDistances():void { _currentDistanceX = Input.mouseX - x; _currentDistanceY = Input.mouseY - y; }You will need to import
net.flashpunk.utils.Inputin the player ship class. Also, don’t forget to enable FlashPunk’s console. After compiling the project, you will see the player ship following the mouse:Step 3: Enemies
Time to add something fun: enemies. Enemies need to spawn randomly (based on waves) and need to have a controlled movement. They also have a different image:
The code for creating the enemy class and adding the image to it shouldn’t be a problem. What we need to focus on is how to make enemies follow a path. The idea is this: we create a wave of enemies and pass them a path to follow, and when to spawn. After that, they will be responsible for showing themselves on the screen and moving along the path.
What is a good representation for a path? A Vector of points seems good enough. Also, we need to define a distance between each point. That way, we can be sure of one important thing: if we define the distance between two points of the path to be the distance that the enemy moves during 1 frame, we won’t need the enemy to process too much information, and we will be able to define after how many frames the enemy will leave the screen by just counting the number of points in the vector. That way, it’s very easy to define when a wave will end.
See it in action: the code below only makes enemies follow a path already given to them, and on the time passed to them (which will be counted in frames). When the enemy is created, a counter that starts on a given number begins decreasing after each elapsed frame, and when it reaches zero, the enemy will put itself on screen and begin following its path.
package { import flash.geom.Point; import net.flashpunk.Entity; import net.flashpunk.FP; import net.flashpunk.graphics.Image; import net.flashpunk.World; public class Enemy extends Entity { [Embed(source = '../img/EnemyImage.png')] private const IMAGE:Class; private var _timeToAct:uint; private var _pathToFollow:Vector.<Point>; private var _currentPoint:uint; private var _myWorld:World; private var _added:Boolean; public function Enemy(timeToAct:uint, pathToFollow:Vector.<Point>, worldToBeAdded:World) { graphic = new Image(IMAGE); graphic.x = -15; graphic.y = -8; _timeToAct = timeToAct; _pathToFollow = pathToFollow; _currentPoint = 0; _myWorld = worldToBeAdded; _added = false; } override public function update():void { if (_timeToAct > 0) { _timeToAct--; } else { if (!_added) { _myWorld.add(this); _added = true; } x = _pathToFollow[_currentPoint].x; y = _pathToFollow[_currentPoint].y; _currentPoint++; if (_currentPoint == _pathToFollow.length) { _myWorld.remove(this); _added = false; destroy(); } } } public function destroy():void { graphic = null; } } }From the code above, you can see that we always move a fixed distance from point to point by just positioning the enemy on its current point. It is because of this that we are able to determine when an enemy wave will end. You can also see that the enemy takes care of everything: adding itself in the world, moving through the world and removing itself from the world. With that, it’s very simple to create enemies in the game.
Step 4: Adding Enemies to the Screen
Our base
Enemyclass is done. Now it’s time to modifyGameWorlda bit to add enemies. The first task is to generate paths for the enemies. For the purposes of this tutorial, we will only create a straight line, but feel free to try creating any kind of wave path. This is the function that creates a straight line:private function generateEnemyPath(distanceBetweenPoints:Number):Vector.<Point> { var i:Number; var vec:Vector.<Point> = new Vector.<Point>(); var xPos:Number = Math.random() * 360 + 20; for (i = -20; i < 520; i += distanceBetweenPoints) { vec.push(new Point(xPos, i)); } return vec; }With that, we can already give to an enemy a path to follow. The next step is to actually create the enemy:
private var _enemy:Enemy; public function GameWorld() { _playerShip = new PlayerShip(); add(_playerShip); _enemy = new Enemy(0, generateEnemyPath(1), this); } override public function update():void { super.update(); if (_enemy) _enemy.update(); }Compile and run the game. You’ll probably get the following error:
That happens because even after the enemy deletes itself from the world, we are still calling the
update()function of it, because our code didn’t detect when the enemy removed itself. Let’s fix that by overriding the currentremove()method:override public function remove(e:Entity):Entity { if (e is Enemy) { _enemy = new Enemy(0, generateEnemyPath(1), this); } return super.remove(e); }Now compile the project and you’ll see this:
This is what that function does: every time the enemy removes itself from the world, we detect that through our overriden function and then just create another enemy to “replace” the old one. That’s it! We now have enemies moving across the screen!
Step 5: Shooting With the Player Ship
A shoot-’em-up game wouldn’t be fun without cool ways of shooting bullets out of your ship, would it? In this step we’ll see a great way of organizing bullet patterns in order to shoot them. If you tried to take a guess, you are probably correct: we’re going to use Vectors of Points. However, this time the points will have dynamic beginnings and ends, because your ship won’t always be at the same place every time you shoot, but don’t worry, it’s not as hard as it sounds!
The strategy here is to generate a bullet pattern around a fixed x- and y-axis, and then sum the player ship’s position to the points from the pattern, thus relocating the axis to a new position, giving the impression that the bullets are coming out of the player ship. The bullet image we are going to use is this:
Everything gets simpler when you look at the code. We are basically doing something very similar to the enemy path:
package { import flash.geom.Point; import net.flashpunk.Entity; import net.flashpunk.graphics.Image; public class PlayerBullet extends Entity { [Embed(source = '../img/BulletImage.png')] private const IMAGE:Class; private var _pathToFollow:Vector.<Point>; private var _xPos:Number; private var _yPos:Number; public function PlayerBullet(pathToFollow:Vector.<Point>, xPos:Number, yPos:Number) { graphic = new Image(IMAGE); graphic.x = graphic.y = -3.5; _pathToFollow = pathToFollow; _xPos = xPos; _yPos = yPos; } override public function update():void { x = _xPos + _pathToFollow[0].x; y = _yPos + _pathToFollow[0].y; _pathToFollow.shift(); if (_pathToFollow.length == 0) { world.remove(this); destroy(); } } public function destroy():void { _pathToFollow = null; graphic = null; } } }Notice that we don’t really create the bullet patterns in here: they’re always passed as parameters, just like the enemies. The only difference is that the bullets are always added right away in the world and we keep the initial position of the bullet.
Let’s try adding a bullet when the player clicks with the mouse. In
PlayerShip.as:override public function update():void { calculateDistances(); if ((_currentDistanceX * _currentDistanceX) + (_currentDistanceY * _currentDistanceY) < SPEED * SPEED) { x = Input.mouseX; y = Input.mouseY; } else { x += Math.cos(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED; y += Math.sin(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED; } if (Input.mousePressed) { world.add(new PlayerBullet(GameWorld(world).generateBulletPath(3), x, y)); } }Now we need to create the bullet path. We are going to create a straight line, just like for the enemy, but you can do any kind of path! In
GameWorld.as, let’s create thegenerateBulletPath()function:public function generateBulletPath(distanceBetweenPoints:Number):Vector.<Point> { var i:Number; var vec:Vector.<Point> = new Vector.<Point>(); for (i = 0; i > -500; i -= distanceBetweenPoints) { vec.push(new Point(0, i)); } return vec; }With that, hit the compile and run button and this is what you get:
Step 6: Collision Detection (Using Masks)
We have now the basics of the game running: a player ship that shoots and enemies that go down the screen. Time to add collision detection!
The first step to add the collision detection is to give each entity a type. I’ll leave that to you: give the “Player” type to
PlayerShip, “Enemy” toEnemyand “PlayerBullet” toPlayerBullet.We will be using pixel-perfect collision in this game, so it might be useful to talk about masks. Masks are elements used by FlashPunk for collision detection. They are basically like hitboxes, but they can have a different form (pixel level). We need to set up masks for the player ship, enemies and bullets. The image used by the mask is the same image of the entity. Look at the code for the
PlayerShip,EnemyandPlayerBullet, respectively:public function PlayerShip() { graphic = new Image(IMAGE); graphic.x = -27.5; graphic.y = -30; mask = new Pixelmask(IMAGE, -27.5, -30); x = 200; y = 400; type = "Player"; }public function Enemy(timeToAct:uint, pathToFollow:Vector.<Point>, worldToBeAdded:World) { graphic = new Image(IMAGE); graphic.x = -15; graphic.y = -8; mask = new Pixelmask(IMAGE, -15, -8); _timeToAct = timeToAct; _pathToFollow = pathToFollow; _currentPoint = 0; _myWorld = worldToBeAdded; _added = false; type = "Enemy"; }public function PlayerBullet(pathToFollow:Vector.<Point>, xPos:Number, yPos:Number) { graphic = new Image(IMAGE); graphic.x = graphic.y = -3.5; mask = new Pixelmask(IMAGE, -3.5, -3.5); _pathToFollow = pathToFollow; _xPos = xPos; _yPos = yPos; type = "PlayerBullet"; }As you can see, this is very simple: we create a new
Pixelmask, pass the source to use as a mask (just like with the graphic) and then pass both x and y offsets (in case you want to center the mask somewhere). Now, inGameWorld.as:private var _bulletList:Vector.<PlayerBullet>; override public function update():void { super.update(); if (_enemy) _enemy.update(); _bulletList = new Vector.<PlayerBullet>(); getType("PlayerBullet", _bulletList); for each (var bullet:PlayerBullet in _bulletList) { if (bullet.collideWith(_enemy, bullet.x, bullet.y)) { _enemy.takeDamage(); remove(bullet); bullet.destroy(); } } }Notice that we could have simply used
_enemy.collide("PlayerBullet", _enemy.x, _enemy.y)to check for collisions, but the method above is better when we have many bullets on the screen and there is a possibility that two bullets hit the same enemy at the same time. We have called thetakeDamage()function of theEnemyclass, but at the moment there is none. (Create an empty function for now. In the next step we will make the enemy take damage and explode when necessary.) Compile the project and you’ll get this:Step 7: Enemy Death
We have made bullets hit our enemies. In this step we will play an explosion animation every time an enemy dies and remove it from the game. The explosion animation sprite sheet is below:
The approach we will take to do this is by decreasing the enemy’s health in the
takeDamage()function, and if the health gets below zero, we will destroy it and put the animation in the screen. The code for decreasing the health is below:private var _health:int = 100; public function takeDamage():void { _health -= 50; if (_health <= 0) { addExplosion(); _myWorld.remove(this); _added = false; destroy(); } }The code is very simple. There’s only one thing unknown about it: the
addExplosion()function. This function will create an instance ofExplosionand add it to the world. TheExplosionclass will just play and remove itself from the world after that. It’s a simple class:package { import net.flashpunk.Entity; import net.flashpunk.graphics.Spritemap; public class Explosion extends Entity { [Embed(source = '../img/ExplosionAnimation.png')] private const ANIMATION:Class; public function Explosion(xPos:Number, yPos:Number) { graphic = new Spritemap(ANIMATION, 50, 46, onAnimationEnd); graphic.x = -25; graphic.y = -23; x = xPos; y = yPos; Spritemap(graphic).add("Explode", [0, 1, 2, 3, 4], 25/60, false); Spritemap(graphic).play("Explode"); } private function onAnimationEnd():void { world.remove(this); destroy(); } public function destroy():void { graphic = null; } } }The trick here is using the
callbackparameter of theSpritemapclass: when the animation ends, that function will be called, and then it removes itself from the world.Now, back to
Enemy.asto finish that function!private function addExplosion():void { world.add(new Explosion(x, y)); }Easy, isn’t it? Compile the game and destroy some enemies!
Step 8: Score
The next logical step after making enemies die is to add a score in the game! We will do that using FlashPunk’s
Textclass. Start by creating aGameScoreclass, which will hold the score of the game. SinceTextis aGraphic, we will makeGameScoreanEntityand add its text as agraphic. Look at the code:package { import net.flashpunk.Entity; import net.flashpunk.graphics.Text; public class GameScore extends Entity { private var _score:int; public function GameScore() { graphic = new Text("Score: 0"); _score = 0; } public function addScore(points:int):void { _score += points; Text(graphic).text = "Score: " + _score.toString(); } public function destroy():void { graphic = null; } } }As you can see, we will call the
addScore()function to add points to the game’s score. First, we need to add it to the world. InGameWorld.as:private var _score:GameScore; public function GameWorld() { _playerShip = new PlayerShip(); add(_playerShip); _enemy = new Enemy(0, generateEnemyPath(1), this); _score = new GameScore(); _score.x = 300; _score.y = 470; add(_score); } public function get score():GameScore { return _score; } public function get score():int { return _score; }If we hit compile, we get just a static score on the bottom of the screen:
We need to add something to the score every time an enemy is killed. In
Enemy.as:public function takeDamage():void { _health -= 50; if (_health <= 0) { addExplosion(); GameWorld(world).score.addScore(1); _myWorld.remove(this); _added = false; destroy(); } }Hit compile now and the score will always increase when an enemy is killed!
Step 9: Upgrades
Time to add upgrades! We will not have a nice screen to choose upgrades. Instead, we will create upgrades based on the score: each time the score goes up by 5 (up to 45), the player’s speed will increase a bit. When the score reaches 25, the player will be able to shoot two shots on every click. We will make the score 50 be the end of the game.
Let’s begin by coding the player speed upgrade. For that, we will need to add a multiplier to the speed. In
PlayerShip.as:public var speedMultiplier:Number = 1; override public function update():void { calculateDistances(); if ((_currentDistanceX * _currentDistanceX) + (_currentDistanceY * _currentDistanceY) < SPEED * SPEED * speedMultiplier * speedMultiplier) { x = Input.mouseX; y = Input.mouseY; } else { x += Math.cos(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier; y += Math.sin(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier; } if (Input.mousePressed) { world.add(new PlayerBullet(GameWorld(world).generateBulletPath(3), x, y)); } }Now it’s all about changing the multiplier in
GameWorld.as:private var _speedUpgradeNumber:int = 0; override public function update():void { super.update(); if (_enemy) _enemy.update(); _bulletList = new Vector.<PlayerBullet>(); getType("PlayerBullet", _bulletList); for each (var bullet:PlayerBullet in _bulletList) { if (bullet.collideWith(_enemy, bullet.x, bullet.y)) { _enemy.takeDamage(); remove(bullet); bullet.destroy(); } } if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5)) { _playerShip.speedMultiplier += 0.1; _speedUpgradeNumber++; } }Done! Now, every 5 enemy deaths the player will receive a 10% increase in moving speed!
For the double bullet upgrade, we will make a boolean in the
PlayerShipclass indicating whether or not the ship has the upgrade. Then we will check that when shooting. Here is it:public var hasDoubleShoot:Boolean = false; override public function update():void { calculateDistances(); if ((_currentDistanceX * _currentDistanceX) + (_currentDistanceY * _currentDistanceY) < SPEED * SPEED * speedMultiplier * speedMultiplier) { x = Input.mouseX; y = Input.mouseY; } else { x += Math.cos(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier; y += Math.sin(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier; } if (Input.mousePressed) { if (hasDoubleShoot) { world.add(new PlayerBullet(GameWorld(world).generateBulletPath(3), x - 5, y)); world.add(new PlayerBullet(GameWorld(world).generateBulletPath(3), x + 5, y)); } else { world.add(new PlayerBullet(GameWorld(world).generateBulletPath(3), x, y)); } } }Now, let’s do the same as we did for the speed in
GameWorld.as:override public function update():void { super.update(); if (_enemy) _enemy.update(); _bulletList = new Vector.<PlayerBullet>(); getType("PlayerBullet", _bulletList); for each (var bullet:PlayerBullet in _bulletList) { if (bullet.collideWith(_enemy, bullet.x, bullet.y)) { _enemy.takeDamage(); remove(bullet); bullet.destroy(); } } if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5)) { _playerShip.speedMultiplier += 0.1; _speedUpgradeNumber++; } if (_score.score >= 25) { _playerShip.hasDoubleShoot = true; } }And that’s it! Hit compile and your game will have upgrades!
Step 10: Increasing the Difficulty
If you played the game until getting the double shot upgrade in the last step, you may have noticed that the game is too easy right now. What do you think of increasing its difficulty based on the player’s score?
That’s the idea: the game will now be able to put on the screen more than one enemy. And for each kill the player gets, we decrease the timer of spawning a new enemy. Interesting, isn’t it? But that’s not the only thing. What about increasing the enemies’ healths and decreasing the damage they take from the player? Now we’re getting somewhere!
Let’s jump to the coding: first we will make the health increase and damage decrease every kill. They will be just like the upgrades on the player ships, but this time we will need to change our approach as to where to keep the multipliers. In
Enemy.as:private var _health:int; public static var healthMultiplier:Number = 1; public static var damageMultiplier:Number = 1; public function Enemy(timeToAct:uint, pathToFollow:Vector.<Point>, worldToBeAdded:World) { graphic = new Image(IMAGE); graphic.x = -15; graphic.y = -8; mask = new Pixelmask(IMAGE, -15, -8); _timeToAct = timeToAct; _pathToFollow = pathToFollow; _currentPoint = 0; _myWorld = worldToBeAdded; _added = false; type = "Enemy"; health = 100 * Enemy.healthMultiplier; } public function takeDamage():void { _health -= 50 * Enemy.damageMultiplier; if (_health <= 0) { addExplosion(); GameWorld(world).score.addScore(1); _myWorld.remove(this); _added = false; destroy(); } }And now, in
GameWorld.as:private var _enemyUpgradeNumber:int = 0; override public function update():void { super.update(); if (_enemy) _enemy.update(); _bulletList = new Vector.<PlayerBullet>(); getType("PlayerBullet", _bulletList); for each (var bullet:PlayerBullet in _bulletList) { if (bullet.collideWith(_enemy, bullet.x, bullet.y)) { _enemy.takeDamage(); remove(bullet); bullet.destroy(); } } if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5)) { _playerShip.speedMultiplier += 0.1; _speedUpgradeNumber++; } if (_score.score >= 25) { _playerShip.hasDoubleShoot = true; } if (_score.score > _enemyUpgradeNumber) { Enemy.damageMultiplier -= 0.015; Enemy.healthMultiplier += 0.02; _enemyUpgradeNumber++; } }And now our enemies are getting stronger after each kill! Take that, evil player!
Now we need to modify
GameWorldin order to spawn enemies based on time. It’s a simple thing: we will just need a timer and a spawn interval. Here’s all the modified code:private var _enemyList:Vector.<Enemy>; private var _enemySpawnInterval:int = 5000; private var _enemySpawnTimer:int; public function GameWorld() { _playerShip = new PlayerShip(); add(_playerShip); _enemyList = new Vector.<Enemy>(); _score = new GameScore(); _score.x = 300; _score.y = 470; add(_score); _enemySpawnTimer = 0; } override public function update():void { super.update(); _enemySpawnTimer--; if (_enemySpawnTimer <= 0) { _enemySpawnTimer = _enemySpawnInterval; _enemyList.push(new Enemy(uint(Math.random() * 30), generateEnemyPath(2), this)); add(_enemyList[_enemyList.length - 1]); } _bulletList = new Vector.<PlayerBullet>(); getType("PlayerBullet", _bulletList); for each (var bullet:PlayerBullet in _bulletList) { for each (var enemy:Enemy in _enemyList) { if (bullet.collideWith(enemy, bullet.x, bullet.y)) { enemy.takeDamage(); remove(bullet); bullet.destroy(); } } } if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5)) { _playerShip.speedMultiplier += 0.1; _speedUpgradeNumber++; } if (_score.score >= 25) { _playerShip.hasDoubleShoot = true; } if (_score.score > _enemyUpgradeNumber) { Enemy.damageMultiplier -= 0.015; Enemy.healthMultiplier += 0.02; _enemyUpgradeNumber++; } } override public function remove(e:Entity):Entity { if (e is Enemy) { _enemyList.splice(_enemyList.indexOf(e), 1); } return super.remove(e); }Notice that I gave the enemies a few random frames of “wait time” before appearing. That will make their appearance unpredictable. Now all that’s left is to decrease the spawn interval after every kill:
override public function update():void { super.update(); _enemySpawnTimer--; if (_enemySpawnTimer <= 0) { _enemySpawnTimer = _enemySpawnInterval; _enemyList.push(new Enemy(uint(Math.random() * 30), generateEnemyPath(2), this)); add(_enemyList[_enemyList.length - 1]); } _bulletList = new Vector.<PlayerBullet>(); getType("PlayerBullet", _bulletList); for each (var bullet:PlayerBullet in _bulletList) { for each (var enemy:Enemy in _enemyList) { if (bullet.collideWith(enemy, bullet.x, bullet.y)) { enemy.takeDamage(); remove(bullet); bullet.destroy(); } } } if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5)) { _playerShip.speedMultiplier += 0.1; _speedUpgradeNumber++; } if (_score.score >= 25) { _playerShip.hasDoubleShoot = true; } if (_score.score > _enemyUpgradeNumber) { Enemy.damageMultiplier -= 0.015; Enemy.healthMultiplier += 0.02; _enemySpawnInterval -= 3; _enemyUpgradeNumber++; } }With that, we basically have everything done in the game world!
Step 11: The Main Menu World
I think we have everything finished in the game world. The game’s surprisingly difficult with the tougher enemies! What do you think of making a nice main menu now? I created a nice background for it:
Create the
MainMenuWorldclass, which extends fromnet.flashpunk.World, and add the background in it. I’ll leave the code for you. We will need a play button, which I also created:In order to create the play button, we are going to use the
Buttonclass created in the first part of this tutorial series. Here’s the code for the button inMainMenuWorld.as:[Embed(source = '../img/PlayGameButton.png')] private const PLAYBUTTON:Class; private var _playButton:Button; public function MainMenuWorld() { addGraphic(new Image(TITLE)); _playButton = new Button(playTheGame, null, 48, 395); _playButton.setSpritemap(PLAYBUTTON, 312, 22); add(_playButton); } private function playTheGame():void { FP.world = new GameWorld(); destroy(); } public function destroy():void { removeAll(); _playButton = null; }Don’t forget to change the
Mainclass as well!override public function init():void { FP.world = new MainMenuWorld(); FP.console.enable(); }Hit compile and… Yes! Our shiny main menu world works! Now to the game over world!
Step 12: The Game Over World
The game over world is going to be very simple. I have created two images: one for when the player dies and one for when the player wins the game. There will be a Quit button that will return the player to the main menu. It’s basically the same thing as the main menu world. Here are the two images and the button:
I will leave the coding to you. The only thing that will change in this class is that it will need an argument passed to the constructor, telling whether or not the player destroyed the enemies. Here’s the code for the constructor:
public function GameOverWorld(hasLost:Boolean) { if (hasLost) { addGraphic(new Image(BACKGROUNDLOST)); } else { addGraphic(new Image(BACKGROUNDWON)); } _quitButton = new Button(quitToMain, null, 166, 395); _quitButton.setSpritemap(QUITBUTTON, 69, 19); add(_quitButton); }Step 13: The Boss – Movement
Finally, the moment everyone was waiting for. Every shoot-’em-up needs a boss, and this is ours!
What we need now is code it. First, the movements. And then, the bullets. This step is for the movements.
As you may have guessed, our boss won’t go directly down the screen. Instead, it will move randomly around the top of the screen, to give the player a bit of difficulty defeating it. What we will need to do is a movement very similar to the player’s. The only difference is that it will follow a point chosen randomly in the top of the screen, and not the mouse. Here’s the full code for the
Bossclass!package { import flash.geom.Point; import net.flashpunk.Entity; import net.flashpunk.graphics.Image; import net.flashpunk.masks.Pixelmask; public class Boss extends Entity { [Embed(source = '../img/BossImage.png')] private const IMAGE:Class; private var _currentDistanceX:Number; private var _currentDistanceY:Number; private var _randomPoint:Point; private const SPEED:int = 3; public var speedMultiplier:Number = 1; public function Boss() { graphic = new Image(IMAGE); graphic.x = -38; graphic.y = -35; mask = new Pixelmask(IMAGE, -38, -35); type = "BossEnemy"; getRandomPoint(); } private function getRandomPoint():void { _randomPoint = new Point(); _randomPoint.x = Math.random() * 400; _randomPoint.y = 38 + (Math.random() * 100); } override public function update():void { calculateDistances(); if ((_currentDistanceX * _currentDistanceX) + (_currentDistanceY * _currentDistanceY) < SPEED * SPEED * speedMultiplier * speedMultiplier) { x = _randomPoint.x; y = _randomPoint.y; getRandomPoint(); } else { x += Math.cos(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier; y += Math.sin(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier; } } private function calculateDistances():void { _currentDistanceX = _randomPoint.x - x; _currentDistanceY = _randomPoint.y - y; } } }We are basically using the same movement code from
PlayerShip. Notice that we have kept the speed multipliers, because there will be a really fun thing in the end!We need now to come up with a way to test this movement. Running the game and getting 50 kills is too much time to wait until we can see the boss (and end up realizing there’s a bug and we will need to do it all again and again!). Let’s just add the boss on the screen when the game begins (yes, with the other enemies going down the screen!) and check the movement! In
GameWorld.as:private var _boss:Boss; public function GameWorld() { _playerShip = new PlayerShip(); add(_playerShip); _enemyList = new Vector.<Enemy>(); _score = new GameScore(); _score.x = 300; _score.y = 470; add(_score); _enemySpawnTimer = 0; _boss = new Boss(); add(_boss); }Hit compile and test the game! Our boss is moving nicely, isn’t it? It’s going to be hard to kill it!
Step 14: The Boss – Shooting
Time for the really evil boss bullets! Here is their image:
But there is something really bothering me: they will have exactly the same behavior as the player bullet, but instead they will just follow the same enemy downward path, and will have a different FlashPunk
type, but I don’t want to copy and paste the same code in their class. What do you think about using some Object-Oriented Design and make an inheritance? Take all the code (yes, all the code) ofPlayerBulletand copy it to a new class calledBullet. Remove the code related to the bullet’s image, and this is what you get:package { import flash.geom.Point; import net.flashpunk.Entity; public class Bullet extends Entity { private var _pathToFollow:Vector.<Point>; private var _xPos:Number; private var _yPos:Number; public function Bullet(pathToFollow:Vector.<Point>, xPos:Number, yPos:Number) { _pathToFollow = pathToFollow; _xPos = xPos; _yPos = yPos; } override public function update():void { x = _xPos + _pathToFollow[0].x; y = _yPos + _pathToFollow[0].y; _pathToFollow.shift(); if (_pathToFollow.length == 0) { world.remove(this); destroy(); } } public function destroy():void { _pathToFollow = null; graphic = null; } } }That’s the basic behavior of the bullet. Now, what do we do with the
PlayerBulletclass? Put only the things related to the bullet image in there, and remove the rest. And also make it inherit fromBullet:package { import flash.geom.Point; import net.flashpunk.graphics.Image; import net.flashpunk.masks.Pixelmask; public class PlayerBullet extends Bullet { [Embed(source = '../img/BulletImage.png')] private const IMAGE:Class; public function PlayerBullet(pathToFollow:Vector.<Point>, xPos:Number, yPos:Number) { super(pathToFollow, xPos, yPos); graphic = new Image(IMAGE); graphic.x = graphic.y = -3.5; mask = new Pixelmask(IMAGE, -3.5, -3.5); type = "PlayerBullet"; } } }Do you mind making the same thing for the
BossBulletclass too? The only difference is that it will have a type of “BossBullet”. I’ll leave the code to you. Check the tutorial source if you need help!We could have done the same for the boss and the enemies, but we would need to change more things, because the movement of the enemies isn’t the same as the movement of the boss. It’s better to leave it that way. And now, for the boss shooting. We will use the same timer strategy that we used for the enemy spawning. Here’s the code:
private var _shootingInterval:int = 75; private var _shootingTimer:int = 0; override public function update():void { calculateDistances(); if ((_currentDistanceX * _currentDistanceX) + (_currentDistanceY * _currentDistanceY) < SPEED * SPEED * speedMultiplier * speedMultiplier) { x = _randomPoint.x; y = _randomPoint.y; getRandomPoint(); } else { x += Math.cos(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier; y += Math.sin(Math.atan2(_currentDistanceY, _currentDistanceX)) * SPEED * speedMultiplier; } _shootingTimer--; if (_shootingTimer <= 0) { _shootingTimer = _shootingInterval; world.add(new BossBullet(GameWorld(world).generateBossBulletPath(1.5), x - 10, y)); world.add(new BossBullet(GameWorld(world).generateBossBulletPath(1.5), x + 10, y)); world.add(new BossBullet(GameWorld(world).generateBossBulletPath(1.5), x, y + 5)); } }Compile and run the code and now you have a boss shooting bullets and flying around! Time to make the last battle happen in the next step
Step 15: Final Touches
You may have noticed that the boss’s bullets don’t hit the player ship, and the player’s bullets don’t hit the boss. That’s because we haven’t coded them to hit their enemies. That’s what we are going to do now. We will also make the boss only appear when the score reaches 50, and check for when the player loses the game.
The first task: to make the boss only appear when the score reaches 50. We will do that by checking for the score in
GameWorld.as, just like we did with the enemy and player upgrades.private var _bossAppeared:Boolean = false; public function GameWorld() { _playerShip = new PlayerShip(); add(_playerShip); _enemyList = new Vector.<Enemy>(); _score = new GameScore(); _score.x = 300; _score.y = 470; add(_score); _enemySpawnTimer = 0; _boss = new Boss(); // Not adding the boss in the game this time } override public function update():void { super.update(); _enemySpawnTimer--; if (_enemySpawnTimer <= 0 && !_bossAppeared) { _enemySpawnTimer = _enemySpawnInterval; _enemyList.push(new Enemy(uint(Math.random() * 30), generateEnemyPath(2), this)); add(_enemyList[_enemyList.length - 1]); } _bulletList = new Vector.<PlayerBullet>(); getType("PlayerBullet", _bulletList); for each (var bullet:PlayerBullet in _bulletList) { for each (var enemy:Enemy in _enemyList) { if (bullet.collideWith(enemy, bullet.x, bullet.y)) { enemy.takeDamage(); remove(bullet); bullet.destroy(); } } } if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5)) { _playerShip.speedMultiplier += 0.1; _speedUpgradeNumber++; } if (_score.score >= 25) { _playerShip.hasDoubleShoot = true; } if (_score.score > _enemyUpgradeNumber) { Enemy.damageMultiplier -= 0.015; Enemy.healthMultiplier += 0.02; _enemySpawnInterval -= 3; _enemyUpgradeNumber++; } if (_score.score == 50 && !_bossAppeared) { add(_boss); _bossAppeared = true; } }First task done. Notice that we also changed the block that spawned new enemies. That way, when the boss appears, no more enemies will spawn.
Second task: make boss bullets hit the player and player bullets hit the boss. This is also done in
GameWorld.as. Take a look at the code:private var _bulletList:Vector.<Bullet>; override public function update():void { super.update(); _enemySpawnTimer--; if (_enemySpawnTimer <= 0 && !_bossAppeared) { _enemySpawnTimer = _enemySpawnInterval; _enemyList.push(new Enemy(uint(Math.random() * 30), generateEnemyPath(2), this)); add(_enemyList[_enemyList.length - 1]); } _bulletList = new Vector.<Bullet>(); getType("PlayerBullet", _bulletList); for each (var bullet:PlayerBullet in _bulletList) { for each (var enemy:Enemy in _enemyList) { if (bullet.collideWith(enemy, bullet.x, bullet.y)) { enemy.takeDamage(); remove(bullet); bullet.destroy(); } } if (_bossAppeared) { if (bullet.collideWith(_boss, bullet.x, bullet.y)) { _boss.takeDamage(); if (!_boss) { endTheGame(); return; } remove(bullet); bullet.destroy(); } } } _bulletList = new Vector.<Bullet>(); getType("BossBullet", _bulletList); for each (var bossBullet:BossBullet in _bulletList) { if (bossBullet.collideWith(_playerShip, bossBullet.x, bossBullet.y)) { endTheGame(); return; } } if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5)) { _playerShip.speedMultiplier += 0.1; _speedUpgradeNumber++; } if (_score.score >= 25) { _playerShip.hasDoubleShoot = true; } if (_score.score > _enemyUpgradeNumber) { Enemy.damageMultiplier -= 0.015; Enemy.healthMultiplier += 0.02; _enemySpawnInterval -= 3; _enemyUpgradeNumber++; } if (_score.score == 50 && !_bossAppeared) { add(_boss); _bossAppeared = true; } }In the first part of the code, we check for a collision between the player’s bullets and the boss. If there is a collision, we call the
takeDamage()function from the boss, which is below. After checking the player’s bullets, we check for the boss’s bullets, and if we find a collision between any of them and the player, we end the game. This function is also below.Now, for the
takeDamage()function of the boss. We want the boss to have a lot of health, and we will make it fly faster after each hit. InBoss.as:private var _health:int = 1000; public function takeDamage():void { _health -= 50; speedMultiplier += 0.05; if (_health <= 0) { world.remove(this); destroy(); } } public function destroy():void { graphic = null; mask = null; _randomPoint = null; }Our boss battle is complete! It will die after 20 hits from the player, getting faster after each hit.
Now, the only thing remaining is to end the game and show the screen when the boss is killed, and also make the player lose before the boss battle if an enemy goes down the screen. For the end of the game, we need to check when the boss is killed and create the
endTheGame()function inGameWorld.as. This function will basically remove every bullet from the screen, remove the player and boss and then add the game over screen. InGameWorld.as:override public function remove(e:Entity):Entity { if (e is Enemy) { _enemyList.splice(_enemyList.indexOf(e), 1); } if (e is Boss) { _boss = null; } return super.remove(e); } public function endTheGame():void { removeAll(); while (_bulletList.length > 0) { _bulletList[0].destroy(); _bulletList.shift(); } while (_enemyList.length > 0) { _enemyList[0].destroy(); _enemyList.shift(); } _bulletList = null; _enemyList = null; _playerShip = null; _score = null; if (_boss) { FP.world = new GameOverWorld(true); } else { FP.world = new GameOverWorld(false); } _boss = null; }And that’s it! The
endTheGame()function is basically the same as adestroy()function. It just cleans every reference in the game world.The last part: making the player lose the game if an enemy has reached the end of the screen. For this one we’re going to remember that an enemy only reaches the bottom of the screen if it still has health. So, in the
remove()function ofGameWorld.as:private var _gameEnded:Boolean = false; override public function remove(e:Entity):Entity { if (e is Enemy) { if (Enemy(e).health > 0) { endTheGame(); return e; } _enemyList.splice(_enemyList.indexOf(e), 1); } if (e is Boss) { _boss = null; } return super.remove(e); } override public function update():void { super.update(); if (_gameEnded) { return; } _enemySpawnTimer--; if (_enemySpawnTimer <= 0 && !_bossAppeared) { _enemySpawnTimer = _enemySpawnInterval; _enemyList.push(new Enemy(uint(Math.random() * 30), generateEnemyPath(2), this)); add(_enemyList[_enemyList.length - 1]); } _bulletList = new Vector.<Bullet>(); getType("PlayerBullet", _bulletList); for each (var bullet:PlayerBullet in _bulletList) { for each (var enemy:Enemy in _enemyList) { if (bullet.collideWith(enemy, bullet.x, bullet.y)) { enemy.takeDamage(); remove(bullet); bullet.destroy(); } } if (_bossAppeared) { if (bullet.collideWith(_boss, bullet.x, bullet.y)) { _boss.takeDamage(); if (!_boss) { endTheGame(); return; } remove(bullet); bullet.destroy(); } } } _bulletList = new Vector.<Bullet>(); getType("BossBullet", _bulletList); for each (var bossBullet:BossBullet in _bulletList) { if (bossBullet.collideWith(_playerShip, bossBullet.x, bossBullet.y)) { endTheGame(); return; } } if ((_score.score % 5) == 0 && _score.score > (_speedUpgradeNumber * 5)) { _playerShip.speedMultiplier += 0.1; _speedUpgradeNumber++; } if (_score.score >= 25) { _playerShip.hasDoubleShoot = true; } if (_score.score > _enemyUpgradeNumber) { Enemy.damageMultiplier -= 0.015; Enemy.healthMultiplier += 0.02; _enemySpawnInterval -= 3; _enemyUpgradeNumber++; } if (_score.score == 50 && !_bossAppeared) { add(_boss); _bossAppeared = true; } } public function endTheGame():void { removeAll(); while (_bulletList.length > 0) { _bulletList[0].destroy(); _bulletList.shift(); } while (_enemyList.length > 0) { _enemyList[0].destroy(); _enemyList.shift(); } _bulletList = null; _enemyList = null; _playerShip = null; _score = null; if (_boss) { FP.world = new GameOverWorld(true); } else { FP.world = new GameOverWorld(false); } _boss = null; _gameEnded = true; }This code will only end the game when an enemy reaches the end (is destroyed) with a health above 0. If the enemy dies, this function is also called, but then the enemy health will be below (or equal to) 0, skipping the code to end the game. We have also created the
_gameEndedboolean because when an enemy reaches the end of the screen and gets removed, the world is still updating its entities. Only when it finished updating them (after thesuper.update()call in the class) is that we can end the game.After all these lines and lines of code changed, remove the FlashPunk console, hit compile and test the game. It’s finally done, your very first game entirely done in FlashPunk! Congratulations!
Step 16: Conclusion
Congratulations, you have created your first FlashPunk game! You have used pretty much all of FlashPunk’s features for a very basic game, which means you are ready to create more FlashPunk games and spread the word! What do you think of improving this game? It could have different enemies, enemies that also shoot bullets, levels, more bosses, more upgrades and a lot of other things! Are you up to the challenge?
In the previous session I went over how to create a basic TimelineLite. Today I will show you the methods and properties that you will use to control the playback of your TimelineLite. By combining these methods and properties you can extend the built in functionality of TimelineLite to create fast-forward and play/pause toggle controls. I’ll also show you how easy it is to set up a Slider component to use as a TimelineLite scrubber.
TimelineLite in Action
Let’s take a look at the final result we will be working towards:
You can find all the files used to create the SWF above in the source files for this tutorial.
Watch the Screencast
Don’t like ads? Download the screencast, or subscribe to Activetuts+ screencasts via iTunes!
TimelineLite Playback Methods
The following methods give you precise control over the playback of your TimelineLite
reverse( false ).Custom Playback Controls
By combining the built-in methods and properties its easy to create advanced functionality:
Fast-Forward – You can create a fast-forward control by increasing the
timeScaleproperty and forcing playback in a forward direction.private function fastForwardHandler(e:MouseEvent):void { tl.timeScale = 4; tl.play(); }Play/Pause Toggle – To toggle between the playing and paused states just negate the
pausedproperty. It is important to make suretimeScaleandreversedproperties are set to the normal values as they could be altered by the fast-forward and reverse buttons respectively.private function playPauseHandler(e:MouseEvent):void { resetTimeScale(); tl.reversed = false; tl.paused = !tl.paused; }Slider Component Scrubber – The Slider component makes it fairly easy to scrub through the timeline by altering the
currentProgressof the timeline. The Slider is set to output values between 0 and 100. In the code this value gets converted to a number between 0 and 1.import fl.controls.Slider; import fl.events.SliderEvent; slider.addEventListener(SliderEvent.THUMB_DRAG, sliderChange); private function sliderChange(e:SliderEvent):void { tl.currentProgress = slider.value * .01; //forces the timeline to stop when the scrubber is released. tl.pause(); }Homework
What? You are giving me homework? Yes! I want you to be your best. Flex your mind muscle with this little challenge.
In order to really learn this material nothing is better than diving in and getting your hands a little dirty.
Conclusion
So far I’ve given you a fair amount of information on creating and controlling TimelineLite animations. Although there are some obvious similarities in the way you control TimelineLite and Flash IDE timeline animations, I really love how TimelineLite gives animators so much more control with the
reverse(),restart(), andresume()methods. ThetimeScaleandcurrentProgressproperties open up some really interesting possibilities that will be discussed even more in the future.In the next video I will be showing you how to add labels to TimelineLite instances so that you can easily navigate to certain sections of your timelines. TimelineLite labels work very similarly to frame labels in the Flash IDE but with some added functionality. It’s going to be a lot of fun.
If you have any questions or comments on this tutorial – or your homework
– feel free to post a comment below.
Thanks for watching!
Twice a month, we revisit some of our readers’ favorite posts from Activetuts+ history. This week’s retro-Active tutorial, first published in April, is a guide to Euclidean vectors: what they are, why you’d use them, and how to implement them in Flash with AS3.
Euclidean vectors are objects in geometry with certain properties that are very useful for developing games. They can be seen as points, but they also have a magnitude and a direction. They are represented as arrows going from the initial point to the final point, and that’s how we will draw them in this article.
Euclidean vectors are commonly used in mathematics and physics for a lot of things: they can represent velocity, acceleration and forces in physics, or help prove a lot of important theorems in mathematics. In this tutorial, you’ll learn about Euclidean vectors, and build a class that you can use in your own Flash projects.
Please note that Euclidean vectors are different than ActionScript’s Vector class, and also different than vector drawing.
Vectors can be used in the Flash environment to help you achieve complex tasks that would otherwise require a lot of effort if done without them. In this article you will learn how to use them in Flash, as well as learn a lot of cool tricks with vectors.
Step 1: Cartesian Coordinates and Flash’s Coordinates
Before jumping into vectors, let’s introduce Flash’s coordinate system. You are probably familiar with the Cartesian coordinate system (even if you don’t know it by name):
Flash’s system is very similar. The only difference is that the y-axis is upside-down:
When we start working with vectors in flash, we need to remember that. However, good news: this different system doesn’t make much difference. Working with vectors in it will be basically like working with vectors in the Cartesian system.
Step 2: Defining a Vector
For the purpose of this tutorial, we will define and work with all vectors’ initial points as being the registration point of the stage, just as they are commonly used in mathematics. A vector will then be defined just like a common point, but it will have magnitude and angle properties. Take a look at some example vectors defined in the stage:
As you can see, a vector is represented by an arrow, and each vector has a certain length (or magnitude) and points along a certain angle. The tail of each vector is at the registration point
(0, 0).We will create a simple EuclideanVector class for this tutorial, using the Point class to hold the vector’s coordinates. Let’s create the basic vector class now:
package { import flash.geom.Point; public class EuclideanVector { public var position:Point; public var magnitude:Number; public var angle:Number; public function EuclideanVector(endPoint:Point) { position = endPoint; } } }During this tutorial, we will talk about the sense and the direction of a vector. Note that the direction just defines a line that “contains” the vector. The sense is what defines which way the vector points along this line.
Step 3: Inverse of a Vector
In this tutorial we will use the expression “inverse of a vector”. The inverse of a vector is another vector with the same magnitude and direction, but a contrary sense. That translates to a vector with the opposite signal of the first vector’s coordinates. So a vector with an endpoint of (x, y) would have an inverse vector with an endpoint of (-x, -y).
Let’s add a function to our EuclideanVector class to return the inverse vector:
public function inverse():EuclideanVector { return new EuclideanVector(new Point(-position.x, -position.y)); }Step 4: Basic Operations Addition
Now that we have learned how to define a vector, let’s learn how to add two vectors: it’s as simple as adding their coordinates separately. Look at this image:
If you notice in the image, the result of the addition of two vectors is another vector, and you can see that its coordinates are the sum of the coordinates of the other two vectors. In code, it would look like this:
public function sum(otherVector:EuclideanVector):EuclideanVector { position.x += otherVector.position.x; position.y += otherVector.position.y; return this; }So we can say that:
Step 5: Basic Operations Subtraction
Subtraction works almost the same as addition, but instead we will be adding the inverse of the second vector to the first vector.
It is already known how to sum two vectors, so here’s the code for subtraction:
public function subtract(otherVector:EuclideanVector):EuclideanVector { position.x -= otherVector.position.x; position.y -= otherVector.position.y; return this; }This code is extremely useful to get a vector that goes from the point of a vector to the point of another. Look again at the image and you will see this is true. It will be used a lot in the later examples.
Step 6: Basic Operations Multiplication by a Number
The multiplication between a vector and a number (regular numbers are known as “scalars” in vector math) results in a vector which has had magnitude multiplied by this number, but still pointing in the same direction; it’s “stretched” if the scalar is larger than 1, and squashed if the scalar is between 0 and 1. The sense of the new vector will be the same as the original vector if the scalar is positive, or the opposite if negative. Basically, this number “scales” the vector. Look at the picture:
In the code, we only multiply a vector’s coordinates by the number, which will then scale the vector:
public function multiply(number:Number):EuclideanVector { position.x *= number; position.y *= number; return this; }Step 7: Getting a Vector’s Magnitude
In order to get a vector’s magnitude, we will use the Pythagorean theorem. If you forgot what is it, here is a quick refresher:
(More info here.)
The code is very simple:
public function magnitude():Number { return Math.sqrt((position.x * position.x) + (position.y * position.y)); }You should also remove the line
public var magnitude:Number, as this is what we’ll use from now on.The magnitude of a vector will always be positive, since it is the square root of the sum of two positive numbers.
Step 8: Getting the Angle of a Vector
The angle of a vector is the angle between the x-axis and the vector’s direction line. The angle is measured going from the x-axis and rotating anti-clockwise until the direction line in the cartesian system:
However, in Flash’s coordinate system, since the y-axis is upside down, this angle will be measured rotating clockwise:
This can be easily calculated using the following code. The angle will be returned in radians, in a range from 0 to 2pi. If you don’t know what radians are or how to use them, this tutorial by Michael James Williams will help you a lot.
public function angle():Number { var angle:Number = Math.atan2(position.y, position.x); if (angle < 0) { angle += Math.PI * 2; } return angle; }Step 9: Dot Product
The dot product between two vectors is a number with apparently no meaning, but it has two useful uses. Let’s first take a look at how the dot product can be calculated:
But it also can be obtained by each vector’s coordinates:
The dot product can tell us a lot about the angle between the vectors: if it’s positive, then the angle ranges from 0 to 90 degrees. If it’s negative, the angle ranges from 90 to 180 degrees. If it’s zero, the angle is 90 degrees. That happens because in the first formula only the cosine is responsible for giving the dot product a “signal”: the magnitudes are always positive. But we know that a positive cosine means that the angle ranges from 0 to 90 degrees, and so on for negative cosines and zero.
The dot product can also be used to represent the length of a vector in the direction of the other vector. Think of it as a projection. This proves extremelly useful in things like the Separation of Axis Theorem (SAT) and its implementation in AS3 for collision detection and response in games.
Here is the practical code to get the dot product between two vectors:
public function dot(otherVector:EuclideanVector):Number { return (position.x * otherVector.position.x) + (position.y * otherVector.position.y); }Step 10: Smallest Angle Between Vectors
The angle between vectors, as seen in Step 9, can be given by the dot product. Here is how to calculate it:
public function angleBetween(otherVector:EuclideanVector):Number { return Math.acos(dot(otherVector) / (magnitude() * otherVector.magnitude())); }Step 11: Ranged Angle Between Vectors
There is also another way to calculate the angle, which gives results between -pi and pi and always calculates the angle that goes from the first vector to the second vector; this is useful when you want to easily integrate with a display object’s rotation (which ranges from -180 to 180).
The method works by getting the angle for both vectors, then subtracting the angles and working on the result.
The code:
public function rangedAngleBetween(otherVector:EuclideanVector):Number { var firstAngle:Number; var secondAngle:Number; var angle:Number; firstAngle = Math.atan2(otherVector.position.y, otherVector.position.x); secondAngle = Math.atan2(position.y, position.x); angle = secondAngle - firstAngle; while (angle > Math.PI) angle -= Math.PI * 2; while (angle < -Math.PI) angle += Math.PI * 2; return angle; }Note that this angle returns positive if secondAngle is higher than firstAngle, so the order in which you get the ranged angle will affect the result!
Step 12: Normalizing a Vector
Normalizing a vector means making its magnitude be equal to 1, while still preserving the direction and sense of the vector. In order to do that, we multiply the vector by
1/magnitude. That way, its magnitude will be reduced, or increased, to 1.public function normalize():EuclideanVector { var m:Number = magnitude(); position.x /= m; position.y /= m; return this; }Step 13: Getting the Normal of a Vector
The normal of a vector is another vector that makes a 90 degree angle to the first. It can be calculated by the following formulas:
The formulas rely on the fact that, since the normal is always perpendicular to a vector, we only need to change the order of the x and y coordinates and invert one of them in order to get a normal. The following image shows the process:
In the image, Vec is the original vector, Vec2 is the vector with Vec‘s swapped coordinates, and Vec3 is a vector with Vec2‘s negative y coordinate. Ang and Ang2 are variable, but the angle between Vec and Vec3 is always 90 degrees.
And the code is simple
public function normalRight():EuclideanVector { return new EuclideanVector(new Point(-position.y, position.x)); } public function normalLeft():EuclideanVector { return new EuclideanVector(new Point(position.y, -position.x)); }Step 14: Rotating a Vector
In order to rotate a vector, we assume the (0, 0) position (its initial point) will be the rotation center. The rotated point is given by the formula:
This formula is obtained by applying a rotation matrix to that vector. We would be going beyond the scope of this tutorial if we went into the matrix and how it works, so I will just leave the formula here.
The code is pretty much the same:
public function rotate(angleInRadians:Number):EuclideanVector { var newPosX:Number = (position.x * Math.cos(angleInRadians)) - (position.y * Math.sin(angleInRadians)); var newPosY:Number = (position.x * Math.sin(angleInRadians)) + (position.y * Math.cos(angleInRadians)); position.x = newPosX; position.y = newPosY; return this; }This is the end of our basic vector operations. What you will see next is ways to use this class to do interesting things. Here is our class so far:
package { import flash.geom.Point; public class EuclideanVector { public var position:Point; public var angle:Number; public function EuclideanVector(endPoint:Point) { position = endPoint; } public function inverse():EuclideanVector { return new EuclideanVector(new Point(-position.x, -position.y)); } public function sum(otherVector:EuclideanVector):EuclideanVector { position.x += otherVector.position.x; position.y += otherVector.position.y; return this; } public function subtract(otherVector:EuclideanVector):EuclideanVector { position.x -= otherVector.position.x; position.y -= otherVector.position.y; return this; } public function multiply(number:Number):EuclideanVector { position.x *= number; position.y *= number; return this; } public function magnitude():Number { return Math.sqrt((position.x * position.x) + (position.y * position.y)); } public function angle():Number { var angle:Number = Math.atan2(position.y, position.x); if (angle < 0) { angle += Math.PI * 2; } return angle; } public function dot(otherVector:EuclideanVector):Number { return (position.x * otherVector.position.x) + (position.y * otherVector.position.y); } public function angleBetween(otherVector:EuclideanVector):Number { return Math.acos(dot(otherVector) / (magnitude() * otherVector.magnitude())); } public function rangedAngleBetween(otherVector:EuclideanVector):Number { var firstAngle:Number; var secondAngle:Number; var angle:Number; firstAngle = Math.atan2(otherVector.position.y, otherVector.position.x); secondAngle = Math.atan2(position.y, position.x); angle = secondAngle - firstAngle; while (angle > Math.PI) angle -= Math.PI * 2; while (angle < -Math.PI) angle += Math.PI * 2; return angle; } public function normalize():EuclideanVector { position.x /= magnitude(); position.y /= magnitude(); return this; } public function normalRight():EuclideanVector { return new EuclideanVector(new Point(-position.y, position.x)); } public function normalLeft():EuclideanVector { return new EuclideanVector(new Point(position.y, -position.x)); } public function rotate(angleInRadians:Number):EuclideanVector { var newPosX:Number = (position.x * Math.cos(angleInRadians)) - (position.y * Math.sin(angleInRadians)); var newPosY:Number = (position.x * Math.sin(angleInRadians)) + (position.y * Math.cos(angleInRadians)); position.x = newPosX; position.y = newPosY; return this; } } }OK, we’ve covered building the vector class, now let’s take a took at utilising it.
Step 15: Determining Whether a Point is Inside a Polygon
The action begins here. Determining whether a point lies inside a polygon or not is a very interesting topic, and there are many methods of achieving it. In this article I will present the three methods that are generally used:
All these algorithms will rely on the fact that you know the coordinates of the vertices (corners) that define the polygon.
Step 16: The Crossing Number or Even-Odd Rule Algorithm
This algorithm can be used for any shape. This is what you read: any shape, have it holes or not, be it convex or not. It is based on the fact that any ray cast from the point you want to check out to infinity will cross an even number of edges if the point is outside the shape, or odd number of edges if the point is inside the shape. This can be proven by the Jordan curve theorem, which implies that you will have to cross a border between some region and other region if you want to move from one to other. In our case, our regions are “inside the shape” and “outside the shape”.
The code for this algorithm is the following:
public function isPointInsideShape1(point:EuclideanVector, shapeVertices:Vector.<EuclideanVector>):Boolean { var numberOfSides:int = shapeVertices.length; var i:int = 0; var j:int = numberOfSides - 1; var oddNodes:Boolean = false; while (i < numberOfSides) { if ((shapeVertices[i].position.y < point.position.y && shapeVertices[j].position.y >= point.position.y) || (shapeVertices[j].position.y < point.position.y && shapeVertices[i].position.y >= point.position.y)) { if (shapeVertices[i].position.x + (((point.position.y - shapeVertices[i].position.y) / (shapeVertices[j].position.y - shapeVertices[i].position.y)) * (shapeVertices[j].position.x - shapeVertices[i].position.x)) < point.position.x) { oddNodes = !oddNodes; } } j = i; i++; } return oddNodes; }It will return
falseif the point is not inside the shape, ortrueif the point is inside the shape.Step 17: The Winding Number Algorithm
The winding number algorithm use the sum of all the angles made between the point to check and each pair of points that define the polygon. If the sum is close to 2pi, then the point being checked is inside the vector. If it is close to 0 then the point is outside.
public function isPointInsideShape2(point:EuclideanVector, shapeVertices:Vector.<EuclideanVector>):Boolean { var numberOfSides:int = shapeVertices.length; var i:int = 0; var angle:Number = 0; var rawAngle:Number = 0; var firstVector:EuclideanVector; var secondVector:EuclideanVector; while(i < numberOfSides) { firstVector = new EuclideanVector(new Point(shapeVertices[i].position.x - point.position.x, shapeVertices[i].position.y - point.position.y)); secondVector = new EuclideanVector(new Point(shapeVertices[(i + 1) % numberOfSides].position.x - point.position.x, shapeVertices[(i + 1) % numberOfSides].position.y - point.position.y)); angle += secondVector.rangedAngleBetween(firstVector); i++; } if(Math.abs(angle) < Math.PI) return false; else return true; }The code uses the ranged angle between vectors and gives space for imprecisions: notice how we are checking the results of the sum of all angles. We do not check if the angle is exactly zero or 2pi. Instead, we check if it is less than pi and higher than pi, a considerable median value.
Step 18: The Concave Polygon Algorithm
The concave polygon algorithm relies on the fact that, for a concave polygon, a point inside it is always to the left of the edges (if we are looping through them in a counter-clockwise sense) or to the right of the edges (if we are looping through them in a clockwise sense).
Imagine standing in a room shaped like the image above, and walking around the edges of it with your left hand trailing along the wall. At the point along the wall where you are closest to the point you are interested in, if it’s on your right then it must be inside the room; if it’s on your left then it must be outside.
The problem lies in determining whether a point is to the left or right of an edge (which is basically a vector). This is done through the following formula:
That formula returns a number less than 0 for points to the right of the edge, and greater than 0 for points to the left of it. If the number is equal to 0, the point lies on the edge, and is considered inside the shape. The code is the following:
public function isPointInsideShape3(point:EuclideanVector, shapeVertices:Vector.<EuclideanVector>):Boolean { var numberOfSides:int = shapeVertices.length; var i:int = 0; var firstEdgePoint:EuclideanVector; var secondEdgePoint:EuclideanVector; var leftOrRightSide:Boolean; while(i < numberOfSides) { firstEdgePoint = shapeVertices[i]; secondEdgePoint = shapeVertices[(i + 1) % numberOfSides]; if(i == 0) { // Determining if the point is to the left or to the right of first edge // true for left, false for right leftOrRightSide = ((point.position.y - firstEdgePoint.position.y) * (secondEdgePoint.position.x - firstEdgePoint.position.x)) - ((point.position.x - firstEdgePoint.position.x) * (secondEdgePoint.position.y - firstEdgePoint.position.y)) > 0; } else { // Now all edges must be on the same side if(leftOrRightSide && ((point.position.y - firstEdgePoint.position.y) * (secondEdgePoint.position.x - firstEdgePoint.position.x)) - ((point.position.x - firstEdgePoint.position.x) * (secondEdgePoint.position.y - firstEdgePoint.position.y)) < 0) { // Not all edges are on the same side! return false; } else if(!leftOrRightSide && ((point.position.y - firstEdgePoint.position.y) * (secondEdgePoint.position.x - firstEdgePoint.position.x)) - ((point.position.x - firstEdgePoint.position.x) * (secondEdgePoint.position.y - firstEdgePoint.position.y)) > 0) { // Not all edges are on the same side! return false; } } i++; } // We looped through all vertices and didn't detect different sides return true; }This code works regardless of whether you have the shape’s vertices defined clockwise or counter-clockwise.
Step 19: Ray Casting
Ray casting is a technique often used for collision detection and rendering. It consists of a ray that is cast from one point to another (or out to infinity). This ray is made of points or vectors, and generally stops when it hits an object or the edge of the screen. Similarly to the point-in-shape algorithms, there are many ways to cast rays, and we will see two of them in this post:
In the next two steps we will look into both methods. After that, we will see how to make our ray stop when it hits an object. This is very useful when you need to detect collision against fast moving objects.
Step 20: The Bresenham’s Line Algorithm
This algorithm is used very often in computer graphics, and depends on the convention that the line will always be created pointing to the right and downwards. (If a line has to be created to the up and left directions, everything is inverted later.) Let’s go into the code:
public function createLineBresenham(startVector:EuclideanVector, endVector:EuclideanVector):Vector.<EuclideanVector> { var points:Vector.<EuclideanVector> = new Vector.<EuclideanVector>(); var steep:Boolean = Math.abs(endVector.position.y - startVector.position.y) > Math.abs(endVector.position.x - startVector.position.x); var swapped:Boolean = false; if (steep) { startVector = new EuclideanVector(new Point(startVector.position.y, startVector.position.x)); endVector = new EuclideanVector(new Point(endVector.position.y, endVector.position.x)); } // Making the line go downward if (startVector.position.x > endVector.position.x) { var temporary:Number = startVector.position.x; startVector.position.x = endVector.position.x; endVector.position.x = temporary; temporary = startVector.position.y; startVector.position.y = endVector.position.y endVector.position.y = temporary; swapped = true; } var deltaX:Number = endVector.position.x - startVector.position.x; var deltaY:Number = Math.abs(endVector.position.y - startVector.position.y); var error:Number = deltaX / 2; var currentY:Number = startVector.position.y; var step:int; if (startVector.position.y < endVector.position.y) { step = 1; } else { step = -1; } var iterator:int = startVector.position.x; while (iterator < endVector.position.x) { if (steep) { points.push(new EuclideanVector(new Point(currentY, iterator))); } else { points.push(new EuclideanVector(new Point(iterator, currentY))); } error -= deltaY; if (error < 0) { currentY += step; error += deltaX; } iterator++; } if (swapped) { points.reverse(); } return points; }The code will produce an AS3 Vector of Euclidean vectors that will make the line. With this Vector, we can later check for collisions.
Step 21: The DDA Method
An implementation of the Digital Differential Analyzer is used to interpolate variables between two points. Unlike the Bresenham’s line algorithm, this method will only create vectors in integer positions for simplicity. Here’s the code:
public function createLineDDA(startVector:EuclideanVector, endVector:EuclideanVector):Vector.<EuclideanVector> { var points:Vector.<EuclideanVector> = new Vector.<EuclideanVector>(); var dx:Number; var dy:Number; var _x:Number = startPoint.position.x; var _y:Number = startPoint.position.y; var m:Number; var i:int; dx = endPoint.position.x - startPoint.position.x; dy = endPoint.position.y - startPoint.position.y; if (Math.abs(dx) >= Math.abs(dy)) m = Math.abs(dx); else m = Math.abs(dy); points.push(new EuclideanVector(new Point(int(_x), int(_y)))); i = 1; while (i <= m) { _x += dx / m; _y += dy / m; points.push(new EuclideanVector(new Point(int(_x), int(_y)))); i++; } return points; }This code will also return an AS3 Vector of Euclidean vectors.
Step 22: Checking for Collisions Using Rays
Checking collision via rays is very simple. Since a ray consists of many vectors, we will check for collisions between each vector and a shape, until one is detected or the end of the ray is reached. In the following code,
shapeToCheckwill be a shape just like the ones we have been using in Steps 13-16. Here’s the code:public function checkRayCollision(ray:Vector.<EuclideanVector>, shape:Vector.<EuclideanVector>):Boolean { var rayLength:int = ray.length; var i:int = 0; while(i < rayLength) { if(isPointInsideShape1(ray[i], shape)) { return true; } i++; } return false; }You can use any point-inside-shape function you feel comfortable with, but pay attention to the limitations of the last one!
Conclusion
You’re ready to start using this knowledge everywhere now! It will be useful many times, and will save you a lot of extra calculations when trying to do more complex things in Flash.