Phykick, a 3D and physics game

The last days I’ve experimented with jiglibflash, a 3D Physics Engine for Papervision3D. I’ve been inspired by a great example demo and made a little game out of it.

The result is Phykick.

phykick-screen

You have to kick the boxes off the platform as fast as possible. That’s all.
The best thing about Phkick is the highscore list.

The game consists of a sinlge class only, here is the code (without highscore implementation):


package {
	import __AS3__.vec.Vector;

	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;
	import flash.text.TextFormatAlign;
	import flash.ui.Keyboard;
	import flash.utils.getTimer;

	import jiglib.geometry.*;
	import jiglib.math.*;
	import jiglib.physics.*;
	import jiglib.physics.constraint.*;

	import org.papervision3d.cameras.*;
	import org.papervision3d.core.geom.*;
	import org.papervision3d.core.math.*;
	import org.papervision3d.core.proto.*;
	import org.papervision3d.events.*;
	import org.papervision3d.lights.PointLight3D;
	import org.papervision3d.materials.*;
	import org.papervision3d.materials.shadematerials.*;
	import org.papervision3d.materials.utils.*;
	import org.papervision3d.objects.DisplayObject3D;
	import org.papervision3d.objects.parsers.*;
	import org.papervision3d.objects.primitives.*;
	import org.papervision3d.render.*;
	import org.papervision3d.scenes.*;
	import org.papervision3d.view.Viewport3D;

	[SWF(width=800, height=600, backgroundColor=0x062037, frameRate=60)]

	public class Phykick extends Sprite {

		private var viewport:Viewport3D;
	    private var scene:Scene3D;
	    private var camera:Camera3D;
	    private var renderer:QuadrantRenderEngine;
		private var light:PointLight3D;

		private var playerBody:JSphere;
		private var boxBodies:Vector.<JBox>;

		private var keyRight:Boolean = false;
		private var keyLeft:Boolean = false;
		private var keyForward:Boolean = false;
		private var keyReverse:Boolean = false;

		private static var textFormat:TextFormat = new TextFormat("Arial", 12, 0xffffff);

		private var timeField:TextField;
		private var scoreField:TextField;
		private var fallsField:TextField;
		private var fpsField:TextField;
		private var infoField:TextField;
		private var endScoreField:TextField
		private var aboutField:TextField;

		private var score:int;
		private var numFalls:int;
		private var startTime:int;
		private var lastStepTime:int;
		private var inGame:Boolean;
		private var fpsCount:int;
		private var fpsValue:int;

		public function Phykick() {

			viewport = new Viewport3D(800, 600);
			scene = new Scene3D();
			this.addChild(viewport);

			light = new PointLight3D(true, true);
			light.x = 0;
			light.y = 600;
			light.z = -300;

			// ground
			var materia1s:MaterialsList = new MaterialsList();
			materia1s.addMaterial(new FlatShadeMaterial(light, 0xff9900), "all");
			var groundSkin:Cube = new Cube(materia1s);
			scene.addChild(groundSkin);
			var ground:JBox = new JBox(groundSkin, false);
			PhysicsSystem.getInstance().AddBody(ground);
			ground.Material.Restitution = 0.6;
			ground.Material.StaticFriction = 0.9;

			// player
			var sphereSkin:Sphere = new Sphere(new FlatShadeMaterial(light, 0x990000), 20);
			scene.addChild(sphereSkin);
			playerBody = new JSphere(sphereSkin, true, 20);
			playerBody.setMass(3);
			playerBody.MoveTo(new JNumber3D(0, 500, 0), JMatrix3D.IDENTITY);
			PhysicsSystem.getInstance().AddBody(playerBody);

			//playerBody.Material.Restitution = 0.9;

			// camera
			var cameraTarget:DisplayObject3D = new DisplayObject3D();
			cameraTarget.y = 250;
			camera = new Camera3D();
			camera.x = 0;
			camera.y = 500;
			camera.z = -400;
			camera.lookAt(cameraTarget);

			renderer = new QuadrantRenderEngine(1);

			// boxes
			boxBodies = new Vector.<JBox>();
			for (var i:int = 0; i < 3; i++) boxBodies.push(createBox());

			timeField = new TextField();
			timeField.textColor = 0xffffff;
			timeField.selectable = false;
			timeField.defaultTextFormat = textFormat;
			timeField.x = 3;
			timeField.y = 3;
			addChild(timeField);
			scoreField = new TextField();
			scoreField.textColor = 0xffffff;
			scoreField.selectable = false;
			scoreField.defaultTextFormat = textFormat;
			scoreField.x = 3;
			scoreField.y = 23;
			addChild(scoreField);
			fallsField = new TextField();
			fallsField.textColor = 0xffffff;
			fallsField.selectable = false;
			fallsField.defaultTextFormat = textFormat;
			fallsField.x = 3;
			fallsField.y = 43;
			addChild(fallsField);
			fpsField = new TextField();
			fpsField.textColor = 0xffffff;
			fpsField.selectable = false;
			fpsField.defaultTextFormat = textFormat;
			fpsField.x = 3;
			fpsField.y = 63;
			addChild(fpsField);

			aboutField = new TextField();
			aboutField.selectable = false;
			aboutField.textColor = 0xffffff;
			aboutField.autoSize = TextFieldAutoSize.RIGHT;
			aboutField.defaultTextFormat = textFormat
			aboutField.htmlText = "Phykick v1.0.0\n© Mathis Lichtenberger";
			aboutField.y = stage.stageHeight - aboutField.height;
			aboutField.x = 3;
			addChild(aboutField);

			var infoText:String = "// kick the boxes off the platform";
			infoText += "\n// as fast as possible";
			infoText += "\n// you've got 100 seconds";
			infoText += "\n";
			infoText += "\n// arrow keys:   navigate";
			infoText += "\n// space bar:   start game";
			infoText += "\n// ctrl + F:   fullscreen";

			infoField = new TextField();
			infoField.textColor = 0xffffff;
			infoField.y = 60;
			infoField.width = stage.stageWidth;
			infoField.multiline = true;
			infoField.selectable = false;
			infoField.autoSize = TextFieldAutoSize.CENTER;
			infoField.defaultTextFormat = textFormat;
			infoField.text = infoText;
			addChild(infoField);

			endScoreField = new TextField();
			endScoreField.defaultTextFormat = new TextFormat("Arial", 20, 0xffffff, true, false, false);
			endScoreField.y = 600 - 90;
			endScoreField.width = stage.stageWidth;
			endScoreField.selectable = false;
			endScoreField.autoSize = TextFieldAutoSize.CENTER;
			endScoreField.visible = false;
			addChild(endScoreField);

			timeField.text = "time: 100";
			fallsField.text = "falls: 0";
			scoreField.text = "score: 0";

			stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
			stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
			addEventListener(Event.ENTER_FRAME, step);
		}

		private function startGame():void {
			inGame = true;
			infoField.visible = false;
			endScoreField.visible = false;
			startTime = getTimer();
			lastStepTime = getTimer();
			score = 0;
			numFalls = 0;
			timeField.text = "time: 0";
			fallsField.text = "falls: 0";
			scoreField.text = "score: 0";
		}
		private function stopGame():void {
			inGame = false;
			infoField.visible = true;
			endScoreField.text = score.toString();
			endScoreField.visible = true;
			timeField.text = "time: 100";
		}

		private function step(event:Event):void {

			if (inGame) {
				// update player forces
				if (keyLeft) playerBody.AddWorldForce(new JNumber3D(-100,0,0),playerBody.CurrentState.Position);
				if (keyRight) playerBody.AddWorldForce(new JNumber3D(100,0,0),playerBody.CurrentState.Position);
				if (keyForward) playerBody.AddWorldForce(new JNumber3D(0,0,100),playerBody.CurrentState.Position);
				if (keyReverse) playerBody.AddWorldForce(new JNumber3D(0,0,-100),playerBody.CurrentState.Position);
			}

			// check for kicked boxes
			for (var i:int = boxBodies.length - 1; i >= 0; i--) {
				var boxBody:JBox = boxBodies[i];
				if (boxBody.CurrentState.Position.y < 0) {
					boxBody.MoveTo(new JNumber3D(Math.random()*400-200, 400, Math.random()*400-200), JMatrix3D.IDENTITY);
					if (inGame) score++;
					scoreField.text = "score: " + score;
				}
			}

			// check if player is falling
			if (playerBody.CurrentState.Position.y < 0) {
				playerBody.MoveTo(new JNumber3D(0, 300, 0), JMatrix3D.IDENTITY);
				if (inGame) numFalls++;
				fallsField.text = "falls: " + numFalls;
			}

			if (inGame) {
				timeField.text = "time: " + (getTimer() - startTime) / 1000;
				if (getTimer() - startTime > 100*1000) stopGame();
			}

			var duration:int = getTimer() - lastStepTime;
			var integrateValue:Number = duration / 167;
			lastStepTime = getTimer();

			fpsCount++;
			fpsValue += duration;
			if (fpsCount > 100) {
				fpsField.text = "fps: " + Math.round(1000 / (fpsValue / fpsCount));
				fpsCount = 0;
				fpsValue = 0;
			}

			PhysicsSystem.getInstance().Integrate(integrateValue);
			renderer.renderScene(scene, camera, viewport);
		}

		private function createBox():JBox {
			var materials:MaterialsList = new MaterialsList();
			materials.addMaterial(new FlatShadeMaterial(light,0xeeee00),"all");
			var boxSkin:Cube = new Cube(materials, 40, 40, 40);
			scene.addChild(boxSkin);
			var boxBody:JBox = new JBox(boxSkin, true, 40, 40, 40);
			boxBody.MoveTo(new JNumber3D(Math.random()*400-200, 400, Math.random()*400-200), JMatrix3D.IDENTITY);
			PhysicsSystem.getInstance().AddBody(boxBody);
			return boxBody;
		}

		private function keyDownHandler(event:KeyboardEvent):void {
			switch(event.keyCode) {
				case Keyboard.UP: keyForward = true; break;
				case Keyboard.DOWN: keyReverse = true; break;
				case Keyboard.LEFT: keyLeft = true; break;
				case Keyboard.RIGHT: keyRight = true; break;

				case Keyboard.SPACE: if (!inGame) startGame(); break;
			}
		}
		private function keyUpHandler(event:KeyboardEvent):void {
			switch(event.keyCode) {
				case Keyboard.UP: keyForward = false; break;
				case Keyboard.DOWN: keyReverse = false; break;
				case Keyboard.LEFT: keyLeft = false; break;
				case Keyboard.RIGHT: keyRight = false; break;
			}
		}
	}
}

Leave a Comment