-
Notifications
You must be signed in to change notification settings - Fork 0
3. Multiple Sprites (Groups)
The code we’ve written so far is absolutely fine if we only have a few sprites in our game but as our games become larger and more complex it will quickly become a huge coding task and could end up looking something like this:
window.onload = function(){ CVSetCanvas(document.getElementById('canvas1')); var myImage = new CVSurface({ width: 80, height:80 }); myImage.circle(); mySprite1 = new MyBall({ image: myImage }); mySprite2 = new MyBall({ image: myImage }); // ... repeat for 3 - 49 mySprite50 = new MyBall({ image: myImage }); CVSetLoopFunction(function(){ CVClearCanvas(); mySprite1.update(); mySprite2.update(); // ... repeat for 3 - 49 mySprite50.update(); mySprite1.draw(); mySprite2.draw(); // ... repeat for 3 - 49 mySprite50.draw(); }); CVInit(); CVMainloop(); }
As you can see, it’s gonna be a bit of a pain in the ass. It is also extremely bad programming practice. The more experienced programmers amongst you will probably have already figured out that this can be simplified with arrays and for loops and you would be right. However, we’re way ahead of you. Introducing CVSpriteGroup
. This is a built-in class that comes free of charge. You’re welcome! Let’s re-write the last bit of code to use CVSpriteGroup
and after we’ll go over the changes.
window.onload = function(){ CVSetCanvas(document.getElementById('canvas1')); var myImage = new CVSurface({ width: 80, height:80 }); myImage.circle(); mySpriteGroup = new CVSpriteGroup(); for(var i = 0; i < 50; i++){ mySpriteGroup.add( new MyBall({ image: myImage }) ); } CVSetLoopFunction(function(){ CVClearCanvas(); mySpriteGroup.update(); mySpriteGroup.draw(); }); CVInit(); CVMainloop(); }
- To start off we create an instance of the built-in
CVSpriteGroup
class and named itmySpriteGroup
. - Next we create a for loop that will iterate 50 times. Each time the loop executes we call the
add()
method of ourmySpriteGroup
object. There are two options for this. You can create an instance of your sprite and assign it to a variable then pass the variable name in as an argument foradd()
. Or you can do it the way we have where you assign the object directly into a slot in theCVSpriteGroup
object. Either way is perfectly acceptable and the decision really depends on how you need to use the sprites. - The last thing we did was to remove any sprites from our loop function and replaced them with our
mySpriteGroup
object callingupdate()
anddraw()
respectively. These methods callupdate()
anddraw()
on all sprites contained within theCVSpriteGroup
. Check the documentation for moreCVSpriteGroup
methods.
If you ran the code above you will undoubtedly have noticed a flaw in our code. There still only appears to be one sprite. Well this is because they are all being drawn in exactly the same place. Unacceptable! Lets fix it at once. Replace your for loop with this:
for(var i = 0; i < 50; i++){ mySpriteGroup.add( new MyBall({ image: myImage, dx: i/10, dy: i/10 }) ); }
Before running the script you will also need to change MyBall
’s initialize()
method. Go ahead and remove this.dx = 1;
and this.dy = 1;
and now your script is ready to be run. You should new see a load of ball bouncing around your screen.
You may have noticed at this point that at no point in our code did we declare this.dx
and this.dy
yet in our calcPosition()
method we clearly use them. Yet our code if working fine and we have no errors…”what’s going on here?”, I hear - well, sense - you ask. This introduces a new concept to object initialization. Any attribute you declare during the creation of an object will become an attribute of that object. Here’s how it works:
mySprite = new MyBall({ image: myImage, // translates to this.image within initialize() dx: i/10, // translates to this.dx within initialize() dy: i/10 // translates to this.dy within initialize() });
So any key: value
within the constructor will become this.key = value
and can be used as an attribute from that point forward. I see a lot of the more experienced programmers cringing when they see that and it wouldn’t be completely without reason. But I have a few guidelines to put in place to prevent you encountering problems.
- Be sure to declare any attributes you intend to use within
initialize()
with default values. - Never declare anything new when creating objects.
- Always write your
initialize()
method in this format:
var MyClass = new Class({ Extends: CVSprite, initialize: function(args){ // note 'args' (call this whatever you like) this.attr1 = defaultVal; this.attr2 = defaultVal; // ... etc this.parent(args); // note that this is at the end and passes in 'args' }, });
- Always place at least one argument
args
, which represents the JSON object which is passed in, within the parentesis. - Always place
this.parent(args)
at the end ofinitialize()
remembering to pass in args. - This only applies for classes that extend
CVSprite
and only for theinitialize()
method.
These are only guidelines remember and if you know what you are doing you can bend the rules slightly but just be aware that you code may break if you do.
Now that we’ve gone over all of the basics lets just change some things in our code in order to demonstrate some additional functionality of CanVerse2D.
Before continuing, be sure to place some default values in your initialize()
method if you already haven’t:
initialize: function(args){ this.dx = 1; this.dy = 1; this.parent(args); },
Now lets add some color. Find this line of code myImage.circle();
and replace it with this:
myImage.circle({ fill: 'red', outline: 'blue', outlineWidth: 5, radius: 20 });
I won’t explain what all of the arguments do as I believe the naming makes it obvious. If you are interested in seeing what arguments can be passed in and what they do refer to the documentation. Just note that they are passed in JSON format and if you try any other way you will get an error.
Now lets add some interactivity. There are many built-in method that come with CVSprite
all of which are give to you for free when you inherit from it. Lets make use of two of them, just to get our feet wet. Add this method to our MyBall
class:
mouseDownEvent: function(evt){ this.kill(); },
mouseDownEvent()
is a method that is automatically called when a mouse button is pressed when over the sprite. There are other mouse methods available so check the documentation for more. kill()
does almost exactly what it says on the tin. It actually only really works when used in conjunction with CVSpriteGroup
objects. What it does is removes the sprite from any CVSpriteGroup
that contains it. this means that it will no longer update or draw with the CVSpriteGroup
. If there is no other reference to the sprite then it will be collected by Javascript’s Garbage Collection. There is one more thing you need to do before you can access the CVMouse
objects functionality. Just before CVInit()
place this line of code:
CVMouseEnabled = true;
This is false
by default and needs to be true
in order to use the mouse in your game. If you now run the script you will be able to have heaps of fun chasing balls round on the screen trying to kill them with clicks.
The observant of you will notice a little problem with the rect
when the sprite calls checkBounds()
but we’ll fix that in the next lesson.
Here’s the complete code for you to see in all it’s glory:
var MyBall = new Class({ Extends: CVSprite, initialize: function(args){ this.dx = 0; this.dy = 0; this.parent(args); }, update: function(){ this.checkBounds(); this.calcPosition(); }, calcPosition: function(){ var pos = this.getPos(); this.setPos(pos.x + this.dx, pos.y + this.dy); }, checkBounds: function(){ if(this.rect.left() < 0 || this.rect.right() > CVCanvas.width){ this.dx *= -1; } if(this.rect.top() < 0 || this.rect.bottom() > CVCanvas.height){ this.dy *= -1; } }, mouseDownEvent: function(){ this.kill(); } }) window.onload = function(){ CVSetCanvas(document.getElementById('canvas1')); var myImage = new CVSurface({ width: 80, height:80 }); myImage.circle({ fill: 'red', outline: 'blue', outlineWidth: 5, radius: 20 }); mySpriteGroup = new CVSpriteGroup(); for(var i = 0; i < 50; i++){ mySpriteGroup.add( new MyBall({ image: myImage, dx: i/10, dy: i/10, }) ); } CVSetLoopFunction(function(){ CVClearCanvas(); mySpriteGroup.update(); mySpriteGroup.draw(); }); CVMouseEnabled = true; CVInit(); CVMainloop(); }