Managing multiple collision detection with 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.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

215 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stairs
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers