PDA

View Full Version : Game Engine Tutorial Part 1:



Charleh
November 22nd, 2007, 10:37 AM
Hiya all, I've started writing up a tut on creating a basic game engine for all those struggling - so here it is...!

Creating a basic game engine using classes in AS2.0/3.0:

This tutorial is for AS2.0 but can be adapted to 3.0 with a little shuffling and a couple of changes!

I've been using the Kirupa forums for a while now and one of the things I see the most is people following tutorials for simple games. While these tutorials are great for learning the basics of AS and enable a simple game to be created quickly and easily they are usually inflexible, convoluted and contain bad coding habits. One of my pet hates is the 'string to variable name' thing that Flash allows you to do so readily.

This tutorial covers getting a more flexible game engine working in Flash which will allow you to do more! It takes a little longer to get the 'basics' working but it's worth it!
First of all I'll just do a little crash course on classes. I see a lot of people using the procedural approach to programming getting their code up and running fine, but then stumbling when it comes to changing something fundamental or adding more functionality because their code is disorganised.

We will aim to organise things as best as possible!

First of all, what is a class?

1. Classes, a crash course

A class is basically a collection of methods (functions) and properties (variables) which form an 'object'.

A class can be instantiated, which means a 'copy' of that object will be created in memory for you to manipulate. You can have as many instances of the same class as you want, and the properties of each instance will be unique to that instance.

For example, imagine you have 2 apples - they are both apples, but one may be slightly redder, one may be slightly bigger or sweeter. This is akin to having 2 instances of the same class but with different properties.

44736

Properties can be either public or private...

Public properties can be accessed from anywhere
Private properties can only be accessed from inside the class

I wont go into detail on private/publics as there's no good reason I would use private variables in a game project (as it's all my code and I know what I should/shouldn't be accessing/altering)

A class is defined in AS as follows:



class myClass {
// Constructor
function myClass() {
}
}


Simple. A class file must be saved with the same name as the class and placed into the same directory as the .FLA file to be included in the project. This class filename would be "myClass.as".

Notice the definition of one method myClass(). This method is called the 'constructor' and it must be named the same as the class. This function will be called every time a new instance of myClass is created. This is useful for initialising your objects upon creation.

Now to add to myClass - let's add some properties and another method.



class myClass {

var number1:Number;
var number2:Number;
// Constructor
function myClass() {
number1 = 5;
number2 = 10;
}
function ShowNumber() {
trace(number1 + number2);
}
}


I've added 2 properties called number1 and number2. I've also added a function called ShowNumber().

Now when we create a new myClass object the 2 variables number1 and number2 will be set to 5 and 10 respectively.

If we call the objects ShowNumber() method, it will output the sum of these numbers to the Flash console window.

To create the object and call the method we use the following code



var instanceOfMyClass:myClass = new myClass();
instanceOfMyClass.ShowNumber();


Now this is a great way to organise your code, but we aren't finished yet!

Classes can do much more!

Your myClass now can be added to, but what happens when you want an object which does most of the stuff that myClass does, but maybe a little differently here and there, or completely differently in places?

You could add arguments to the functions and base the function code on a switch statement or if statements like so: (taking the ShowNumber function as an example)



function ShowNumber(method:Number) {
if(method == 0) {
trace(number1 + number2);
} else {
trace(number1 / number2);
}
}


That will work yes...but you could just create a new class

'But why copy all that code?' you ask...

Well, classes have a feature called 'inheritance' which allows you to base a new class upon an existing class, and then override or add to it's methods!

So you could do the following



class myOtherClass extends myClass {
// Constructor
function myOtherClass() {
super();
}
}


Now if you instatiate this class - what do you notice? Yes you can call the ShowNumber method on this new class. All the properties and methods of the parent class are available. Notice the call to super() - this basically means I'm calling the constructor on the parent class - to access anything on the parent class from a subclass you just use the 'super' keyword. This means I don't have to write the same initialisation code as the parents constructor can do it for me.

Now it's possible to modify the ShowNumber() function to suit the new class



class myOtherClass extends myClass {
// Constructor
function myOtherClass() {
super();
}
function ShowNumber() {
trace(number1 / number2);
}
}


Now your myOtherClass objects will have a different ShowNumber function from your myClass objects. You can even run the ShowNumber function from the parent but modify the properties before you call it like so:



class myOtherClass extends myClass {
// Constructor
function myOtherClass() {
super();
}
function ShowNumber() {
number1 = 20;
super.ShowNumber();
}
}


So as you can see, there's a lot of scope for intricate layering of objects. The basic idea in this tutorial is to give all your objects the most 'basic' functions and then build upon these by extending them with more specific levels of functionality.

A quick example in one of my games is as follows

BaseObject -> BaseSoldier -> EnemySoldier -> SoldierAK

The base object handles rendering, physics etc. The BaseSoldier handles the movement, control and some behavioural aspects. The EnemySoldier handles behaviour of an Enemy object (i.e. fires at good guys, can be hit by good guys etc) and finally the SoldierAK describes how a Soldier with an AK-47 rifle fires his gun and what he fires.
This means that I can have a number of different types of soldier running about with minimal code needed to add a new one. I can simply create a new SoldierXXX (XXX being the weapon) class and define how that soldier fires this new weapon. His movement, physics and rendering are handled by the parent classes.

2. Creating the game objects

Now we know how to create a class and what they can be useful for, we can start to create our game engine. First of all we need to think about what needs to happen every Flash 'frame' to keep our game in motion.

We need some way of controlling multiple objects that may be on screen simultaneously, keeping track of their animations and updating their positions/collisions/behaviour etc!
Seems like a lot to look after in one go...this is why creating a 'Base' class can be a good idea. Basically we will be creating a generic 'Base' class that the game engine will take care of for us. This class will be responsible for the basics in showing an object on the screen - pretty much everything that will interact with the game world will be derived from this Base class.

We will also need an Array to hold the list of objects so that we can keep track of them.
First of all let's define our base object

Create a new .as file called BaseObject.as in your project directory (you did make one right?!)

Add the following code



class BaseObject {
// Ok so here are some useful vars
var parent:BaseObject = null; // Holds information on the 'parent' object - this allows you to create heirarchys of objects and means you can have objects which are 'attached' to others. Useful for multipart objects like tanks with turrets etc
var oList:ObjectList = null; // This is a reference to an instance of a class we are going to create next - the ObjectList. This will be the class responsible for handling our list of objects
var clip:MovieClip = null; // The movieclip to use for display purposes - you could use bitmap rendering, but for the sake of simplicity I'll use this for the tut
var _x:Number = 0; // x/y position of the object
var _y:Number = 0;
var velx:Number = 0; // Velocity of object
var vely:Number = 0;

// Initialise the object
function BaseObject(_oList:ObjectList, clipName:String, _parent:BaseObject) {
oList = _oList;
parent = _parent;
SetMovie(clipName);
oList.push(this); // Add this to the object list
}

// Attaches the movieclip which will represent this object - attaches to _root if parent is not specified
function SetMovie(clipName:String) {
if(parent == null) {
clip = oList.root.attachMovie(clipName, clipName + oList.root.getNextHighestDepth(), oList.root.getNextHighestDepth());
} else {
clip = parent.clip.attachMovie(clipName, clipName + parent.clip.getNextHighestDepth(), parent.clip.getNextHighestDepth());
}
clip.stop(); // Stop the animations from playing
Render(); // Call render to set the MC to the correct place/size/etc on screen - should cut any flickering
}

// Behaviour function, does nothing on base
function Behaviour() {
// No behaviour for the base
}

// Physics, set the x/y position += the velocity
function Physics() {
_x += velx;
_y += vely;
}

// Put the MC in the correct place on screen
function Render() {
clip._x = _x;
clip._y = _y;
}

// Function to remove this object from the game
function Remove() {
oList.RemoveObject(this);
}

// Clean up, remove movie clips etc
function CleanUp() {
clip.removeMovieClip();
}
}


So now we have our basics for our game object - let's create the ObjectList class and see how these two fit together



class ObjectList extends Array {
// The object list is just an 'array' with some extra functionality
var rList:Array; // This will hold a list of objects to remove from the game, the reason we use this list will become apparent (or maybe not, but I'll tell you anyway!)
var root:MovieClip; // Just holds a reference to the _root so that you dont need to use _root in your game in case you want to put a preloader on it

// Init etc..!
function ObjectList(_rootRef:MovieClip) {
root = _rootRef;
rList = new Array();
}

// Simply pushes the object onto the removal list
function RemoveObject(obj:BaseObject) {
rList.push(obj);
}

// Gets the array index for the specified object
function GetIndex(o:BaseObject):Number {
var i:Number;
for (i = 0; i < this.length; i++) {
if(this[i] == o) {
return i;
}
}
}

// This runs through the list of objects which are set to be removed and gets rid of them by running their cleanup() function to remove the movieclips from the game and then splicing them out of the array
function CleanUp() {
var SpliceIndex:Number;

for(var i = 0; i < rList.length; i++) {
SpliceIndex = GetIndex(rList[i]);
this[SpliceIndex].CleanUp();
this.splice(SpliceIndex, 1);
}
// EDIT: Added this to clear out the rList array or it just grew and grew and things got slow :(
rList = new Array();
}

// Loop through all game objects and run their update functions
function MainLoop() {
for(var i = 0; i < this.length; i++) {
this[i].Behaviour();
this[i].Physics();
this[i].Render();
}
CleanUp(); // Remove any items that are marked for deletion
}
}


So now you have these two classes, you should be able to start up Flash and get the basics of your engine on screen...

So...open Flash and go make a cuppa while it starts :)

Now create a new Flash project and set the framerate to 30 (or 31 if you are old skool :P)

Create a new movieclip symbol - anything will do, draw a circle maybe or some lines...save it and add it to the library with a linkage identifier of 'TestObject'

On your main timeline let's add the following code:

EDIT: Fixed missing reference to root in ObjectList below



var oList:ObjectList = new ObjectList(this);
var test:BaseObject = new BaseObject(oList, "TestObject", null);
test._x = 200;
test._y = 200;

onEnterFrame = function() {
oList.MainLoop();
}


Now you should see your symbol appear on the screen in the specified place (200, 200).
Your game engine is now up and running!

3. Animation

Now we have our base object - let's add a bit more to baseobject to allow animation. You could just use the movieclips in flash and call gotoAndPlay() on clips, but we really need to link the animations to an object and we also want a bit more control. At the moment the movieclip that BaseObject is using for display purposes will just run in a constant animation loop. We can add our own custom animation system using classes so that we have a lot more control over which animations get played, and the speed at which they play. Our animation will be based on keyframes - you will be creating all your frames of animation for an object in one movieclip - no nested animations should be in there (unless you want a subclip that just loops over and over which can be useful). I use this animation system because it's a little more flexible and extensible than the regular flash animation system. With this system you can extend it to add your own event system which is very useful.

A good thing about this is that you can use any movieclip symbol to represent any object in the game.

So let's create our animation class:



class Animation {
// Vars
var animName:String;
var frames:Array;
var delays:Array;
var animTag:String;

// Constructor
function Animation(newName:String) {
animName = newName;
}

// Add frames to this animation
function addFrames(frms:Array) {
frames = frms;
}

// Add delays to this animation
function addDelays(dels:Array) {
delays = dels;
}

// Tag this animation - will come back to this later! :)
function tag(tag:String) {
animTag = tag;
}

// returns the next frame for the animation
function getNextFrame(currentFrame:Number):Number {
// if the current frame is greater than the array length then we need to check the last frame

if(currentFrame + 1 == frames.length) {
return 0;
} else {
return currentFrame + 1;
}
}

// Some getters!
function getFrame(currentFrame:Number):Number {
return frames[currentFrame];
}

function getDelay(currentFrame:Number):Number {
return delays[currentFrame] - 1;
}
}


Ok so we have an animation class now which is basically just an object which stores a list of frames and a list of corresponding delays for those frames. Now that we have this class we can use it to decide which frames our game objects should show.

We now need to add some bits to the BaseObject class to make sure it can utilise the animation class..

Add the following code



// variables
var anims:Array; // An array of animation objects
var currentFrame:Number; // Holds the index of the current animation frame
var currentDelay:Number; // Holds the amount of time left before we get the next frame
// in the constructor add
anims = new Array(); // to initialise the anims array
// Add the new 'animate' function - this function just uses the animation class to find out which

// frame we should be displaying from the movieclip
function Animate() {
// Check if we need to get the next frame, if not just decrement the delay
if(currentAnim != null) {
if (currentDelay == 0) {
currentFrame = currentAnim.getNextFrame(currentFrame);
currentDelay = currentAnim.getDelay(currentFrame);
clip.gotoAndStop(currentAnim.getFrame(currentFrame ));
trace(currentAnim.getFrame(currentFrame));

// If the delay is negative then quit this animation
if(currentDelay < 0) {
StopAnimation();
}
} else {
currentDelay -= 1;
}
}
}

// Add this function - it just stops the animation thats currently playing
function StopAnimation() {
currentFrame = 0;
currentDelay = 0;
currentAnim = null;
}
// Add this setanimation function
function SetAnimation(searchName:String) {
// Find animation by name in anim list does nothing if it can't find one
var i:Number;

for(i = 0; i < anims.length; i++) {
if (anims[i].animName == searchName) {
currentAnim = anims[i];
}
}
currentDelay = 0;
currentFrame = 0;
}
// Add this randomanimation function
function RandomAnimation(searchTag:String) {
// Find animation by tag in anim list and choose a random one
var i:Number = 0;
var targetAnims:Array;
targetAnims = new Array();
for(i = 0; i < anims.length; i++) {
if (anims[i].animTag == searchTag) {
targetAnims.push(anims[i]);
}
}
currentAnim = targetAnims[random(targetAnims.length)];
currentDelay = 0;
currentFrame = 0;
}
// Add this code to the Physics() function
Animate();


Now we have several new functions and some bits and pieces which will allow our objects to be animated. We have added this code to the BaseObject class because most likely all game objects will require animation so it's a pretty fundamental process that needs to go in at the lowest level. The Animate() function chooses the next frame for the movieclip based on the animations you have added to the object.

So now we will add some extra frames to our library symbol - open it up and add a few more keyframes - in the keyframes move the object about a bit or change it - animate it in your own personal style if you like. I'm going to draw a pentagon shape and rotate it by 14 degrees in each frame (it will look like it's completely rotating 360 degreed when these 5 frames play in order)

Now that the symbol has some frames let's create a new class extending the base class and give it some animations so we can see the code in action.

To add an animation use the following code



var anim:Animation = new Animation("AnimName"); // Create a new animation object called "AnimName"
anim.tag("Tag1"); // Tag this animation - optional, I'll explain what this is for in a second!
anim.addFrames(new Array(1,2,3,4,5)); // Add the frames - this animation will use frames 1 - 5 in the movieclip
anim.addDelays(new Array(1,1,1,1,1)); // Add the frame delays - this will change frame every
someObject.anims.push(anim); // Add the animation to an object


So if we create a new class we can add some animations directly to this new class instead of modifying the base



class animatedClass extends BaseObject {
function animatedClass(_oList:ObjectList, clipName:String, _parent:BaseObject) {
super(_oList, clipName, _parent);

var anim:Animation = new Animation("Test1");
anim.addFrames(new Array(1,2,3,4,5));
anim.addDelays(new Array(1,1,1,1,1));
anims.push(anim);
var anim:Animation = new Animation("Test2");
anim.addFrames(new Array(5,4,3,2,1));
anim.addDelays(new Array(1,1,1,1,1));
anims.push(anim);
}
function Behaviour() {
super.Behaviour();
if(Key.isDown(Key.UP)) {
SetAnimation("Test1");
} else if(Key.isDown(Key.DOWN)) {
SetAnimation("Test2");
}
}
}


Remember to save this as animatedClass.as in your source directory and that's it! I've added some extra code in the Behaviour function for this class - this is just a quick hack to enable you to mess with the animations.

Change the instantiation code in the root timeline to



var test:animatedClass = new animatedClass(oList, "TestObject", null);


Run the file and press the up or down arrow to change the animation.

See how flexible classes can be now?

Now have a play about with the frame delays - there are 2 options

A delay of > 0 gives the current animation frame a delay of the specified number of 'Flash' frames (or physics frames if you seperate the physics/rendering which we will do later!)
A delay of <= 0 means the current animation will end and currentAnim will be set to NULL on the base

You are probably wondering what the tag() function does: the tag function is a way of 'grouping' animations under 'tags'. When you create an animation you can give it a tag using the tag function - tagging multiple animations with the same tag forms a group. Now you can play a random animation from that group of animations by using the RandomAnimation() function on the BaseObject. Imagine a set of death animations for a soldier - maybe falling over backwards, clutching his chest when he gets shot, etc - you might want to play a random one of these animations when a soldier dies - with the tags you can do that.

4. Adding a player class

Next we will add a class which can handle keyboard input. Create a new class called PlayerInput and give it the following code:



class PlayerInput extends BaseObject {
function PlayerInput(_oList:ObjectList, clipName:String, _parent:BaseObject) {
super(_oList, clipName, _parent);
}
function Behaviour() {
if(Key.isDown(Key.UP)) {
MoveUp();
} else if(Key.isDown(Key.DOWN)) {
MoveDown();
} else {
UpDownReleased();
}
if(Key.isDown(Key.LEFT)) {
MoveLeft();
} else if(Key.isDown(Key.RIGHT)) {
MoveRight();
} else {
LeftRightReleased();
}
}
function UpDownReleased() {
vely = 0;
}
function MoveUp() {
vely = -5;
}
function MoveDown() {
vely = 5;
}
function LeftRightReleased() {
velx = 0;
}
function MoveLeft() {
velx = -5;
}
function MoveRight() {
velx = 5;
}
}


This is your player class. Save it as PlayerInput.as in your source dir. (You could use interfaces for the movement but I don't really find it advantageous in the situation)

Change the instantiation code on the root to



var test:PlayerInput = new PlayerInput(oList, "TestObject", null);


So now you have a static object which moves about. How about we put some animations on this object while it moves! Let's create another class to extend this one



class AnimatedPlayer extends PlayerInput {
function AnimatedPlayer(_oList:ObjectList, clipName:String, _parent:BaseObject) {
super(_oList, clipName, _parent);
var anim:Animation = new Animation("Test1");
anim.addFrames(new Array(1,2,3,4,5));
anim.addDelays(new Array(1,1,1,1,1));
anims.push(anim);
var anim:Animation = new Animation("Test2");
anim.addFrames(new Array(5,4,3,2,1));
anim.addDelays(new Array(1,1,1,1,1));
anims.push(anim);
}

function LeftRightReleased() {
super.LeftRightReleased();
StopAnimation();
}
function MoveLeft() {
super.MoveLeft();
if(currentAnim.animName != "Test2") {
SetAnimation("Test2");
}
}
function MoveRight() {
super.MoveRight();
if(currentAnim.animName != "Test1") {
SetAnimation("Test1");
}
}
}


Save this as AnimatedPlayer.as and make the change in the root timeline to the instantiation code again...



var test:AnimatedPlayer = new AnimatedPlayer(oList, "TestObject", null);


So now you have an object that animates when you move left and right. See how the classes are built up in layers, this adds a lot of flexibility and means you can concentrate on writing behavioural code rather than copying/pasting code or rewriting code which is already in your project.

The code in BaseObject handles all the physics, animation and rendering, the PlayerInput class handles player input and the AnimatedPlayer class handles behaviour.

Now looking at the structur of the classes can you see any obvious flaws?

I'd say that the PlayerInput class is probably a bad place to put any behavioural code such as the actual movement code. We should be using PlayerInput only for handling key presses and mouse movement/presses - the classes that are built on top of this should really handle all the behaviour.

Let's rename our AnimatedPlayer class to just Player and move the code for movement from the PlayerInput class into the Player class.

Your Player class should now look like this



// FIX: Error here, this class definition was still named AnimatedPlayer when it should be just Player
class Player extends PlayerInput {
function AnimatedPlayer(_oList:ObjectList, clipName:String, _parent:BaseObject) {
super(_oList, clipName, _parent);
var anim:Animation = new Animation("Test1");
anim.addFrames(new Array(1,2,3,4,5));
anim.addDelays(new Array(1,1,1,1,1));
anims.push(anim);
var anim:Animation = new Animation("Test2");
anim.addFrames(new Array(5,4,3,2,1));
anim.addDelays(new Array(1,1,1,1,1));
anims.push(anim);
}

function UpDownReleased() {
vely = 0;
}
function MoveUp() {
vely = -5;
}
function MoveDown() {
vely = 5;
}
function LeftRightReleased() {
velx = 0;
StopAnimation();
}
function MoveLeft() {
velx = -5;
if(currentAnim.animName != "Test2") {
SetAnimation("Test2");
}
}
function MoveRight() {
velx = 5;
if(currentAnim.animName != "Test1") {
SetAnimation("Test1");
}
}
}


and PlayerInput should now be



class PlayerInput extends BaseObject {
function PlayerInput(_oList:ObjectList, clipName:String, _parent:BaseObject) {
super(_oList, clipName, _parent);
}
function Behaviour() {
if(Key.isDown(Key.UP)) {
MoveUp();
} else if(Key.isDown(Key.DOWN)) {
MoveDown();
} else {
UpDownReleased();
}
if(Key.isDown(Key.LEFT)) {
MoveLeft();
} else if(Key.isDown(Key.RIGHT)) {
MoveRight();
} else {
LeftRightReleased();
}
}
function UpDownReleased() {
}
function MoveUp() {
}
function MoveDown() {
}
function LeftRightReleased() {
}
function MoveLeft() {
}
function MoveRight() {
}
}


Now your code is a little more structured - you can add handling code in PlayerInput and more behavioural code in Player. Also remember if you decide to make any changes to the MoveXXX() functions in PlayerInput and you also want the Player class or any other classes to follow this behaviour you need to add a call to the parent classes function in your child class...

So for instance in Player:



function MoveUp() {
super.MoveUp(); // Add this to tell the class to also run the code that's in the parent classes MoveUp() function
vely = -5;
}


5. Shooting

So now that we have a moveable player class and some animation, let's start crafting it all into a little exercise and create a space invaders clone.

First of all we need to add the handling of the 'shooting' button into the PlayerInput class

Change the behaviour code and add a ButtonPressed function in PlayerInput:



function Behaviour() {
if(Key.isDown(Key.UP)) {
MoveUp();
} else if(Key.isDown(Key.DOWN)) {
MoveDown();
} else {
UpDownReleased();
}
if(Key.isDown(Key.LEFT)) {
MoveLeft();
} else if(Key.isDown(Key.RIGHT)) {
MoveRight();
} else {
LeftRightReleased();
}

if(Key.isDown(Key.SPACE)) {
ButtonPressed();
}
}
function ButtonPressed() {
}



Now we need to create an object that can be 'shot' at something else.

Let's start by creating a Projectile class which we will use for shooting about the place :)



class Projectile extends BaseObject {
var power:Number;
var LifeTimer:Number;
var owner:BaseObject;
function Projectile(_oList:ObjectList, clipName:String, _parent:BaseObject) {
super(_oList, clipName, _parent);
LifeTimer = 50;
}
function Behaviour() {
super.Behaviour();
if(LifeTimer > 0) {
LifeTimer--;
} else {
Remove();
}
}
}


This bullet code contains some behaviour which removes it after 50 frames.
Ok so how do we go about shooting it or making it collide? We'll take care of the shooting part first.

First create a small movieclip in the library which will serve as your bullet. Export for actionscript in the library and call it 'Bullet'

Now add this code to Player



var FireTime:Number;
function Behaviour() {
super.Behaviour(); // Remember to add this or all our button handling code wont be run
// Decrement our 'firetime' counter so we can fire again when it reaches 0
if(FireTime > 0) {
FireTime--;
}
}
function ButtonPressed() {
// Are we allowed to fire?
if(FireTime == 0) {
// Create a new projectile, set it to our position, set its velocity
var p:Projectile = new Projectile(oList, "Bullet", null);
p.isEnemy = false;
p.vely = -8;
p._x = _x;
p._y = _y;
p.owner = this;
// Give us a delay of 5 frames before we can fire again
FireTime = 5;
}
}


When you press the button in game you should get a steady stream of bullets
Now onto the collision...one of the easiest ways to do this is to set up a simple bounding box or use sphere collision. Let's use sphere collision for this basic example as it's very simple and requires less variables/setup than box collision.

First of all we need to put a variable somewhere which holds the 'size' of each of our objects. I reckon we should put this on the BaseObject class as most if not all objects in the game will have some sort of size.

Add the size variable and the collision variable to BaseObject

This size variable will be the radius of your collision circle (not the diameter!)



var size:Number;
var collision:Boolean = true; // Default this to true, most objects in game will have collision


Now in your constructor function for Projectile set the size of the object (or you could do it on the firing code to make bullets with different collision box sizes depending on who fired them - i.e. big for good guys, small for bad guys to give you a chance!)



function Projectile(_oList:ObjectList, clipName:String, _parent:BaseObject) {
super(_oList, clipName, _parent);
LifeTimer = 50;
size = 5;
}


So we've got a 'size' on our projectiles. Let's make them collide with other things - for now we will put the code directly in the Projectile class

Add the CheckCollision function and modify Behaviour accordingly



function CheckCollision() {
// Loop through the object list and find all objects which we could collide with
for(var i = 0; i < oList.length; i++) {
// Don't collide with myself!
if(oList[i] <> this) {

// Don't let friendly bullets collide with friendlies or enemy bullets collide with enemies
if(oList[i] != owner) {

// Check to see if we were close enough to hit the object
// Use the formula x*x + y*y - R*R
var dx:Number, dy:Number, r:Number;
dx = _x - oList[i]._x;
dy = _y - oList[i]._y;
r = size + oList[i].size;

// This formula returns a value - if the value is zero or less than zero the two circles are overlapping

if(dx * dx + dy * dy - oList[i].size * oList[i].size <= 0) {
// This is where we'd do some damage to the object we hit but we haven't got that far yet, so for now lets just remove the bullet
Remove();
return;
}
}
}
}
}

function Behaviour() {
super.Behaviour();
if(LifeTimer > 0) {
LifeTimer--;
} else {
Remove();
}
CheckCollision();
}


Now these bullets should collide with objects and disappear. Let's give them something to collide against

Modify the code on the root timeline to



var oList:ObjectList = new ObjectList(this);
var test:Player = new Player(oList, "TestObject", null);
var test2:BaseObject = new BaseObject(oList, "TestObject", null);
test2.size = 30;
test2._x = 200;
test2._y = 10;
test._x = 200;
test._y = 200;
onEnterFrame = function() {
oList.MainLoop();
}


Now you have a solid object which should be absorbing the bullets you are firing at it!

Note that bullets can also collide with themselves at the moment so I'd probably not make them too big (size 50 will make them collide with each other and disappear!)

Why not add a few simple particle effects to the bullets...create a new library object called 'Spark' and make it a little yellow circle (or be imaginative!) make sure you link it with the 'spark' name

Create a new class called Particle



class Particle extends BaseObject {
var LifeTimer:Number;
function Particle(_oList:ObjectList, clipName:String, _parent:BaseObject) {
super(_oList, clipName, _parent);
LifeTimer = 50;
collision = false;
}

function Behaviour() {
super.Behaviour();
if(LifeTimer > 0) {
LifeTimer--;
} else {
Remove();
}
}
}


Now add the Kill() function to Projectile and change the call to Remove() in the collision code to Kill() instead



function Kill() {
// Create 10 particles and set them to random directions. Also set the lifetimer to a random small value
for(var i = 0; i < 10; i++) {
var p:Particle = new Particle(oList, "Spark", null);
p._x = _x;
p._y = _y;
p.velx = Math.random() * 10 - Math.random() * 10;
p.vely = Math.random() * 10 - Math.random() * 10;
p.LifeTimer = 10 + random(5);
}
// Make sure we remove this object
Remove();
}


There you go, lovely particles flying about the place!

Right - that's got you started. I will be doing a second part to finish off this tutorial soon :)

::Charleh::

prg9
November 23rd, 2007, 05:22 PM
Hiya all, I've started writing up a tut on creating a basic game engine for all those struggling - so here it is...! Creating a basic game engine using classes in AS2.0/3.0: ..... Right - that's got you started. I will be doing a second part to finish off this tutorial soon

Really nice Charleh, this is good for classes, particles, games etc... should be a good resource for people... looking forward to see part 2. Thanks for the effort and sharing, good work, its a nice overall learning resource for people. Nice post!

;)

Charleh
November 30th, 2007, 09:19 AM
Creating a basic game engine using classes in AS2.0/3.0:

Part 2

Hello again, welcome back - if you missed part 1 of the tutorial then I suggest reading up on that first before reading this one.

You can find it at:

http://www.kirupa.com/forum/showthread.php?t=281507

For those that missed it I'll briefly recap what we did last time:

- Gave a quick overview of classes and how to define them
- Created our main game object
- Created our game object handler
- Put these together to form our game engine
- Added a basic animation system
- Created the input handling class
- Added a player class which allowed movement/firing

So what's next on the agenda?

Let's start with adding an enemy handling class. We can create the basic 'space invaders' aliens which move left and right across the screen in a group quite easily.

1. Enemies

First of all let's create an intermediate class for enemies which will handle the most basic aspects of enemy behaviour. We will also need to modify BaseObject to add a health variable and a method for modifying that health variable.

Modify the BaseObject class and add the following



var health:Number;

function Damage(d:Number) {
health -= d;

// We will allow negative health for objects which allows a little more flexibility in reacting to high damage attacks
// i.e. enemies hit directly by missiles might explode, whereas enemies hit by splash damage will just set on fire
}


Now create the basic enemy handling class called BaseEnemy which contains the following



class BaseEnemy extends BaseObject {

var PointsValue:Number;

function BaseEnemy(_oList:ObjectList, clipName:String, _parent:BaseObject) {
super(_oList, clipName, _parent);
}

function Behaviour() {
super.Behaviour();

if(health < -20) {
OverKill();
} else if(health <= 0) {
Kill();
}
}

function Kill() {
oList.Player.AddScore(PointsValue);
Remove();

}

function OverKill() {
oList.Player.AddScore(PointsValue);
Remove();
}
}


We have defined what happens to a basic enemy at the lowest level. It can be killed when it's health reaches 0, or 'overkilled' when its health is less than -20. It adds its 'PointsValue' to the player score when it's killed via one of these two methods. Notice that Kill() doesn't do more than just remove the object for this class - the sub classes of BaseEnemy will add explosions and particles to this function so that the killing of bad guys is a little more exciting. We could put these effects in the 'Remove' function but that would make removing the enemies without making them explode impossible!

Now let's subclass this one to make a moving enemy - it will move to one side, then reverse direction if it meets the end of the screen...



// Yeah the front ones look a bit like crabs in space invaders...they certainly move like them...so I called them EnemyCrab!

class EnemyCrab extends BaseEnemy {

var Direction:Number;

function EnemyCrab(_oList:ObjectList, clipName:String, _parent:BaseObject, _Controller:EnemyController) {
super(_oList, clipName, _parent);
PointsValue = 25; // 25 points for the crab dudes!
health = 10; // Easy to kill

var a:Animation = new Animation("Healthy");
a.addFrames(new Array(1,2));
a.addDelays(new Array(10,10));
anims.push(a);

var a:Animation = new Animation("Hurt");
a.addFrames(new Array(3,4));
a.addDelays(new Array(10,10));
anims.push(a);

Direction = 1;

SetAnimation("Healthy");
}

function Behaviour() {
super.Behaviour();

velx = 5 * Direction;

if(_x > 400) {
Direction = -1;
} else if(_x < 100) {
Direction = 1;
}

if(health < 10) {
if(currentAnim.animName != "Hurt") {
SetAnimation("Hurt");
}
}
}
}


Ok so you will need to make a new movieclip for this enemy with 4 frames. Two for when its flying about unhurt, and two for when it's been damaged (maybe covered in blood, bits missing, use your imagination!)

When the crab gets hit, it will start playing it's "Hurt" animation instead of the regular one so you know hes been given a taste of laser in the eye!

We need to add a couple more things to the classes now, we are missing a couple of little bits and pieces...

Add a Player object reference to objectList - this is so that all objects can find the most important game object (the player) easily



var Player:Player;


and in the root after the declaration of the player add the reference to it to the objectlist



var test:Player = new Player(oList, "TestObject", null);
oList.Player = test;


Also add the AddScore function to the Player class - it won't do anything at the moment



function AddScore(s:Number) {
}


And finally add the damage code to Projectile.as just above the Kill() function we added after a collision happens. This makes our projectiles deal damage when they hit!



oList[i].Damage(power);


Now go to the timeline and change the instantiation of that object (the one we were shooting at before) to a EnemyCrab instead of a BaseObject - make sure you use your new enemy movieclip with 4 frames instead of the one we did for our player. You should now have a little critter that moves left and right pretty sharpish at the top of the screen, and he should disappear upon a couple of hits from your gun - the first hit will make him look a bit beaten up too! Cool eh? Now feel free to add a 'Death' animation to the bad guy or make some of your own particles that explode out when hes hit. Bear in mind the death animation will need extra code to make sure the enemy isn't removed until his animation has finished playing. I will be posting two versions of the game at the end of the tutorial, one completed via the tutorial and one with a few more special effects and animations to spice it up a bit!

2. More Enemies! (Oh..and Events!)

We've got one bad guy now, let's add a few more. The problem we are about to face at the moment though, is that the objects will all move independently of each other - in space invaders when one line of bad guys hits the end of the screen the whole lot change direction, we will try to get this behaviour working with our enemies. The simplest way to do this is to add the enemies to an array, but this probably doesn't have the flexibility we require as we are creating an 'engine' not just a simple game - and you want to be able to build on it! Instead let's create a new class that will coordinate groups of enemies! We will also need some sort of 'event' handling code which will allow our enemy handler and enemies to communicate with each other!

First of all let's add a new class which will handle the passing around of events



class EventConsumer {

var listeners:Array;
var queue:Array;

function EventConsumer() {
listeners = new Array();
queue = new Array();
}

// Add a listener to this object
function AddListener(l:EventConsumer) {
listeners.push(l);
}

// Remove a listener
function RemoveListener(l:EventConsumer) {
for(var i = 0; i < listeners.length; i++) {
if(listeners[i] == l) {
listeners.splice(i, 1);
}
}
}

// Remove all listeners
function RemoveAllListeners() {
listeners = new Array();
}

// Handle an event passed in
function HandleEvent(e:GameEvent) {
// Doesn't do much here, but it will on your other classes!
}

// Dont send events immediately... instead add the events to a queue - this is to stop events
// being handled halfway through a physics step which can cause slight syncronisation problems
// with objects
function AddEvent(e:GameEvent) {
queue.push(e);
}

// Dispatch our events then clear the queue
function DispatchEvents() {
for(var i = 0; i < queue.length; i++) {
for(var j = 0; j < listeners.length; j++) {
listeners[j].HandleEvent(queue[i]);
}
}

queue = new Array();
}
}


You will also need to make the GameEvent class - this just contains an event name and an optional array of parameters. The parameters are useful for lots of things - say you shoot a bad guy, you might want to send events to all the other bad guys telling them how much damage you are doing to this little dude - well with the parameters you can!

I have thought about using named parameters and multiple classes for the events, but far by the simplest way is just to add any arguments as an array (you can use named indexes if you want to make things clear for yourself).



class GameEvent {
var EventName:String;
var EventArgs:Array;

function GameEvent(Name:String, Args:Array) {
EventName = Name;
EventArgs = Args;
}
}


So now make BaseObject extend this EventConsumer class



class BaseObject extends EventConsumer {


Now you can add any number of listener objects to any other object and when sending an event, all the listener objects will handle it. This means we can now set up a controller object for our bad guys, add them all to this object and then send events to them to control their actions.

It's entirely possible to just use a BaseObject that doesn't do anything as your enemy controller and just send events via that to the other enemies in the group, but you won't have that extra layer of control that you get from creating a new class for it

So let's add the enemy controller class



class EnemyController extends EventConsumer {

var oList:ObjectList;

function EnemyController(_oList:ObjectList) {
oList = _oList;
// Add this to a new array in the object list called the consumer list
oList.cList.push(this);

}

function HandleEvent(e:GameEvent) {
// Here you can handle the events that get received and if neccessary do some fancy stuff
switch(e.EventName) {
case "ScreenExitRight":
DispatchEvent(e);
break;
case "ScreenExitLeft":
DispatchEvent(e);
break;
}
}
}


Now that we have implemented this, we need to make sure any objects which don't extend BaseObject but do extend EventConsumer get handled in the game loop - modify the ObjectList MainLoop function accordingly




// Add the new cList array
var cList:Array;

// Change the constructor to init the cList array
function ObjectList(_rootRef:MovieClip) {
root = _rootRef;
rList = new Array();
cList = new Array();
}

function MainLoop() {
for(var i = 0; i < this.length; i++) {
this[i].Behaviour();
this[i].Physics();
this[i].Render();
}

// Split the DispatchEvents for all game objects off into a seperate loop
// This ensures all objects get events at the same time
for(var i = 0; i < this.length; i++) {
this[i].DispatchEvents();
}

// Dispatch any cList events
DispatchEvents();

CleanUp();
}

// This function just dispatches all events for cList
function DispatchEvents() {
for(var i = 0; i < cList.length; i++) {
cList[i].DispatchEvents();
}
}


3. Event Response

we can change the EnemyCrab class to respond to events accordingly.. first we add a reference to our 'controller' class and add it in the constructor, and then we remove the direction change code and replace it with some event code...



var Controller:EnemyController;

// Change your constructor to look like this

function EnemyCrab(_oList:ObjectList, clipName:String, _parent:BaseObject, _Controller:EnemyController) {

// Other code still here...
// blah blah

// Add these lines at the bottom of it

Controller = _Controller;

if(Controller != null) {
Controller.AddListener(this);
}
}


// Now change your behaviour function...
// -------------------------------------

function Behaviour() {
super.Behaviour();

velx = 5 * Direction;

// Remove this code
/*
if(_x > 400) {
Direction = -1;
} else if(_x < 100) {
Direction = 1;
}
*/

// And replace with this

// If we have a controller
if(Controller != null) {
if(_x > 400) {
Controller.HandleEvent(new GameEvent("ScreenExitRight"));
} else if (_x < 100) {
Controller.HandleEvent(new GameEvent("ScreenExitLeft"));
}
}

if(health < 10) {
if(currentAnim.animName != "Hurt") {
SetAnimation("Hurt");
}
}
}

// And add the HandleEvent function
function HandleEvent(e:GameEvent) {
super.HandleEvent(e);

// If we got one of the events then change direction depending on which one it was
switch(e.EventName) {
case "ScreenExitLeft":
Direction = 1;
break;
case "ScreenExitRight":
Direction = -1;
break;
}
}


So now in your timeline change the code to add a couple more EnemyCrab objects and set them to different positions from the original



var oList:ObjectList = new ObjectList(this);
var test:Player = new Player(oList, "TestObject", null);

// Add this line to create the controller
var controller:EnemyController = new EnemyController(oList);

oList.Player = test;

var test2:EnemyCrab = new EnemyCrab(oList, "TestObject", null, controller);
test2.size = 30;
test2._x = 200;
test2._y = 10;

var test3:EnemyCrab = new EnemyCrab(oList, "TestObject", null, controller);
test3.size = 30;
test3._x = 280;
test3._y = 10;


So now run the game and watch the enemies move in formation! Great eh? Try shooting the bad guys, if you create a good few you can blow away the end ones and the rest will still move the full width of the screen - they will all move together until they are toast!

4. Attacking the Player

Now we've got those critters moving in formation, let's make them shoot back at the player. Space invaders aren't the brightest of the bunch, so they should just randomly shoot every now and then... Also we need to make sure that missiles from the space invaders don't collide with other space invaders - at the moment missiles only ignore their 'owners'. We can circumvent this by adding a new property to the BaseObject class - I'm putting it on the BaseObject class because you may want to check many different objects against each other, not just missiles vs enemies

So on BaseObject add the following




var category:Number = 0;

// Setup 'constants' for this category - Flash doesn't really have constants so just make sure you don't change these during run time :)
var NEUTRAL:Number = 0;
var FRIENDLY:Number = 1;
var ENEMY:Number = 2;



So by default the category for a new object will be 0 (NEUTRAL). Let's modify the Projectile class and change the collision code so it ignores all objects in the same category as itself... we will also make sure it ignores particles because we don't need to collision check them and with brute force collision checking this can become processor expensive

change the CheckCollision function to



function CheckCollision() {
// Loop through the object list and find all objects which we could collide with
for(var i = 0; i < oList.length; i++) {

// ignore particles
if(!(oList[i] instanceof Particle)) {

// Don't collide with myself!
if(oList[i] <> this) {

// Ignore anything thats in the same category
if(this.category != oList[i].category) {

// Don't let friendly bullets collide with friendlies or enemy bullets collide with enemies
if(oList[i] != owner) {

// Check to see if we were close enough to hit the object
// Use the formula x*x + y*y - R*R
var dx:Number, dy:Number, r:Number;

dx = _x - oList[i]._x;
dy = _y - oList[i]._y;
r = size + oList[i].size;

if(dx * dx + dy * dy - oList[i].size * oList[i].size <= 0) {
// Damage is whatever power the bullet is
oList[i].Damage(power);
Kill();
return;
}

}
}
}
}
}
}


Now make sure the Player is set to FRIENDLY and that his projectiles are set to the same category upon firing




// Add this to the ButtonPressed() function after the bullet has been instantiated
p.category = this.category;

// Add this to the Players constructor class
category = FRIENDLY;


Now set the category for the EnemyCrab class



// In EnemyCrab constructor
category = ENEMY;


Now add a random chance for the enemies to fire each frame



// In the Behaviour function add this code
// It will just make a 2 in 100 chance of an enemy firing each frame - we will change this later
if(random(100) > 98) {
FireMissile();
}


// now add this new FireMissile function
function FireMissile() {
var p:Projectile = new Projectile(oList, "Bullet", null);
p.isEnemy = false;
p.vely = 5;
p._x = _x;
p._y = _y;
p.category = this.category;
p.owner = this;
}


So now you will have the odd bullet (or 10) raining down from the enemies! Lovely!

5. Explosions

Let's create some basic particle explosions for when you kill the invaders...

Add the following code to EnemyCrab



function Kill() {
for(var i = 0; i < 30; i++) {
var p:Particle = new Particle(oList, "Spark", null);
p._x = _x;
p._y = _y;
p.velx = Math.random() * 10 - Math.random() * 10;
p.vely = Math.random() * 10 - Math.random() * 10;
p.LifeTimer = 10 + random(5);
}
super.Kill();
}


So now when the enemies die, they spew out a load of particles!

Ok I'll leave this part of the tutorial as it is and do a part 3 soon! I don't think I've missed anything but in case I have the source and .fla is attached! I've added rows of aliens in the attached version. I've also added a cacheAsBitmap line to the BaseObject class to help speed up rendering when there's a lot going on. Finally I've tweaked the Animate function in BaseObject to allow animation delays of -1. These allow you to keep an animation looping on the same frame, which I use to make the ship tilt animations (have a look in the Player class MoveLeft and MoveRight functions)

Of course the game could be sped up a lot more by using bitmap rendering and can be kept nice and consistent using an accumulator to keep the game speed constant - I will be covering that in the next tutorial along with a lot of optimisation to make it all run nice!

::Charleh::

Dark Viper
November 30th, 2007, 01:10 PM
Im sorry if you've already answered this but, do you have a website with this info? I'd rather read it there than squished in a forum post.

Charleh
November 30th, 2007, 02:25 PM
Negatory :P I can put it on my personal site if you want, I'll create a couple of pages with the info on!

prg9
November 30th, 2007, 07:23 PM
Im sorry if you've already answered this but, do you have a website with this info? I'd rather read it there than squished in a forum post.

You can also copy paste into your favoriate text editor (Word, etc....) and resize it etc... to whatever you prefer, or even print it out :thumb: Would save Charlie some work, as he has already provided a heck of alot of typing here ;)

Dark Viper
December 1st, 2007, 03:38 PM
nnnnnnnah, cant be bothered...

prg9
December 1st, 2007, 05:51 PM
nnnnnnnah, cant be bothered...

:huh: Wow, yeah that would take like 2 minutes for you to do? :sure:

slyerguy
December 1st, 2007, 07:00 PM
im right now at step 2. Creating the game objects, but i have all the code in place and my TestObject movieclip is not appearing on screen. All the code is the exact same as written and i have set the linkage of the TestObject and it still doesn't work. Any idea as to why it doesnt appear?

Charleh
December 1st, 2007, 07:49 PM
There may be a few mistakes in the article - of course I tried to make it as I went along but sometimes you have to go eat, talk to your other half etc, so when I got back to my chair I forgot where I was - this may mean there are some errors

I'll try and help if I can, and then I can fix up the article :)

Can you post the code you've got so far in your class files and main timeline

Also you can look at the final source code for the tutorial which might give you a clue!

Blabbo
December 2nd, 2007, 09:26 PM
very cool, Charleh!
you are my god!
keep it up!
:)

slyerguy
December 2nd, 2007, 11:36 PM
BaseObject Code:

class BaseObject {
// Ok so here are some useful vars
var parent:BaseObject = null; // Holds information on the 'parent' object - this allows you to create heirarchys of objects and means you can have objects which are 'attached' to others. Useful for multipart objects like tanks with turrets etc
var oList:ObjectList = null; // This is a reference to an instance of a class we are going to create next - the ObjectList. This will be the class responsible for handling our list of objects
var clip:MovieClip = null; // The movieclip to use for display purposes - you could use bitmap rendering, but for the sake of simplicity I'll use this for the tut
var _x:Number = 0; // x/y position of the object
var _y:Number = 0;
var velx:Number = 0; // Velocity of object
var vely:Number = 0;

// Initialise the object
function BaseObject(_oList:ObjectList, clipName:String, _parent:BaseObject) {
oList = _oList;
parent = _parent;
SetMovie(clipName);
oList.push(this); // Add this to the object list
}

// Attaches the movieclip which will represent this object - attaches to _root if parent is not specified
function SetMovie(clipName:String) {
if(parent == null) {
clip = oList.root.attachMovie(clipName, clipName + oList.root.getNextHighestDepth(), oList.root.getNextHighestDepth());
} else {
clip = parent.clip.attachMovie(clipName, clipName + parent.clip.getNextHighestDepth(), parent.clip.getNextHighestDepth());
}
clip.stop(); // Stop the animations from playing
Render(); // Call render to set the MC to the correct place/size/etc on screen - should cut any flickering
}

// Behaviour function, does nothing on base
function Behaviour() {
// No behaviour for the base
}

// Physics, set the x/y position += the velocity
function Physics() {
_x += velx;
_y += vely;
}

// Put the MC in the correct place on screen
function Render() {
clip._x = _x;
clip._y = _y;
}

// Function to remove this object from the game
function Remove() {
oList.RemoveObject(this);
}

// Clean up, remove movie clips etc
function CleanUp() {
clip.removeMovieClip();
}
}

ObjectList Code:

class ObjectList extends Array {
// The object list is just an 'array' with some extra functionality
var rList:Array; // This will hold a list of objects to remove from the game, the reason we use this list will become apparent (or maybe not, but I'll tell you anyway!)
var root:MovieClip; // Just holds a reference to the _root so that you dont need to use _root in your game in case you want to put a preloader on it

// Init etc..!
function ObjectList(_rootRef:MovieClip) {
root = _rootRef;
rList = new Array();
}

// Simply pushes the object onto the removal list
function RemoveObject(obj:BaseObject) {
rList.push(obj);
}

// Gets the array index for the specified object
function GetIndex(o:BaseObject):Number {
var i:Number;
for (i = 0; i < this.length; i++) {
if(this[i] == o) {
return i;
}
}
}

// This runs through the list of objects which are set to be removed and gets rid of them by running their cleanup() function to remove the movieclips from the game and then splicing them out of the array
function CleanUp() {
var SpliceIndex:Number;

for(var i = 0; i < rList.length; i++) {
SpliceIndex = GetIndex(rList[i]);
this[SpliceIndex].CleanUp();
this.splice(SpliceIndex, 1);
}
}

// Loop through all game objects and run their update functions
function MainLoop() {
for(var i = 0; i < this.length; i++) {
this[i].Behaviour();
this[i].Physics();
this[i].Render();
}
CleanUp(); // Remove any items that are marked for deletion
}
}

Main Timeline Code:

var oList:ObjectList = new ObjectList();
var test:BaseObject = new BaseObject(oList, "TestObject", null);
test._x = 200;
test._y = 200;

onEnterFrame = function() {
oList.MainLoop();
}

And i have a movieclip TestObject with linkage identifier as TestObject

Charleh
December 3rd, 2007, 04:54 AM
Ah sorry my bad, I've fixed the problem in the tut - you need to modify the code on the timeline from



var oList:ObjectList = new ObjectList();


to



var oList:ObjectList = new ObjectList(this);


I forgot to pass in the reference to the _root :)

Charleh
December 3rd, 2007, 06:21 AM
I also forgot a line of important code in the ObjectList class - make sure you change it or it severely impacts performance as the game goes on



function CleanUp() {

var SpliceIndex:Number;

for(var i = 0; i < rList.length; i++) {
SpliceIndex = GetIndex(rList[i]);
this[SpliceIndex].CleanUp();
this.splice(SpliceIndex, 1);
}

// EDIT: Added this to clear out the rList array or it just grew and grew and things got slow :(
rList = new Array();
}

Blabbo
December 4th, 2007, 03:26 AM
some questions :)

why do you put the objects to remove into an array?
why not just delete them right away like this:

function RemoveObject(obj:BaseObject) {
rList.push(obj);
SpliceIndex = GetIndex(obj);
this[SpliceIndex].CleanUp();
this.splice(SpliceIndex, 1);

}


in as3 the Event Sytem was improved alot compared to as2.
Would you code your own Event-System nevertheless?
if yes, what are the advantages?

Charleh
December 4th, 2007, 05:47 AM
The reason you don't remove objects right away is down to arrays and also timing (and indirectly, events):

1: The objects array will change length if you remove an object mid frame therefore an object may get skipped in the engine update. If you have an array of 10 elements long and the array counter is at element 5 - if you remove an object at element 4, the object at position 5 gets shifted down to 4. The pointer still remains at 5 so this object will be skipped during this update.

2: The the objects may have events they need to dispatch - all events are dispatched at the end of the frame (if you've not got to the events bit yet have a look at bit further on). This is done to keep everything synchronised - for instance if the space invaders are moving left and one reaches the edge of the screen it sends an event to the controller to change direction. If this is done immediately in the middle of an update then all the invaders would receive the event and change direction - now all invaders know they should be moving right...unfortunately the ones that have already moved will have moved left, the ones that haven't moved yet will move right so they will all get out of sync. Space Invaders love formations...!

The advantages to your own event system? Not entirely sure how the AS3 one works, but it's probably got some overhead - it's up to you, if you are comfortable with using the AS3 events then use them. I just prefer to have everything as cut down as possible as I probably won't use the extra functionality.

My event system is pretty simple it's just an event handling function on an object and a single event class with a name and an array list of unnamed parameters - nothing special, but it does the job.

vini
December 4th, 2007, 01:16 PM
really impressive

alexgeek
December 4th, 2007, 02:51 PM
Brilliant tutorial! I will read the rest later.

digitaldivide
December 5th, 2007, 05:04 PM
Thank you very munch for clearing up some things about classes! Some of us are mostly designers with a thing for Flash, and for me classes were sometimes a bridge to far, althought I have to learn it for my work at the moment. Thanks very much.

zedetach
December 6th, 2007, 03:18 AM
First off, thank you very much Mr. Charleh for your in-depth game engine tutorial. With regards to the first part, I have managed to follow it till the part where I'm suppose to add some codes in the BaseObject class in order to utilise the animation class.

I'm getting the following error :-

Description - This statement is not permitted in a class definition.

Source - anims = new Array();

Maybe if you could have a look at the code you might be able to spot my mistake.


class BaseObject {
// Ok so here are some useful vars
var parent:BaseObject = null; // Holds information on the 'parent' object - this allows you to create heirarchys of objects and means you can have objects which are 'attached' to others. Useful for multipart objects like tanks with turrets etc
var oList:ObjectList = null; // This is a reference to an instance of a class we are going to create next - the ObjectList. This will be the class responsible for handling our list of objects
var clip:MovieClip = null; // The movieclip to use for display purposes - you could use bitmap rendering, but for the sake of simplicity I'll use this for the tut
var _x:Number = 0; // x/y position of the object
var _y:Number = 0;
var velx:Number = 0; // Velocity of object
var vely:Number = 0;

// Initialise the object
function BaseObject(_oList:ObjectList, clipName:String, _parent:BaseObject) {
oList = _oList;
parent = _parent;
SetMovie(clipName);
oList.push(this); // Add this to the object list
}

// Attaches the movieclip which will represent this object - attaches to _root if parent is not specified
function SetMovie(clipName:String) {
if(parent == null) {
clip = oList.root.attachMovie(clipName, clipName + oList.root.getNextHighestDepth(), oList.root.getNextHighestDepth());
} else {
clip = parent.clip.attachMovie(clipName, clipName + parent.clip.getNextHighestDepth(), parent.clip.getNextHighestDepth());
}
clip.stop(); // Stop the animations from playing
Render(); // Call render to set the MC to the correct place/size/etc on screen - should cut any flickering
}

// Behaviour function, does nothing on base
function Behaviour() {
// No behaviour for the base
}

// Physics, set the x/y position += the velocity
function Physics() {
_x += velx;
_y += vely;

// Add this code to the Physics() function
Animate();
}

// Put the MC in the correct place on screen
function Render() {
clip._x = _x;
clip._y = _y;
}

// Function to remove this object from the game
function Remove() {
oList.RemoveObject(this);
}

// Clean up, remove movie clips etc
function CleanUp() {
clip.removeMovieClip();
}


// variables
var anims:Array; // An array of animation objects
var currentFrame:Number; // Holds the index of the current animation frame
var currentDelay:Number; // Holds the amount of time left before we get the next frame
// in the constructor add


anims = new Array(); // to initialise the anims array
// Add the new 'animate' function - this function just uses the animation class to find out which
// frame we should be displaying from the movieclip

function Animate() {

// Check if we need to get the next frame, if not just decrement the delay
if(currentAnim != null) {
if (currentDelay == 0) {
currentFrame = currentAnim.getNextFrame(currentFrame);
currentDelay = currentAnim.getDelay(currentFrame);
clip.gotoAndStop(currentAnim.getFrame(currentFrame ));
trace(currentAnim.getFrame(currentFrame));

// If the delay is negative then quit this animation
if(currentDelay < 0) {
StopAnimation();
}
} else {
currentDelay -= 1;
}
}
}

// Add this function - it just stops the animation thats currently playing
function StopAnimation() {
currentFrame = 0;
currentDelay = 0;
currentAnim = null;
}
// Add this setanimation function
function SetAnimation(searchName:String) {
// Find animation by name in anim list does nothing if it can't find one
var i:Number;

for(i = 0; i < anims.length; i++) {
if (anims[i].animName == searchName) {
currentAnim = anims[i];
}
}
currentDelay = 0;
currentFrame = 0;
}
// Add this randomanimation function
function RandomAnimation(searchTag:String) {
// Find animation by tag in anim list and choose a random one
var i:Number = 0;
var targetAnims:Array;
targetAnims = new Array();
for(i = 0; i < anims.length; i++) {
if (anims[i].animTag == searchTag) {
targetAnims.push(anims[i]);
}
}
currentAnim = targetAnims[random(targetAnims.length)];
currentDelay = 0;
currentFrame = 0;
}
}

zedetach
December 6th, 2007, 03:30 AM
Sorry but I forgot to mention two more errors.

Description - There is no property with the name '_x'.

Source - test._x = 200;

Description - There is no property with the name '_y'.

Source - test._y = 200;


The errors above only appeared after i changed the code in the main timeline from this :-

var test:BaseObject = new BaseObject(oList, "TestObject", null);

to this :-

var test:animatedClass = new animatedClass(oList, "TestObject", null);

Charleh
December 6th, 2007, 05:06 AM
I think maybe you've misinterpreted some of the instructions and put the array instantiation outside the constructor - pay careful attention to the comments that I put in the code snippets!



// variables
var anims:Array; // An array of animation objects
var currentFrame:Number; // Holds the index of the current animation frame
var currentDelay:Number; // Holds the amount of time left before we get the next frame

// in the constructor add
anims = new Array(); // to initialise the anims array



I avoid posting the whole class again by putting instructions in the comment, basically this piece of code



// in the constructor add
anims = new Array(); // to initialise the anims array


should go in the constructor

The constructor should look like



// Initialise the object
function BaseObject(_oList:ObjectList, clipName:String, _parent:BaseObject) {
oList = _oList;
parent = _parent;
SetMovie(clipName);
anims = new Array(); // to initialise the anims array
oList.push(this); // Add this to the object list
clip.cacheAsBitmap = true;
}


Also when I say to add variables definitions such as



// variables
var anims:Array; // An array of animation objects
var currentFrame:Number; // Holds the index of the current animation frame
var currentDelay:Number; // Holds the amount of time left before we get the next frame


It's probably best to put these at the top of the class, as that's the usual coding standard which aids readability. In fact you could seperate them all out into variable 'categories' using comments (like Gameplay, physics, behaviour) for readability

Make sure you open the source files that I've added at the end of the tutorial and you can see the correct code - make the neccesary adjustments to yours :)

zedetach
December 6th, 2007, 09:41 AM
Mr. Charleh thank you replying. I'm sorry I din't notice the source file was available for download :p

slyerguy
December 6th, 2007, 08:33 PM
im trying to make a zelda based game with this tutorial but im not to sure if im doing it right. I was wondering if you could point me in the right direction. so far i have my main character linkMC (linkage also linkMC) , no coding on the main timeline. I also have 2 classes so far.
playerInput class


class playerInput {
var linkSpeedX:Number = 0;
var linkSpeedY:Number = 0;
var linkHealth:Number = 6;
//constructor
function playerInput() {
function Behaviour() {
if (Key.isDown(Key.UP)) {
moveUp();
} else if (Key.isDown(Key.DOWN)) {
moveDown();
} else {
upDownReleased();
}
}
function Behaviour() {
if (Key.isDown(Key.LEFT)) {
moveLeft();
} else if (Key.isDown(Key.RIGHT)) {
moveRight();
} else {
leftRightReleased();
}
}
function upDownReleased() {
}
function moveUp() {
}
function moveDown() {
}
function leftRightReleased() {
}
function moveLeft() {
}
function moveRight() {
}
}
}


and player class


class player extends playerInput {
//constructor
function player() {
super();
}
function upDownReleased() {
linkSpeedY = 0;
}
function moveUp() {
linkSpeedY = -3;
}
function moveDown() {
linkSpeedY = 3;
}
function leftRightReleased() {
linkSpeedX = 0;
}
function moveLeft() {
linkSpeedX = -3;
}
function moveRight() {
linkSpeedX = 3;
}
}


Im not sure what classes i still need and how to write them to get the character moving properly on the screen so any help would be greatly appreciated :thumb2:

Charleh
December 7th, 2007, 05:16 AM
Well you should implement the BaseObject class and you need the ObjectList class

Make PlayerInput extend BaseObject - there's no need to make a linkSpeedX/Y variable as the velx/vely on the BaseObject will take care of this.

Just change your Player class movement functions to use velx/vely instead of linkspeedx/y and remove the variable definitions for linkspeedx/y. You don't need to declare velx/vely because they will be in the BaseObject class which PlayerInput and Player (indirectly) will extend

So you will have

BaseObject:
contains the velx / vely - these are velocity variables and you should never add your own velocity variable to a class that extends BaseObject - you should put collision code in here

PlayerInput extends BaseObject:
movement functions are in here

Player extends PlayerInput:
adds handling of movement functions which directly manipulate the velx/vely of BaseObject

You need ObjectList too as this provides the basis for the main engine loop. You also need to add the few variables that I declare in the main timeline which control the ObjectList (object cleanup array etc)

I'd run through the first part of the tutorial up to the part where you get your ship moving and I'd probably run through the rest too, because it looks like you don't have that much experience with OOP programming - it helps to get an idea of the way many people do things so also read up on a few more tutorials

My tut is an attempt to create an open ended engine so a zelda game would be suited to it fine! It probably wouldn't suit turn based games (not immediately) but it's a very basic example.

slyerguy
December 7th, 2007, 06:54 PM
im not sure what im doing wrong here, im trying to add in more animations for when the character moves up and down.

Player class



class Player extends PlayerInput {
function Player(_oList:ObjectList, clipName:String, _parent:BaseObject) {
super(_oList, clipName, _parent);
var anim:Animation = new Animation("Test1");
anim.addFrames(new Array(21, 22, 23, 24, 25, 26, 27, 28));
anim.addDelays(new Array(2, 2, 2, 2, 2, 2, 2, 2));

var anim:Animation = new Animation("Test2");
anims.push(anim);
anim.addFrames(new Array(13, 14, 15, 16, 17, 18, 19, 20));
anim.addDelays(new Array(2, 2, 2, 2, 2, 2, 2, 2));
anims.push(anim);

var anim:Animation = new Animation("Test3");
anims.push(anim);
anim.addFrames(new Array(29, 30, 31, 32, 33, 34, 35, 36));
anim.addDelays(new Array(2, 2, 2, 2, 2, 2, 2, 2));
anims.push(anim);


size = 50;
isEnemy = false;
}
function UpDownReleased() {
vely = 0;
}
function MoveUp() {
if (currentAnim.animName != "Test3") {
SetAnimation("Test3");
}
vely = -3;
}
function MoveDown() {
vely = 3;
}
function LeftRightReleased() {
StopAnimation();
velx = 0;
}
function MoveLeft() {
if (currentAnim.animName != "Test2") {
SetAnimation("Test2");
}
velx = -3;
}
function MoveRight() {
if (currentAnim.animName != "Test1") {
SetAnimation("Test1");
}
velx = 3;
}
}


and the PlayerInput class



class PlayerInput extends BaseObject {
function PlayerInput(_oList:ObjectList, clipName:String, _parent:BaseObject) {
super(_oList, clipName, _parent);


var anim:Animation = new Animation("Test1");
anim.addFrames(new Array(21, 22, 23, 24, 25, 26, 27, 28));
anim.addDelays(new Array(2, 2, 2, 2, 2, 2, 2, 2));
anims.push(anim);

var anim:Animation = new Animation("Test2");
anim.addFrames(new Array(13, 14, 15, 16, 17, 18, 19 ,20));
anim.addDelays(new Array(2, 2, 2, 2, 2, 2, 2, 2));
anims.push(anim);

var anim:Animation = new Animation("Test3");
anim.addFrames(new Array(29, 30, 31, 32, 33, 34, 35, 36));
anim.addDelays(new Array(2, 2, 2, 2, 2, 2, 2, 2));
anims.push(anim);




}
function Behaviour() {
if (Key.isDown(Key.UP)) {
MoveUp();
} else if (Key.isDown(Key.DOWN)) {
MoveDown();
} else {
UpDownReleased();
}
if (Key.isDown(Key.LEFT)) {
MoveLeft();
} else if (Key.isDown(Key.RIGHT)) {
MoveRight();
} else {
LeftRightReleased();
}
}
function UpDownReleased() {
}
function MoveUp() {
}
function MoveDown() {
}
function LeftRightReleased() {
}
function MoveLeft() {
}
function MoveRight() {
}
}

so far all ive tried to do is make the animation for the moving up. The character faces up when i push the up key, but then freezes in that position and and just moves up without animation. Is there another class i have to edit or did i not write this right?

Charleh
December 7th, 2007, 07:36 PM
im not sure what im doing wrong here, im trying to add in more animations for when the character moves up and down.

Player class



class Player extends PlayerInput {
function Player(_oList:ObjectList, clipName:String, _parent:BaseObject) {
super(_oList, clipName, _parent);
var anim:Animation = new Animation("Test1");
anim.addFrames(new Array(21, 22, 23, 24, 25, 26, 27, 28));
anim.addDelays(new Array(2, 2, 2, 2, 2, 2, 2, 2));

var anim:Animation = new Animation("Test2");
anims.push(anim);
anim.addFrames(new Array(13, 14, 15, 16, 17, 18, 19, 20));
anim.addDelays(new Array(2, 2, 2, 2, 2, 2, 2, 2));
anims.push(anim);

var anim:Animation = new Animation("Test3");
anims.push(anim);
anim.addFrames(new Array(29, 30, 31, 32, 33, 34, 35, 36));
anim.addDelays(new Array(2, 2, 2, 2, 2, 2, 2, 2));
anims.push(anim);


size = 50;
isEnemy = false;
}
function UpDownReleased() {
vely = 0;
}
function MoveUp() {
if (currentAnim.animName != "Test3") {
SetAnimation("Test3");
}
vely = -3;
}
function MoveDown() {
vely = 3;
}
function LeftRightReleased() {
StopAnimation();
velx = 0;
}
function MoveLeft() {
if (currentAnim.animName != "Test2") {
SetAnimation("Test2");
}
velx = -3;
}
function MoveRight() {
if (currentAnim.animName != "Test1") {
SetAnimation("Test1");
}
velx = 3;
}
}


and the PlayerInput class



class PlayerInput extends BaseObject {
function PlayerInput(_oList:ObjectList, clipName:String, _parent:BaseObject) {
super(_oList, clipName, _parent);


var anim:Animation = new Animation("Test1");
anim.addFrames(new Array(21, 22, 23, 24, 25, 26, 27, 28));
anim.addDelays(new Array(2, 2, 2, 2, 2, 2, 2, 2));
anims.push(anim);

var anim:Animation = new Animation("Test2");
anim.addFrames(new Array(13, 14, 15, 16, 17, 18, 19 ,20));
anim.addDelays(new Array(2, 2, 2, 2, 2, 2, 2, 2));
anims.push(anim);

var anim:Animation = new Animation("Test3");
anim.addFrames(new Array(29, 30, 31, 32, 33, 34, 35, 36));
anim.addDelays(new Array(2, 2, 2, 2, 2, 2, 2, 2));
anims.push(anim);




}
function Behaviour() {
if (Key.isDown(Key.UP)) {
MoveUp();
} else if (Key.isDown(Key.DOWN)) {
MoveDown();
} else {
UpDownReleased();
}
if (Key.isDown(Key.LEFT)) {
MoveLeft();
} else if (Key.isDown(Key.RIGHT)) {
MoveRight();
} else {
LeftRightReleased();
}
}
function UpDownReleased() {
}
function MoveUp() {
}
function MoveDown() {
}
function LeftRightReleased() {
}
function MoveLeft() {
}
function MoveRight() {
}
}

so far all ive tried to do is make the animation for the moving up. The character faces up when i push the up key, but then freezes in that position and and just moves up without animation. Is there another class i have to edit or did i not write this right?

Yep - first of all you've added the animations to the PlayerInput class - you don't need to put the code for anims in there

Also you've got 2 anims.push(anim); for each animation in the Player class and one of them is in the wrong place -

var anim:Animation = new Animation("Test3");
anims.push(anim); <--- Remove this
anim.addFrames(new Array(29, 30, 31, 32, 33, 34, 35, 36));
anim.addDelays(new Array(2, 2, 2, 2, 2, 2, 2, 2));
anims.push(anim);

You need to remove the line in bold otherwise you are pushing an empty animation to the animation list then you are pushing the full animation afterwards which can be confusing for the game engine!!!

I should really put some warnings in or some errors when you try to add two animations with the same name...but this should fix your problem

slyerguy
December 7th, 2007, 08:21 PM
Still not working. When link moves left and right, it works fine but when i try up and down, it doesn't work right. I took out extra push method but that didnt solve it and when i took the animations out of the player input class, i had even more problems and even moving left and right didn't work right anymore.

Im gonna post what i have so far so you can get a better idea of what the problem is

Charleh
December 8th, 2007, 08:13 AM
Ah right, it's a bug that you've got there - I wrote the Player class for an object which only moves left/right (the Space Invaders ship!)

What you need to do is instead of putting the animation calls in the move functions, put them in the Behaviour function instead and base them on Links velocity

So when his velx is greater than 0 play the "running right" animation - if its less than 0 play the "running left" animation

if the velx is 0 and the vely is 0 stop any animations

Of course you will need to modify this behaviour in the future when you do your attacks, as you will be standing still when you attack and you don't want to StopAnimation during your attack, you will be needing to put some flags in

Anyway I've taken the animations out of the PlayerInput class, put them into the Player class, renamed them to be more meaningful and then added the behaviour to make him animate properly :)

Look at the Behaviour function in Player.as for the code!

slyerguy
December 10th, 2007, 02:01 AM
Thanks for the help Charleh with the movement, but now ive already got another problem with the game engine. this time its about the attacking i tried to add.

First off i'll tell you what i tried. I started by adding in a new variable (Boolean) called "attacking" to determine whether or not to display the attacking animation. I also added in another variable (String) called "direct" (standing for direction) to determine what direction the character is facing so as to determine which attacking animation to play. I made another Function called "Attack" that is called with the press of the space key. Inside the Attack function, it determines with some if statements which animation to play based of the direct variable. I put all the code in, but when i tested it, attacking still doesnt work. I was wondering if you could take a look at what i did and see if you can find my error.

Charleh
December 10th, 2007, 07:02 AM
Well I think you need to go back to the basics first before attempting to modify this code to make any sort of game, there are a few things wrong with the attacking code - the first is that you are using the assignment operator = to do comparison which should be ==

if(attacking = true) would ALWAYS be true as you are assigning attacking to true in the same statement you are testing it. if(attacking == true) is a comparison check.

I'd go back to your code and first modify this. You were on the right track putting an Attack function in PlayerInput - but you also put the same key handling code in Player which is a conflict of code.

You may also want to add an 'attack timer' too or use the event system to signify the start/end of an attack. This would make sure that you could only attack again once the first attack has finished.

slyerguy
December 10th, 2007, 10:54 PM
OK, ive got all the code working right, got rid of anything wrong and excess so now to get down to the attacking. I can get the animations to play when the space key is hit and in the right direction. the only problem i have is and i have no idea how to implement it is some sort of attack timer like you previously stated.

anyways heres the fixed up version if you or anyone else wants to see it

Charleh
December 12th, 2007, 05:48 AM
Well what you've done is when you hold Space you get a constant playing of the attack animation.

You need to set a flag when you attack which lets the animation complete without letting the user move/attack again, then when the animation is complete unset the flag. I'd probably do it either with an event which is sent to the character from itself (add itself as a listener of itself) which is handled which unsets the flag or using a timer (i.e. set a variable to say 20 - decrement this variable every frame and then unset your flag when it reaches 0)

Dimitree
December 18th, 2007, 08:12 PM
Hey Charleh nice tutorial. Can you please define which function I have to modify so that the shooting should be horizontal instead of vertical?=)

Is it the shooting function?

Charleh
December 19th, 2007, 05:14 AM
Yeah - the shooting function sets the velocity of the projectile (vely/velx) so modify this to set a new direction

Dimitree
December 19th, 2007, 06:16 AM
Yeah - the shooting function sets the velocity of the projectile (vely/velx) so modify this to set a new direction
Sorry but I am a bit confused ... In which class is it in?:red:
and which parametrs I should change?

Charleh
December 19th, 2007, 07:22 AM
In buttonPressed of the player class



function ButtonPressed() {
if(FireTime == 0) {
var p:Projectile = new Projectile(oList, "Bullet", null);
p.isEnemy = false;
p.vely = -8;
p._x = _x;
p._y = _y;
p.category = this.category;
p.owner = this;
FireTime = 5;
}
}


Where I set p.vely = -8, just replace that with your new velocity (i.e. p.velx = 8)

The velx/vely are the velocities for game objects.

Bear in mind this code might have additions as I'm currently working on Pt III of the tut!

Dimitree
December 19th, 2007, 07:54 AM
In buttonPressed of the player class



function ButtonPressed() {
if(FireTime == 0) {
var p:Projectile = new Projectile(oList, "Bullet", null);
p.isEnemy = false;
p.vely = -8;
p._x = _x;
p._y = _y;
p.category = this.category;
p.owner = this;
FireTime = 5;
}
}
Where I set p.vely = -8, just replace that with your new velocity (i.e. p.velx = 8)

The velx/vely are the velocities for game objects.

Bear in mind this code might have additions as I'm currently working on Pt III of the tut!
Thanks for your help! :pleased:

Terraneer
July 16th, 2008, 08:14 AM
I also had a problem making adjustments to the BaseObject class :(

Here is the code:


class BaseObject {
// Ok so here are some useful vars
var parent:BaseObject = null; // Holds information on the 'parent' object - this allows you to create heirarchys of objects and means you can have objects which are 'attached' to others. Useful for multipart objects like tanks with turrets etc
var oList:ObjectList = null; // This is a reference to an instance of a class we are going to create next - the ObjectList. This will be the class responsible for handling our list of objects
var clip:MovieClip = null; // The movieclip to use for display purposes - you could use bitmap rendering, but for the sake of simplicity I'll use this for the tut
var _x:Number = 0; // x/y position of the object
var _y:Number = 0;
var velx:Number = 0; // Velocity of object
var vely:Number = 0;
// variables
var anims:Array; // An array of animation objects
var currentFrame:Number; // Holds the index of the current animation frame
var currentDelay:Number; // Holds the amount of time left before we get the next frame

// Initialise the object
function BaseObject(_oList:ObjectList, clipName:String, _parent:BaseObject) {
anims = new Array(); // to initialise the anims array
oList = _oList;
parent = _parent;
SetMovie(clipName);
oList.push(this); // Add this to the object list
}

// Attaches the movieclip which will represent this object - attaches to _root if parent is not specified
function SetMovie(clipName:String) {
if(parent == null) {
clip = oList.root.attachMovie(clipName, clipName + oList.root.getNextHighestDepth(), oList.root.getNextHighestDepth());
} else {
clip = parent.clip.attachMovie(clipName, clipName + parent.clip.getNextHighestDepth(), parent.clip.getNextHighestDepth());
}
clip.stop(); // Stop the animations from playing
Render(); // Call render to set the MC to the correct place/size/etc on screen - should cut any flickering
}

// Behaviour function, does nothing on base
function Behaviour() {
// No behaviour for the base
}

// Physics, set the x/y position += the velocity
function Physics() {
_x += velx;
_y += vely;
// Add this code to the Physics() function
Animate();
}

// Put the MC in the correct place on screen
function Render() {
clip._x = _x;
clip._y = _y;
}

// Function to remove this object from the game
function Remove() {
oList.RemoveObject(this);
}

// Clean up, remove movie clips etc
function CleanUp() {
clip.removeMovieClip();
}
}

// Add the new 'animate' function - this function just uses the animation class to find out which

// frame we should be displaying from the movieclip
function Animate() {
// Check if we need to get the next frame, if not just decrement the delay
if(currentAnim != null) {
if (currentDelay == 0) {
currentFrame = currentAnim.getNextFrame(currentFrame);
currentDelay = currentAnim.getDelay(currentFrame);
clip.gotoAndStop(currentAnim.getFrame(currentFrame ));
trace(currentAnim.getFrame(currentFrame));

// If the delay is negative then quit this animation
if(currentDelay < 0) {
StopAnimation();
}
} else {
currentDelay -= 1;
}
}
}
// Add this function - it just stops the animation thats currently playing
function StopAnimation() {
currentFrame = 0;
currentDelay = 0;
currentAnim = null;
}
// Add this setanimation function
function SetAnimation(searchName:String) {
// Find animation by name in anim list does nothing if it can't find one
var i:Number;

for(i = 0; i < anims.length; i++) {
if (anims[i].animName == searchName) {
currentAnim = anims[i];
}
}
currentDelay = 0;
currentFrame = 0;
}
// Add this randomanimation function
function RandomAnimation(searchTag:String) {
// Find animation by tag in anim list and choose a random one
var i:Number = 0;
var targetAnims:Array;
targetAnims = new Array();
for(i = 0; i < anims.length; i++) {
if (anims[i].animTag == searchTag) {
targetAnims.push(anims[i]);
}
}
currentAnim = targetAnims[random(targetAnims.length)];
currentDelay = 0;
currentFrame = 0;
}


Error Description:
BaseObject.as, Line 120, 105, 92, 86 - ActionScript 2.0 class scripts may only define class or interface constructs.


Any help is appreciated! Thanks in advance.

Charleh
July 21st, 2008, 05:39 AM
I think you are missing some code near



// Clean up, remove movie clips etc
function CleanUp() {
clip.removeMovieClip();
}
}


Looks like you have an extra bracket } there which you need to lose!

Should work after that!

Terraneer
July 23rd, 2008, 02:04 AM
I think you are missing some code near



// Clean up, remove movie clips etc
function CleanUp() {
clip.removeMovieClip();
}
}


Looks like you have an extra bracket } there which you need to lose!

Should work after that!

Ok now I am getting a 'There is no property/method with the name 'currentAnim'.' error message on lines 70, 72, 73, 74, 75, 90, 99, 116, pretty much anything involved with currentAnim. Here is the BaseObject.as code:



class BaseObject {
// Ok so here are some useful vars
var parent:BaseObject = null; // Holds information on the 'parent' object - this allows you to create heirarchys of objects and means you can have objects which are 'attached' to others. Useful for multipart objects like tanks with turrets etc
var oList:ObjectList = null; // This is a reference to an instance of a class we are going to create next - the ObjectList. This will be the class responsible for handling our list of objects
var clip:MovieClip = null; // The movieclip to use for display purposes - you could use bitmap rendering, but for the sake of simplicity I'll use this for the tut
var _x:Number = 0; // x/y position of the object
var _y:Number = 0;
var velx:Number = 0; // Velocity of object
var vely:Number = 0;
// variables
var anims:Array; // An array of animation objects
var currentFrame:Number; // Holds the index of the current animation frame
var currentDelay:Number; // Holds the amount of time left before we get the next frame

// Initialise the object
function BaseObject(_oList:ObjectList, clipName:String, _parent:BaseObject) {
anims = new Array(); // to initialise the anims array
oList = _oList;
parent = _parent;
SetMovie(clipName);
oList.push(this); // Add this to the object list
}

// Attaches the movieclip which will represent this object - attaches to _root if parent is not specified
function SetMovie(clipName:String) {
if(parent == null) {
clip = oList.root.attachMovie(clipName, clipName + oList.root.getNextHighestDepth(), oList.root.getNextHighestDepth());
} else {
clip = parent.clip.attachMovie(clipName, clipName + parent.clip.getNextHighestDepth(), parent.clip.getNextHighestDepth());
}
clip.stop(); // Stop the animations from playing
Render(); // Call render to set the MC to the correct place/size/etc on screen - should cut any flickering
}
// Behaviour function, does nothing on base
function Behaviour() {
// No behaviour for the base
}

// Physics, set the x/y position += the velocity
function Physics() {
_x += velx;
_y += vely;
// Add this code to the Physics() function
Animate();
}

// Put the MC in the correct place on screen
function Render() {
clip._x = _x;
clip._y = _y;
}

// Function to remove this object from the game
function Remove() {
oList.RemoveObject(this);
}

// Clean up, remove movie clips etc
function CleanUp() {
clip.removeMovieClip();
}

// Add the new 'animate' function - this function just uses the animation class to find out which

// frame we should be displaying from the movieclip
function Animate() {
// Check if we need to get the next frame, if not just decrement the delay
if(currentAnim != null) {
if (currentDelay == 0) {
currentFrame = currentAnim.getNextFrame(currentFrame);
currentDelay = currentAnim.getDelay(currentFrame);
clip.gotoAndStop(currentAnim.getFrame(currentFrame ));
trace(currentAnim.getFrame(currentFrame));

// If the delay is negative then quit this animation
if(currentDelay < 0) {
StopAnimation();
}
} else {
currentDelay -= 1;
}
}
}
// Add this function - it just stops the animation thats currently playing
function StopAnimation() {
currentFrame = 0;
currentDelay = 0;
currentAnim = null;
}
// Add this setanimation function
function SetAnimation(searchName:String) {
// Find animation by name in anim list does nothing if it can't find one
var i:Number;

for(i = 0; i < anims.length; i++) {
if (anims[i].animName == searchName) {
currentAnim = anims[i];
}
}
currentDelay = 0;
currentFrame = 0;
}
// Add this randomanimation function
function RandomAnimation(searchTag:String) {
// Find animation by tag in anim list and choose a random one
var i:Number = 0;
var targetAnims:Array;
targetAnims = new Array();
for(i = 0; i < anims.length; i++) {
if (anims[i].animTag == searchTag) {
targetAnims.push(anims[i]);
}
}
currentAnim = targetAnims[random(targetAnims.length)];
currentDelay = 0;
currentFrame = 0;
}
}


Thx for your help in advance ;).

Charleh
July 23rd, 2008, 05:24 AM
Ok now I am getting a 'There is no property/method with the name 'currentAnim'.' error message on lines 70, 72, 73, 74, 75, 90, 99, 116, pretty much anything involved with currentAnim. Here is the BaseObject.as code:



class BaseObject {
// Ok so here are some useful vars
var parent:BaseObject = null; // Holds information on the 'parent' object - this allows you to create heirarchys of objects and means you can have objects which are 'attached' to others. Useful for multipart objects like tanks with turrets etc
var oList:ObjectList = null; // This is a reference to an instance of a class we are going to create next - the ObjectList. This will be the class responsible for handling our list of objects
var clip:MovieClip = null; // The movieclip to use for display purposes - you could use bitmap rendering, but for the sake of simplicity I'll use this for the tut
var _x:Number = 0; // x/y position of the object
var _y:Number = 0;
var velx:Number = 0; // Velocity of object
var vely:Number = 0;
// variables
var anims:Array; // An array of animation objects
var currentFrame:Number; // Holds the index of the current animation frame
var currentDelay:Number; // Holds the amount of time left before we get the next frame

// Initialise the object
function BaseObject(_oList:ObjectList, clipName:String, _parent:BaseObject) {
anims = new Array(); // to initialise the anims array
oList = _oList;
parent = _parent;
SetMovie(clipName);
oList.push(this); // Add this to the object list
}

// Attaches the movieclip which will represent this object - attaches to _root if parent is not specified
function SetMovie(clipName:String) {
if(parent == null) {
clip = oList.root.attachMovie(clipName, clipName + oList.root.getNextHighestDepth(), oList.root.getNextHighestDepth());
} else {
clip = parent.clip.attachMovie(clipName, clipName + parent.clip.getNextHighestDepth(), parent.clip.getNextHighestDepth());
}
clip.stop(); // Stop the animations from playing
Render(); // Call render to set the MC to the correct place/size/etc on screen - should cut any flickering
}
// Behaviour function, does nothing on base
function Behaviour() {
// No behaviour for the base
}

// Physics, set the x/y position += the velocity
function Physics() {
_x += velx;
_y += vely;
// Add this code to the Physics() function
Animate();
}

// Put the MC in the correct place on screen
function Render() {
clip._x = _x;
clip._y = _y;
}

// Function to remove this object from the game
function Remove() {
oList.RemoveObject(this);
}

// Clean up, remove movie clips etc
function CleanUp() {
clip.removeMovieClip();
}

// Add the new 'animate' function - this function just uses the animation class to find out which

// frame we should be displaying from the movieclip
function Animate() {
// Check if we need to get the next frame, if not just decrement the delay
if(currentAnim != null) {
if (currentDelay == 0) {
currentFrame = currentAnim.getNextFrame(currentFrame);
currentDelay = currentAnim.getDelay(currentFrame);
clip.gotoAndStop(currentAnim.getFrame(currentFrame ));
trace(currentAnim.getFrame(currentFrame));

// If the delay is negative then quit this animation
if(currentDelay < 0) {
StopAnimation();
}
} else {
currentDelay -= 1;
}
}
}
// Add this function - it just stops the animation thats currently playing
function StopAnimation() {
currentFrame = 0;
currentDelay = 0;
currentAnim = null;
}
// Add this setanimation function
function SetAnimation(searchName:String) {
// Find animation by name in anim list does nothing if it can't find one
var i:Number;

for(i = 0; i < anims.length; i++) {
if (anims[i].animName == searchName) {
currentAnim = anims[i];
}
}
currentDelay = 0;
currentFrame = 0;
}
// Add this randomanimation function
function RandomAnimation(searchTag:String) {
// Find animation by tag in anim list and choose a random one
var i:Number = 0;
var targetAnims:Array;
targetAnims = new Array();
for(i = 0; i < anims.length; i++) {
if (anims[i].animTag == searchTag) {
targetAnims.push(anims[i]);
}
}
currentAnim = targetAnims[random(targetAnims.length)];
currentDelay = 0;
currentFrame = 0;
}
}


Thx for your help in advance ;).

I think you may have missed putting some code in the BaseObject class - though I'm not sure, what I'd do is download the complete source that I posted and compare the source with your class.

The point of the tutorial is to let you learn how to do it manually by typing the code, but of course the full source is there if you get stuck or miss something. You need to understand the reason behind the error - basically currentAnim doesn't exist in the scope you are trying to access it in which means you probably havent added it to the correct class or added it at all