Creation of another casual on a Flash platform with physics. Part II

  • Tutorial
Hello to the Habra community.

Relatively recently, quite a long time ago I wrote an article about creating another casual on a Flash platform with physics, promised a second article, welcome.
In this article, I will teach you how to draw the world and talk about sensors. The rest is under the cut.

What can be done from these two lessons can be seen here (music cannot be turned off, but you can remove the sound in the system) .

Once again, remember what happened in the last " lesson ".

Graphics


Now you need to steal to draw future graphics in the game, but because This lesson is primarily about programming games, you can take advantage of what I did:

Texture 1:


Texture 2:


Here are two BMP textures, 474x474 pixels.
They will serve as the basis for our world.

Texture 2 is a static texture, it will be presented in the form of Sprite and will always be drawn with the rotation angle we need.

Texture 1 is a texture for drawing the world, we will fill it with the rectangles of our world using beginBitmapFill and translate matrix. In other words, what is not flooded is freedom of movement and empty space.

Having designed it a little beautifully in accordance with the setting we need, we get something like:



We give a shell-picture (sprite) to our hero ball, here I also suggest taking my schedule:


And so that it would not be really boring to go through the most boring mazes, we will make obstacles: spikes, fire, lasers. But since the volume of the article should be adequate, consider only the spikes, but create the basis for everything else.

Thorn texture:


We create the necessary links and our library looks like this: The


graphics are ready.

Sensors



Sensors are ordinary objects (Static Body, Dynamic Body), except that they have the isSensor flag. What does this checkbox do? Everything is very simple, such an object “feels” a collision, however, for an object without the isSensor flag, nothing will happen. A solid object - it simply passes through the sensor, triggering a collision event in ContactListener.

ContactListener is a collision listener in Box2D. For example, object A and B collide in the world, the Add (point: b2ContactPoint) function is called in ContactListener, point contains: shapefiles that are involved in the collision; collision point in the global coordinate system; colliding body. In other words, these events can be processed.

- Sensors, listeners, nothing is clear, why is this?
Then, that in the collision of our hero with a spike - it is logical to cause the death of the hero and restart the level.

From theory to business, we proceed to programming



Learning to draw in the world


We change the size of the project from 500x500 to 600x580.
Change the position of drawing the debug sprite to:

sprite.x = 300; // половина 600
sprite.y = 290; // половина 580


All honestly, all centered.

Add new variables:

public var game:Sprite; // контейнер-спрайт для мира
public var level_sprite:Sprite = new Sprite(); // Процедурно-cгенерированная текстура мира
public var viewport:Sprite; // контейнер-спрайт для мира (героя, шипов)


In the main class constructor, initialize the game and viewport:

game = new sprite_game();
game.x = 300; // центрируем
game.y = 290; // центрируем
game.cacheAsBitmap = true; 
addChild(game);
viewport = new Sprite();
viewport.x = 300;
viewport.y = 290;
addChild(viewport);


Flash has a special option “cache as bitmap” that can be set for individual clips - this means that the clip is stored somewhere in the memory as a bitmap image and is no longer converted as a vector image. My experiments have shown that this gives a performance boost, but for complete happiness it is clearly not enough.

Add the rotation of the “gear” to enterFrameListener, in the same place where we rotate the sprite:

game.rotation = sprite.rotation = rotator;


We start, we see already something more or less pleasing to the eye than just a rotating green garbage picture.

Give the ball a sprite. We look for the createHero function and add it somewhere at the end:

var sprite:Sprite = new sprites_hero();
sprite.width = sprite.height = 16;		
viewport.addChild(sprite);
hero.SetSprite(sprite); // о том, что такое SetSprite я расскажу позже.
// задаем позицию спрайта такую же, как и у математики движка, умноженного на 30 (коф. преобразования)			
hero.GetSprite().x = hero.GetPosition().x * 30; 
hero.GetSprite().y = hero.GetPosition().y * 30;
// вращение			
hero.GetSprite().rotation = hero.GetAngle() * 180 / Math.PI;


body.SetSprite - missing from the original box2D. In other words, this is an analogue of GetUserData. But GetUserData is used for the type of object ("hero", "box"), so I added SetSprite to the engine. If you write on the original Box2D - do not worry, use dynamic objects, for example: body.SetUserData ({type: "hero", sprite: sprite});

We synchronize the position of the display object with mathematical calculations every frame, go to the enterFrameListener function, and specifically at the moment where we update the hero’s gravity, add:

body.GetSprite().x = body.GetPosition().x * 30;
body.GetSprite().y = body.GetPosition().y * 30;	
body.GetSprite().rotation = body.GetAngle() * 180 / Math.PI;


We remove Alpha from the sprite debug so that we can distinguish between what is happening:

dbgDraw.m_alpha = 0.5;
dbgDraw.m_fillAlpha = 0.1;


We start, twist, we see that everything works:



It remains now to get rid of DebugDraw in order to completely switch to our graphics, but it prevents us from doing this, that our walls are not drawn, we will fix this.
Create variables for the translate matrix and texture:

public var matrix_texture:Matrix;
public var textureal:BitmapData;


Initialize them in the constructor:

textureal = new texture_gear();
matrix_texture = new Matrix();
matrix_texture.translate(237, 237);
// заодно добавим наш level_sprite
viewport.addChild(level_sprite);


Go to the CreateStaticRect function and add something like:

level_sprite.graphics.beginBitmapFill(textureal, matrix_texture);
level_sprite.graphics.drawRect(x - 150, y - 150, w, h);
level_sprite.graphics.endFill();


And for clarity, add another StaticRect (in the constructor):

CreateStaticRect(150, 140, 30, 30);


For a while, disable DebugDraw by commenting the line:

// world.SetDebugDraw(dbgDraw);


We compile, admire the traced walls:



We learned to draw our world, create obstacles.
We create an abstract class, our obstacles will be inherited from it, Obstacle listing :

package  
{
	import Box2D.Dynamics.b2World;
	import flash.display.Sprite;
	/**
	 * ...
	 * @author forhaxed
	 */
	public class Obstacle extends Sprite
	{
		public var active:Boolean = true; // активна ли в текущей момент
		public var angle:int = 0; // угол поворота
		public var viewport:Sprite; // ссылка на viewport
		public var world:b2World; // ссылка на мир
		// 0 - LEFT, 1 - UP, 2 - RIGHT - 3 - DOWN
		public function Obstacle(_viewport:Sprite, _world:b2World, x:Number, y:Number, angle:int = 0) 
		{
			viewport = _viewport;
			world = _world;
		}
	}
}


And create a child of this class, Spike (actually our spike), listing:

package  
{
	import Box2D.Collision.Shapes.b2CircleDef;
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2BodyDef;
	import Box2D.Dynamics.b2World;
	import flash.display.Sprite;
	/**
	 * ...
	 * @author forhaxed
	 */
	public class Spike extends Obstacle
	{
		public function Spike(_viewport:Sprite, _world:b2World, x:Number, y:Number, angle:int = 0) 
		{
			super(_viewport, _world, x, y, angle); // конструктор родителя
			switch(angle) // вращаем как нам надо
			{
				case 0: this.rotation = 90; break;
				case 1: this.rotation = 180; break;
				case 2: this.rotation = 270; break;
				case 3: this.rotation = 0; break;
			}
			var body:b2Body;
			var bodyDef:b2BodyDef;
			var circleDef:b2CircleDef;
			var sprite:Sprite;
			this.x = x;
			this.y = y;
			viewport.addChild(this);
			/* OBSTACLE */
			x = x / 30;
			y = y / 30;
			var r:Number = 6 / 30;
			bodyDef = new b2BodyDef();
			bodyDef.position.Set(x, y);
			circleDef = new b2CircleDef();
			circleDef.radius = r;
			circleDef.density = 1;
			circleDef.friction = 1;
			circleDef.isSensor = true; // устанавливаем isSensor в true, чтобы этот объект только следил за миром
			circleDef.restitution = 0.2;
			body = world.CreateBody(bodyDef);
			body.SetUserData("spike"); // тип ставим как spike
			body.SetSprite(this); // ставим спрайт
			body.CreateShape(circleDef);
			body.SetMassFromShapes();
			addChild(new saw()); 
		}
	}
}


Add three spikes to our world, go to the main class constructor and write:

new Spike(viewport, world, 37, 5, 0);
new Spike(viewport, world, 15, 27, 1);
new Spike(viewport, world, 15, -17, 3);


We compile, admire the spikes, but they are not dangerous yet, so we will make them dangerous.



We create a new GearMazeContact class , which is inherited from b2ContactListener , we will redefine its methods (yes, override is very relevant in AS3 ). Listing:

package 
{
	import Box2D.Dynamics.*;
	import Box2D.Collision.*;
	import Box2D.Collision.Shapes.*;
	import Box2D.Dynamics.Joints.*;
	import Box2D.Dynamics.Contacts.*;
	import Box2D.Common.*;
	import Box2D.Common.Math.*;
	import flash.display.MovieClip;
	public class GearMazeContact extends b2ContactListener
	{
		public override function Add(point:b2ContactPoint):void
		{
			var p1:b2Body = point.shape1.GetBody();
			var p2:b2Body = point.shape2.GetBody();
			if(p1.GetUserData()=="hero" && p2.GetUserData()=="spike")
			{
				if ((p2.GetSprite() as Obstacle).active)
				{
					trace("Ha-ha!");
					p1.SetUserData("hero_dead"); // меняем тип объекта на hero_dead, для его удаления
					/* Внимание, в процессинге просчета физики ничего с объектами не сделать, мир заблокирован от нас, поэтому мы просто удалим его, когда мир разблокируется */
				}
			}
		}
	}
}


We connect our listener to the world, in the same place where we create the world and set DebugDraw:

var GearListener:GearMazeContact = new GearMazeContact();
world.SetContactListener(GearListener);


And add the functionality that will remove our ball in the case of death, in enumerating an array with bodies (enterFrameListener):

if (body.GetUserData() == "hero_dead")
				{
					if (body.GetSprite() is Sprite) viewport.removeChild(body.GetSprite());
					world.DestroyBody(body);
				}


Here you can continue to saw the gameplay and do all sorts of goodies: lasers, portals, saws, girls .

If you want to make a toy out of this, then you can make a simple algorithm for the procedural generation of labyrinths with Obstacle.

By tradition, I attach all the code for the article, a link to a demo and a finished toy made in these steps.

By the way, there will not be a finished game, alas :-(
So far this is some kind of alpha with 18 levels. Code refactoring kills me, I'm a creative person: create, create and create again. Money doesn’t interest me :-)

Links: source codes | demo | finished game

P.S. thanks a lot to the good man datacompboy when I wrote the first part of the article -he gave me hosting with a domain, so that you did not download demos, games and sources from freeware sites.
PSS you can still write to me about the game dev in as3, I will be happy to answer.

Also popular now: