Create a Flash prototype of The Moops – Combos of Joy

Read all posts about "" game

Did you play The Moops – Combos of Joy?

It’s a cute physics game we can build a prototype in a few minutes (don’t worry, Plants Vs Zombies fans, as next step is about to come).

Let’s see game’s features:

* It’s a physics game so we are using Box2D to create it.

* The player fires a ball with the mouse. The direction can be decided according to mouse position, but power can’t. Player fires balls with the same, predefined speed.

* A series of shapes “fall” down from the top of the stage. Actually they don’t fall because there is no gravity in the game. Let’s say they move from top to bottom at a constant speed.

* When the ball hits a square, the square reacts according to physics, changing its direction and speed, but the ball does not exactly follow physics rules as its speed never changes.

Obviously there are more features in the original game but at the moment we’ll see these ones. Since it’s a long time we don’t deal with Box2D, I am using a line by line approach to explain the code.

This is the script:

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.utils.Timer;
	import flash.events.TimerEvent;
	import Box2D.Dynamics.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Common.Math.*;
	public class moops extends Sprite {
		private var world:b2World=new b2World(new b2Vec2(0,0),true);
		private var worldScale:int=30;
		private var timeCount:Timer=new Timer(500);
		public function moops():void {
			timeCount.start();
			debugDraw();
			addEventListener(Event.ENTER_FRAME, update);
			stage.addEventListener(MouseEvent.CLICK,onClick);
			timeCount.addEventListener(TimerEvent.TIMER, onTime);
		}
		private function onClick(e:MouseEvent):void {
			var angle:Number=Math.atan2(mouseX,mouseY-480)-Math.PI/2;
			addball(20*Math.cos(angle),-20*Math.sin(angle));
		}
		private function onTime(event:TimerEvent):void {
			addBox(300+Math.random()*200,-100,40);
		}
		private function addball(xVel:Number,yVel:Number):void {
			var ball:b2BodyDef= new b2BodyDef();
			ball.userData="ball";
			ball.type=b2Body.b2_dynamicBody;
			ball.position.Set(20/worldScale, 460/worldScale);
			var circle:b2CircleShape=new b2CircleShape(15/worldScale);
			var ballFixture:b2FixtureDef = new b2FixtureDef();
			ballFixture.shape=circle;
			ballFixture.friction=0;
			ballFixture.density=1;
			ballFixture.restitution=1;
			var ballBody:b2Body=world.CreateBody(ball);
			ballBody.CreateFixture(ballFixture);
			ballBody.SetLinearVelocity(new b2Vec2(xVel,yVel));
		}
		private function debugDraw():void {
			var debugDraw:b2DebugDraw = new b2DebugDraw();
			var debugSprite:Sprite = new Sprite();
			addChild(debugSprite);
			debugDraw.SetSprite(debugSprite);
			debugDraw.SetDrawScale(worldScale);
			debugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit);
			debugDraw.SetFillAlpha(0.5);
			world.SetDebugDraw(debugDraw);
		}
		private function addBox(xOrigin:Number,yOrigin:Number,size:Number):void {
			var box:b2BodyDef= new b2BodyDef();
			box.position.Set(xOrigin/worldScale,yOrigin/worldScale);
			box.type=b2Body.b2_dynamicBody;
			var square:b2PolygonShape = new b2PolygonShape();
			square.SetAsBox(size/2/worldScale, size/2/worldScale);
			var boxFixture:b2FixtureDef = new b2FixtureDef();
			boxFixture.shape=square;
			boxFixture.friction=0;
			boxFixture.density=4;
			boxFixture.restitution=1;
			var boxBody:b2Body=world.CreateBody(box);
			boxBody.CreateFixture(boxFixture);
			boxBody.SetLinearVelocity(new b2Vec2(0,5));
		}
		private function update(e:Event):void {
			world.Step(1/30,10,10);
			world.ClearForces();
			world.DrawDebugData();
			for (var currentBody:b2Body = world.GetBodyList(); currentBody; currentBody=currentBody.GetNext()) {
				if (currentBody.GetPosition().y*worldScale>600 || currentBody.GetPosition().y*worldScale<(100*-1)) {
					world.DestroyBody(currentBody);
				}
				if (currentBody.GetUserData()=="ball") {
					var velocity:b2Vec2=currentBody.GetLinearVelocity();
					if (velocity.Length()!=20) {
						var speedOffset:Number=20/velocity.Length();
						currentBody.SetLinearVelocity(new b2Vec2(velocity.x*speedOffset,velocity.y*speedOffset));
					}
				}
			}
		}
	}
}

Let's see how does it work:

import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.utils.Timer;
import flash.events.TimerEvent;

Imports the required Flash libraries. We are using the mouse to fire the ball and a timer to add new shapes.

import Box2D.Dynamics.*;
import Box2D.Collision.Shapes.*;
import Box2D.Common.Math.*;

Imports the required Box2D libraries.

private var world:b2World=new b2World(new b2Vec2(0,0),true);
private var worldScale:int=30;

word will contain the physics environment itself. The first argument of the b2World constructor represents the gravity, which in this game is disabled, as you can see from the (0,0) vector.

worldScale is the amount of pixels used to represent a meter. Refer to Understanding pixels and meters with Box2D for more information.

public function moops():void {
	timeCount.start();
	debugDraw();
	addEventListener(Event.ENTER_FRAME, update);
	stage.addEventListener(MouseEvent.CLICK,onClick);
	timeCount.addEventListener(TimerEvent.TIMER, onTime);
}

This is the main function. We start the timer (refer to Understanding AS3 timer class for more information), we setup all listeners and we call debugDraw function.

This is the function:

private function debugDraw():void {
	var debugDraw:b2DebugDraw = new b2DebugDraw();
	var debugSprite:Sprite = new Sprite();
	addChild(debugSprite);
	debugDraw.SetSprite(debugSprite);
	debugDraw.SetDrawScale(worldScale);
	debugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit);
	debugDraw.SetFillAlpha(0.5);
	world.SetDebugDraw(debugDraw);
}

Basically we are using the Box2D built in API to represent the world on the stage. Refer to Understanding Box2D debug draw for more information.

private function onTime(event:TimerEvent):void {
	addBox(300+Math.random()*200,-100,40);
}

This is the function triggered by the timer event. It simply calls addBox function. Its arguments represent the x and y origin of the box, and the size, in pixels.

private function addBox(xOrigin:Number,yOrigin:Number,size:Number):void {
	var box:b2BodyDef= new b2BodyDef();
	box.position.Set(xOrigin/worldScale,yOrigin/worldScale);
	box.type=b2Body.b2_dynamicBody;
	var square:b2PolygonShape = new b2PolygonShape();
	square.SetAsBox(size/2/worldScale, size/2/worldScale);
	var boxFixture:b2FixtureDef = new b2FixtureDef();
	boxFixture.shape=square;
	boxFixture.friction=0;
	boxFixture.density=4;
	boxFixture.restitution=1;
	var boxBody:b2Body=world.CreateBody(box);
	boxBody.CreateFixture(boxFixture);
	boxBody.SetLinearVelocity(new b2Vec2(0,5));
}

addBox function adds a box to the world. Its basics are explained at Box2D tutorial for the absolute beginners, but I want you to focus on the last line.

SetLinearVelocity method gives the box a linear velocity according to its vector argument.

private function onClick(e:MouseEvent):void {
	var angle:Number=Math.atan2(mouseX,mouseY-480)-Math.PI/2;
	addball(20*Math.cos(angle),-20*Math.sin(angle));
}

When the player clicks, we have to determine the angle of the mouse pointer relative to the bottom-left corner of the stage, to fire the ball.

From the angle, we can calculate the horizontal and vertical velocity of the ball.

Then we call addball function, which works in a similar way as addBox.

private function addball(xVel:Number,yVel:Number):void {
	var ball:b2BodyDef= new b2BodyDef();
	ball.userData="ball";
	ball.type=b2Body.b2_dynamicBody;
	ball.position.Set(20/worldScale, 460/worldScale);
	var circle:b2CircleShape=new b2CircleShape(15/worldScale);
	var ballFixture:b2FixtureDef = new b2FixtureDef();
	ballFixture.shape=circle;
	ballFixture.friction=0;
	ballFixture.density=1;
	ballFixture.restitution=1;
	var ballBody:b2Body=world.CreateBody(ball);
	ballBody.CreateFixture(ballFixture);
	ballBody.SetLinearVelocity(new b2Vec2(xVel,yVel));
}

Again, look how I set the linear velocity of the ball.

private function update(e:Event):void {
	world.Step(1/30,10,10);
	world.ClearForces();
	world.DrawDebugData();
	for (var currentBody:b2Body = world.GetBodyList(); currentBody; currentBody=currentBody.GetNext()) {
		if (currentBody.GetPosition().y*worldScale>600 || currentBody.GetPosition().y*worldScale<(100*-1)) {
			world.DestroyBody(currentBody);
		}
		if (currentBody.GetUserData()=="ball") {
			var velocity:b2Vec2=currentBody.GetLinearVelocity();
			if (velocity.Length()!=20) {
				var speedOffset:Number=20/velocity.Length();
				currentBody.SetLinearVelocity(new b2Vec2(velocity.x*speedOffset,velocity.y*speedOffset));
			}
		}
	}
}

update function is the core of the script as it handles the physics simulation at every frame.

Nothing new here, just refer to the Box2D tutorial for the absolute beginners for general concepts and to the basic Filler engine to keep the ball moving at a constant speed.

The rule is: at every frame, check ball's velocity with GetLinearVelocity method and if the length of the vector representing the velocity is different than 20, then adjust it.

And this is the result:

Click with the mouse on the stage to fire the ball.

Download the source code.

Next time, the same collision management you see in the original game.

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

214 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
// 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