
Game Scene 1: 3D Room with Away3D
da plane!
Moving back to 3D, I'm starting a series of tutorials on how to build a simple 3D scene for a possible game, where lots and lots of 2D sprites are used instead of 3D models. In the example below I use a plane object as my "hero" character and I texture it with the animation frames for that sprite.
Click here to see the movie (click to move the sprite)
This was done with Away3D, the alpha version of broomstick, and I might post a version with the beta one. Unfortunately the two versions are significantly different and code from one breaks with the other.
Then later I'll move the same code, or the same idea rather, and try to make it work with some new 3D engines out there (and some old ones too).
The trick to make 2D sprites work in 3D games is the quality of the art and animation, with lots of shading and foreshortening and movement. Then the other thing you should pay attention to is the camera placement. The camera must be within a "goldilock" area, not too far from the side and not too high so you can control the amount of distortion on the planes.
Of course, all these considerations can be eliminated if you want to go for a pop-out look in your game.
The Code
package
{
import Box2D.Collision.Shapes.b2PolygonShape;
import Box2D.Common.Math.b2Vec2;
import Box2D.Dynamics.b2Body;
import Box2D.Dynamics.b2BodyDef;
import Box2D.Dynamics.b2DebugDraw;
import Box2D.Dynamics.b2FixtureDef;
import Box2D.Dynamics.b2World;
import away3d.cameras.Camera3D;
import away3d.containers.ObjectContainer3D;
import away3d.core.base.Object3D;
import away3d.containers.View3D;
import away3d.lights.LightBase;
import away3d.lights.PointLight;
import away3d.materials.BitmapMaterial;
import away3d.primitives.Cube;
import away3d.primitives.Plane;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.*;
[SWF(width="800", height="400", frameRate = "40", backgroundColor="0x222222")]
public class Main extends Sprite {
public static const p2m:int = 30;
private static const MOVE_UP:int = 0;
private static const MOVE_DOWN:int = 1;
private static const MOVE_LEFT:int = 2;
private static const MOVE_RIGHT:int = 3;
private var _direction:int;
[Embed(source="../assets/frames.png")] private var SpriteSheet:Class;
//away3D stuff
private var _light:LightBase;
private var _view:View3D;
private var _camera:Camera3D;
private var _container:ObjectContainer3D;
private var _player:Plane;
private var _frame_front:Vector. = new Vector.;
private var _frame_back:Vector. = new Vector.;
private var _frame_side:Vector. = new Vector.;
//box2D stuff
private var _world:b2World;
private var _b2player:b2Body;
private var _boxPositions:Array = [new Point(600, 600),
new Point(200, 600), new Point(400,1000),
new Point(500,500), new Point(600,200), new Point(700,950)];
//I use a scale factor to reduce the size of the
//box2D simulation so it fits the screen
private var _scale:Number = 5;
private var _wallHeight:int = 400;
private var _roomSide:int = 1200;
private var _boxSide:int = 100;
private var _cubes:Vector. = new Vector.();
private var _boxes:Vector. = new Vector.();
private var _animate:Boolean = false;
private var _showBox2D:Boolean = false;
private var _show3D:Boolean = true;
private var _playerHeight:int = 256;
private var _player_rec:Rectangle = new Rectangle(600,1100,128,60);
private var _center:Point;
private var _speed:Number = 0.8;
private var _animationCnt:int = 0;
private var _frameIndex:int = 0;
public function Main() {
super();
createBox2DWorld();
create3DWorld();
_center = new Point(stage.stageWidth * 0.5, stage.stageHeight * 0.5);
addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown, false, 0, true);
stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp, false, 0, true);
}
protected function onMouseDown(event:MouseEvent):void {
_animate = true;
}
protected function onMouseUp(event:MouseEvent):void {
_animate = false;
_player.y = _playerHeight * 0.5;
_player.scaleX = 1;
_frameIndex = 0;
switch (_direction) {
case MOVE_UP:
_player.material = _frame_back[0];
break;
case MOVE_DOWN:
_player.material = _frame_front[0];
break;
case MOVE_LEFT:
_player.scaleX = -1;
_player.material = _frame_side[0];
break;
case MOVE_RIGHT:
_player.material = _frame_side[0];
break;
}
}
private function onLoop (event:Event):void {
_world.Step(1/p2m,10,10);
updatePlayer();
transformObject(_b2player, _player);
for (var i:int = 0; i < _boxes.length; i++) {
transformObject(_boxes[i], _cubes[i]);
}
if (_showBox2D) _world.DrawDebugData();
if (_show3D) {
_camera.z = _player.z + 180;
if (_camera.z > -_roomSide*0.4) _camera.z = -_roomSide*0.4;
_camera.x = _player.x;
if (_player.x > 250) {
_camera.x = 250;
}
if (_player.x < -250) {
_camera.x = -250;
}
_view.render();
}
}
private function updatePlayer ():void {
_b2player.SetLinearVelocity(new b2Vec2(0,0));
var playerVelocity:b2Vec2 = _b2player.GetLinearVelocity();
if (_animate) {
var p:Point = new Point(mouseX, mouseY);
var diffx:Number = p.x - _center.x;
var diffy:Number = p.y - _center.y;
var rad:Number = Math.atan2(diffy, diffx);
var angle:int = (180 * rad) / Math.PI;
if (angle < 360) angle += 360;
if (angle > 360) angle -= 360;
if (angle > 315 || angle < 45) {
playerVelocity.x += _speed;
_direction = MOVE_RIGHT;
} else if (angle >= 45 && angle <= 135) {
playerVelocity.y += _speed;
_direction = MOVE_DOWN;
} else if (angle > 135 && angle < 225) {
playerVelocity.x -= _speed;
_direction = MOVE_LEFT;
} else {
playerVelocity.y -= _speed;
_direction = MOVE_UP;
}
_animationCnt++;
if (_animationCnt > 4) {
_animationCnt = 0;
_frameIndex++;
if (_frameIndex == 4) _frameIndex = 1;
_player.scaleX = 1;
switch (_direction) {
case MOVE_UP:
_player.material = _frame_back[_frameIndex];
break;
case MOVE_DOWN:
_player.material = _frame_front[_frameIndex];
break;
case MOVE_LEFT:
_player.scaleX = -1;
_player.material = _frame_side[_frameIndex];
break;
case MOVE_RIGHT:
_player.material = _frame_side[_frameIndex];
break;
}
if (_player.y == _playerHeight * 0.5) {
_player.y = _playerHeight * 0.54;
} else {
_player.y = _playerHeight * 0.5;
}
}
}
}
private function transformObject (original:b2Body, threeD:Object3D):void {
if (!threeD || !original) return;
var position:b2Vec2 = original.GetPosition();
var angle:Number = original.GetAngle() * 180 / Math.PI ;
threeD.x = position.x * p2m * _scale - _roomSide *0.5;
threeD.z = -1 * position.y * p2m * _scale;
threeD.rotationY = angle;
}
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//Create 3D and Box2D worlds >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
private function create3DWorld ():void {
var source:BitmapData = new SpriteSheet().bitmapData;
var frame:BitmapData;
var frameMaterial:BitmapMaterial;
//the uv values for materials I want to repeat (tile) on the plane
var uvWall:int = 1;
var uvGround:int = 5;
_view = new View3D();
this.addChild(_view);
_container = new ObjectContainer3D();
_view.scene.addChild(_container);
_camera = new Camera3D();
_camera.y = 150;
_camera.z = -1000;
_camera.lookAt(new Vector3D(0,0,0));
_view.camera = _camera;
_light = new PointLight();
_light.castsShadows = false;
_light.specular = 0.2;
_light.x = 100;
_light.y = _wallHeight*0.1;
_light.z = -_roomSide*0.5;
_light.color = 0xFFFFFF;
//ground material
frame = new BitmapData(256, 256);
frame.copyPixels(source, new Rectangle(2, 2, 256, 256), new Point(0,0));
var groundMaterial:BitmapMaterial = new BitmapMaterial(frame);
groundMaterial.repeat = true;
groundMaterial.lights = [_light];
//wall material
frame = new BitmapData(256, 256);
frame.copyPixels(source, new Rectangle(2, 260, 256, 256), new Point(0,0));
var wallMaterial:BitmapMaterial = new BitmapMaterial(frame);
wallMaterial.repeat = true;
wallMaterial.lights = [_light];
_view.scene.addChild(_light);
//ground
var plane:Plane = new Plane (groundMaterial, _roomSide, _roomSide);
plane.rotationX = 90;
//set the material to repeat (multiply the uv values)
plane.geometry.scaleUV(uvGround);
plane.y = 0;
plane.z = -_roomSide*0.5;
_container.addChild(plane);
//back wall
plane = new Plane (wallMaterial, _roomSide, _wallHeight);
plane.geometry.scaleUV(uvWall);
plane.y = _wallHeight * 0.5;
plane.z = 0;
_container.addChild(plane);
//right wall
plane = new Plane (wallMaterial, _roomSide, _wallHeight);
plane.rotationY = 90;
plane.x = _roomSide*0.5;
plane.z = -_roomSide*0.5;
plane.y = _wallHeight * 0.5;
plane.geometry.scaleUV(uvWall);
_container.addChild(plane);
//left wall
plane = new Plane (wallMaterial, _roomSide, _wallHeight);
plane.rotationY = -90;
plane.x = -_roomSide*0.5;
plane.z = -_roomSide*0.5;
plane.y = _wallHeight * 0.5;
plane.geometry.scaleUV(uvWall);
_container.addChild(plane);
//ceiling
plane = new Plane (wallMaterial, _roomSide, _roomSide);
plane.rotationX = -90;
plane.y = _wallHeight;
plane.z = -_roomSide*0.5;
plane.geometry.scaleUV(uvGround*0.5);
_container.addChild(plane);
frame = new BitmapData(128, 128);
frame.copyPixels(source, new Rectangle(2, 776, 128, 128), new Point(0,0));
var crateMaterial:BitmapMaterial = new BitmapMaterial(frame);
//add boxes
var cube:Cube;
for (var i:int = 0; i < _boxPositions.length; i++) {
cube = new Cube(crateMaterial, _boxSide, _boxSide, _boxSide);
//repeat texture for each face
cube.tile6 = false;
cube.z = -1 * _boxPositions[i].y;
cube.x = _boxPositions[i].x - _roomSide * 0.5;
cube.y = _boxSide*0.5;
cube.rotationY = -1*(180*_boxes[i].GetAngle())/Math.PI;
_cubes.push(cube);
_container.addChild(cube);
}
//create player frames
frame = new BitmapData(128, 256);
frame.copyPixels(source, new Rectangle(392, 518, 128, 256), new Point(0,0));
frameMaterial = new BitmapMaterial(frame);
//make invisible parts of the texture invisible
frameMaterial.alphaBlending = true;
_frame_front.push(frameMaterial);
frame = new BitmapData(128, 256);
frame.copyPixels(source, new Rectangle(650, 2, 128, 256), new Point(0,0));
frameMaterial = new BitmapMaterial(frame);
frameMaterial.alphaBlending = true;
_frame_front.push(frameMaterial);
frame = new BitmapData(128, 256);
frame.copyPixels(source, new Rectangle(390, 260, 128, 256), new Point(0,0));
frameMaterial = new BitmapMaterial(frame);
frameMaterial.alphaBlending = true;
_frame_front.push(frameMaterial);
frame = new BitmapData(128, 256);
frame.copyPixels(source, new Rectangle(520, 260, 128, 256), new Point(0,0));
frameMaterial = new BitmapMaterial(frame);
frameMaterial.alphaBlending = true;
_frame_front.push(frameMaterial);
frame = new BitmapData(128, 256);
frame.copyPixels(source, new Rectangle(260, 2, 128, 256), new Point(0,0));
frameMaterial = new BitmapMaterial(frame);
frameMaterial.alphaBlending = true;
frameMaterial.bothSides = true;
_frame_side.push(frameMaterial);
frame = new BitmapData(128, 256);
frame.copyPixels(source, new Rectangle(132, 518, 128, 256), new Point(0,0));
frameMaterial = new BitmapMaterial(frame);
frameMaterial.alphaBlending = true;
frameMaterial.bothSides = true;
_frame_side.push(frameMaterial);
frame = new BitmapData(128, 256);
frame.copyPixels(source, new Rectangle(390, 2, 128, 256), new Point(0,0));
frameMaterial = new BitmapMaterial(frame);
frameMaterial.alphaBlending = true;
frameMaterial.bothSides = true;
_frame_side.push(frameMaterial);
frame = new BitmapData(128, 256);
frame.copyPixels(source, new Rectangle(520, 2, 128, 256), new Point(0,0));
frameMaterial = new BitmapMaterial(frame);
frameMaterial.alphaBlending = true;
frameMaterial.bothSides = true;
_frame_side.push(frameMaterial);
frame = new BitmapData(128, 256);
frame.copyPixels(source, new Rectangle(262, 518, 128, 256), new Point(0,0));
frameMaterial = new BitmapMaterial(frame);
frameMaterial.alphaBlending = true;
_frame_back.push(frameMaterial);
frame = new BitmapData(128, 256);
frame.copyPixels(source, new Rectangle(260, 260, 128, 256), new Point(0,0));
frameMaterial = new BitmapMaterial(frame);
frameMaterial.alphaBlending = true;
_frame_back.push(frameMaterial);
frame = new BitmapData(128, 256);
frame.copyPixels(source, new Rectangle(780, 2, 128, 256), new Point(0,0));
frameMaterial = new BitmapMaterial(frame);
frameMaterial.alphaBlending = true;
_frame_back.push(frameMaterial);
frame = new BitmapData(128, 256);
frame.copyPixels(source, new Rectangle(2, 518, 128, 256), new Point(0,0));
frameMaterial = new BitmapMaterial(frame);
frameMaterial.alphaBlending = true;
_frame_back.push(frameMaterial);
//add player
_player = new Plane(_frame_back[0], _player_rec.width, _playerHeight);
_player.y = _playerHeight * 0.5;
_container.addChild(_player);
_container.z = 500;
_container.y = 100;
_container.rotationX = -10;
}
private function createBox2DWorld ():void {
_world = new b2World(new b2Vec2(0,0),true);
var wallThickness:int = 2;
if (_showBox2D) {
//set up debug draw
var debugDraw:b2DebugDraw = new b2DebugDraw();
var container:Sprite = new Sprite();
addChild(container);
debugDraw.SetSprite(container);
debugDraw.SetDrawScale(p2m);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit);
debugDraw.SetFillAlpha(0.5);
}
var bodyDef:b2BodyDef;
var bodyFixture:b2FixtureDef;
//create player
bodyDef = createBoxDef(_player_rec.x, _player_rec.y);
bodyDef.type = b2Body.b2_dynamicBody;
bodyDef.allowSleep = false;
bodyFixture = createBoxFixture(_player_rec.width, _player_rec.height);
_b2player = _world.CreateBody(bodyDef);
_b2player.CreateFixture(bodyFixture);
_b2player.SetFixedRotation(true);
//create crates
for (var i:int = 0; i < _boxPositions.length; i++) {
makeBox(_boxPositions[i]);
}
//create walls
var wall:b2Body;
//left wall
bodyDef = createBoxDef(-wallThickness*0.5, _roomSide*0.5);
bodyDef.type = b2Body.b2_staticBody;
bodyFixture = createBoxFixture(wallThickness, _roomSide);
wall = _world.CreateBody(bodyDef);
wall.CreateFixture(bodyFixture);
//right wall
bodyDef = createBoxDef(_roomSide+wallThickness*0.5, _roomSide*0.5);
bodyDef.type = b2Body.b2_staticBody;
bodyFixture = createBoxFixture(wallThickness, _roomSide);
wall = _world.CreateBody(bodyDef);
wall.CreateFixture(bodyFixture);
//bottom wall
bodyDef = createBoxDef(_roomSide*0.5, _roomSide+wallThickness*0.5);
bodyDef.type = b2Body.b2_staticBody;
bodyFixture = createBoxFixture(_roomSide, wallThickness);
wall = _world.CreateBody(bodyDef);
wall.CreateFixture(bodyFixture);
//top wall
bodyDef = createBoxDef(_roomSide*0.5, -wallThickness*0.5);
bodyDef.type = b2Body.b2_staticBody;
bodyFixture = createBoxFixture(_roomSide, wallThickness);
wall = _world.CreateBody(bodyDef);
wall.CreateFixture(bodyFixture);
if (_showBox2D) _world.SetDebugDraw(debugDraw);
}
private function makeBox (position:Point):void{
var bodyDef:b2BodyDef;
var bodyFixture:b2FixtureDef;
var body:b2Body;
bodyDef = createBoxDef(position.x, position.y);
bodyDef.type = b2Body.b2_dynamicBody;
bodyDef.linearDamping = 5;
bodyFixture = createBoxFixture( _boxSide, _boxSide);
bodyFixture.density = 2;
bodyFixture.friction = 0.3;
body = _world.CreateBody(bodyDef);
body.CreateFixture(bodyFixture);
body.SetAngle(Math.random());
_boxes.push(body);
}
private function createBoxDef (x:Number, y:Number):b2BodyDef {
var bodyDef:b2BodyDef = new b2BodyDef();
bodyDef.position.Set(x/p2m / _scale, y/p2m / _scale);
bodyDef.type = b2Body.b2_staticBody;
bodyDef.angularDamping = 10;
return bodyDef;
}
private function createBoxFixture (width:Number, height:Number):b2FixtureDef {
var bodyShape:b2PolygonShape = new b2PolygonShape();
bodyShape.SetAsBox ((width*0.5)/p2m / _scale, (height*0.5)/p2m /_scale);
var bodyFixture:b2FixtureDef = new b2FixtureDef();
bodyFixture.shape = bodyShape;
bodyFixture.density = 1;
bodyFixture.restitution = 0;
return bodyFixture;
}
}
}

