Talking about Flash.
In all tutorials covered until now, we have always seen how to determine collisions between a single object and the rest of the objects in the stage, or part of them.
No matter if it was performed using hit test or trigonometry, the point is that in every moment of the game we knew all the objects that could be involved in a collision detection.
For example, collision between one ball and the state, or one bullet and enemies, and so on.
Sometimes we have to manage multiple collision detection.
Try to imagine a shoot’em up game, where several projectiles are fired against enemy ships, or a breakout/arkanoid clone where a power up multiplies the number of ball in the stage, or a pool game where every ball can hit every other ball or the table bounds.
Since we do not know how many objects there are in the stage, we need a routine to scan all objects and determine if they collide among each others.
There are two types of multiple collision: the first is when we have two (or more) separate types of objects that can collide. For example, in a shoot’em up we know we do not care about collisions among enemy ships themselves while we care about collisions between enemy ships and bullets.
In the following example we have two classes of objects: the evil red squares and the good blue squares. When a red square hit a blue square, they both die. We won’t care about collisions among squares of the same color.
In the first step, I’ll put on the stage 20 blue squares moving horizontally and 20 red squares moving vertically.
To do this, I created a blue square object linkaged as “blue” and a red square objects linkaged as “red”
for (x=1; x<=20; x++) {
blue = _root.attachMovie("blue", "blue_"+_root.getNextHighestDepth(), _root.getNextHighestDepth(), {_x:Math.random()*500, _y:Math.random()*350});
blue.speed = 2+Math.random()*3;
blue.onEnterFrame = function() {
this._x += this.speed;
if (this._x>500) {
this._x -= 500;
}
};
}
for (x=1; x<=20; x++) {
red = _root.attachMovie("red", "red_"+_root.getNextHighestDepth(), _root.getNextHighestDepth(), {_x:Math.random()*500, _y:Math.random()*350});
red.speed = 2+Math.random()*3;
red.onEnterFrame = function() {
this._y -= this.speed;
if (this._y<0) {
this._y += 350;
}
};
}
Line 1: Loop to be executed 20 times
Line 2: Attaching the blue movieclip at the next highest depth and placing in a random place of thes tage
Line 3: Giving the blue movieclip a random speed
Line 4: Beginning of the function to be executed at every frame
Line 5: Moving the blue square according to its speed
Lines 6-8: If the blue square reaches the right end of the stage, then have it reappearing from the left side
Lines 11-20: Same routine with red squares execpt they are moving vertically
Now we have a bunch of boxes moving.
What we want is to check if any blue box hits any red box
var blue_array = new Array();
for (x=1; x<=20; x++) {
blue_array.push("blue_"+_root.getNextHighestDepth());
blue = _root.attachMovie("blue", "blue_"+_root.getNextHighestDepth(), _root.getNextHighestDepth(), {_x:Math.random()*500, _y:Math.random()*350});
blue.speed = 2+Math.random()*3;
blue.onEnterFrame = function() {
this._x += this.speed;
if (this._x>500) {
this._x -= 500;
}
};
}
for (x=1; x<=20; x++) {
red = _root.attachMovie("red", "red_"+_root.getNextHighestDepth(), _root.getNextHighestDepth(), {_x:Math.random()*500, _y:Math.random()*350});
red.speed = 2+Math.random()*3;
red.onEnterFrame = function() {
this._y -= this.speed;
if (this._y<0) {
this._y += 350;
}
for (x in blue_array) {
if (this.hitTest(_root[blue_array[x]])) {
_root[blue_array[x]].removeMovieClip();
this.removeMovieClip();
blue_array.splice(x, 1);
}
}
};
}
Line 1: Creation of an array called blue_array that will contain blue boxes instances.
Line 3: Pushing in the array the name of the box we are about to create. It's very important to place this line before the creation line (line 4) or the getNextHighestDepth() function will return the (obviously) next highest depth. I mean that if you are about to create your first box the next highest depth is n, if you already created it then you next highest depth will be n+1. The push method adds one or more elements to the end of an array and returns the array's new length.
Line 21: loop that scans all elements in the blue_array array
Line 22: checking if a hit test between the red square and the x-th element in the blue_array (the x-th blue square) happens.
Lines 23-25: if positive, removing the red square movieclip, the blue square movieclip and the element from the blue_array array. The splice method removes n elements (in our case 1) from the m-th position (in our case x) in the array.
You may need to refresh the page to see the code in action
Being the "easy" case, you may notice there are lots of collision detections:in the initial case there are 20 blue squares that may collide with 20 red squares. That is as much as 400 hit test performed on every frame.
In a 30fps shooter, this means 12000 hit tests per second.
Think about it when you are going to make a game.
The "hard" case happens when any movieclip may hit any other movieclip, for example in a pool game, or in this case
var blue_array = new Array();
for (x=1; x<=40; x++) {
blue = _root.attachMovie("blue", "blue_"+_root.getNextHighestDepth(), _root.getNextHighestDepth(), {_x:Math.random()*500, _y:Math.random()*350});
blue.xspeed = 2+Math.random()*3;
blue.yspeed = 2+Math.random()*3;
blue.onEnterFrame = function() {
this._x += this.xspeed;
this._y -= this.yspeed;
if (this._x>500) {
this._x -= 500;
}
if (this._y<0) {
this._y += 350;
}
};
}
This is the same case as the one with blue and red boxes, execpt there are only blue boxes.
Let's examine now the collision engine:
var blue_array = new Array();
for (x=1; x<=40; x++) {
blue_array.push("blue_"+_root.getNextHighestDepth());
blue = _root.attachMovie("blue", "blue_"+_root.getNextHighestDepth(), _root.getNextHighestDepth(), {_x:Math.random()*500, _y:Math.random()*350});
blue.xspeed = 2+Math.random()*3;
blue.yspeed = 2+Math.random()*3;
blue.onEnterFrame = function() {
this._x += this.xspeed;
this._y -= this.yspeed;
if (this._x>500) {
this._x -= 500;
}
if (this._y<0) {
this._y += 350;
}
for (x in blue_array) {
if (this != _root[blue_array[x]]) {
if (this.hitTest(_root[blue_array[x]])) {
_root[blue_array[x]].removeMovieClip();
this.removeMovieClip();
blue_array.splice(x, 1);
}
}
}
};
}
Line 17: In the loop cycling through the array, before performing the hit test I must be sure I am not testing the hit of a movie with itself.
The remaining code is the same as for blue/red boxes.
You may need to refresh the page to see the code in action
Seems to work well but... it doesn't.
Try to imagine the first movieclip testing the hit with the second one, the third, fourth and so on.
Now we have the second movieclip testing the hit with the first one, the third, fourth, and so on.
As you can see, I checked the hit between the first and the second movieclip two times: one when I check between the 1st and the 2nd and one when I check between the 2nd and the 1st.
What a waste of time!!
To prevent this, we need this fix:
var blue_array = new Array();
for (x=1; x<=40; x++) {
blue_array.push("blue_"+_root.getNextHighestDepth());
blue = _root.attachMovie("blue", "blue_"+_root.getNextHighestDepth(), _root.getNextHighestDepth(), {_x:Math.random()*500, _y:Math.random()*350});
blue.xspeed = 2+Math.random()*3;
blue.yspeed = 2+Math.random()*3;
blue.onEnterFrame = function() {
this._x += this.xspeed;
this._y -= this.yspeed;
if (this._x>500) {
this._x -= 500;
}
if (this._y<0) {
this._y += 350;
}
for (x in blue_array) {
if (this.getDepth()>_root[blue_array[x]].getDepth()) {
if (this.hitTest(_root[blue_array[x]])) {
_root[blue_array[x]].removeMovieClip();
this.removeMovieClip();
blue_array.splice(x, 1);
}
}
}
};
}
Now line 17 performs the test only if the target movieclip has a depth lower than its own depth. In this case there is no test for the 1st movieclip, the 2nd performs the test only with the 1st, the 3rd with the 2nd and the 1st and so on.
You may need to refresh the page to see the code in action
In the previous case to perform all tests in a frame I needed 40*39 = 1560 tests
In this one I need only 39+38+37...+2+1 = 780 tests
And this is the right way.
That's all you need to know about multiple collision detection, now we can start planning a good shooter game.
Download the source codes and give me feedback.
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.