Author: Max Pellizzaro
Date: December 5th 2008
version: 3.0.3
When I started to build this tutorial my intent was only to show how to use the clone(.) method for any Sandy objects, and in this case using it for a MD2 object. But I got excited, so I have tried to set up the basis to develop a little game.
In this tutorial you will learn :
Let's begin then.
The Document class must be changed to Example026.as The name of the class in the .as file and the name of the constructor now is: Example026. No extra classes are needed for this tutorial, but you will need all extra the resources: 2 MD2 files and 2 skins that can be found in the attached zip.
All the required files can be found here:
This tutorial requires a little extra work to set up the environment of our scene. As I always suggest, if you don't need to move elements in the scene, place a nice static 3d image, it will save a lot of computation. Here you see how I set up my Stage with a static tank.
package {
import flash.display.*;
import flash.events.*;
import flash.net.*;
import flash.utils.*;
import flash.ui.*;
import flash.text.*;
import sandy.util.*;
import sandy.core.Scene3D;
import sandy.core.data.*;
import sandy.core.scenegraph.*;
import sandy.materials.*;
import sandy.materials.attributes.*;
import sandy.primitive.*;
import sandy.events.*;
public class Example026 extends Sprite {
private var queue:LoaderQueue;
private var scene:Scene3D;
public var camera:Camera3D;
private var arrayDemon = new Array();
private var demonTest:MD2;
private var demondie:MD2;
private var gun:Cylinder = new Cylinder( "gun", 3, 50, 13, 6 );
private var bt = new Array();
private var bullet:Sphere = new Sphere( "bullet", 3);
private var fire:Boolean = false;
private var directionBall:Number = 0;
private var idInterval:Number = 0;
private var myText:TextField = new TextField();
private var myFormat:TextFormat = new TextFormat();
private var score:Number = 0;
public function Example026() {
queue = new LoaderQueue();
queue.add( "demon", new URLRequest("demon/Demon.md2"), "BIN" );
queue.add( "demondie", new URLRequest("demon/Demondie.md2"), "BIN" );
queue.add( "demonSkin", new URLRequest("demon/Skindemon.jpg"), "IMG" );
queue.add( "demondieSkin", new URLRequest("demon/Skindemondie.jpg"), "IMG" );
queue.addEventListener(SandyEvent.QUEUE_COMPLETE, loadMD2Complete );
queue.start();
}
private function loadMD2Complete(event:Event):void {
// let's add the "gun" in the screen
gun.x = 0;
gun.y = 50;
gun.z = -260;
gun.rotateX = 90;
gun.geometryCenter = new Vector(0,20,0);
Mouse.hide();
// let's add the score points:
myText.width = 200;
myText.text = "00";
myFormat.color = 0x000000;
myFormat.size = 24;
myFormat.italic = true;
myText.setTextFormat(myFormat);
myText.textColor = 0xFFFFFF;
myText.x = 83;
myText.y = 33;
addChild(myText);
// We create the "group" that is the tree of all the visible objects
var root:Group = createScene();
camera = new Camera3D( 600, 450 );
camera.z = -400;
camera.y = 100;
camera.lookAt(0,0,0);
// We create a Scene and we add the camera and the objects tree
scene = new Scene3D( "scene", this, camera, root );
stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMovedHandler);
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyPressed);
addEventListener( Event.ENTER_FRAME, enterFrameHandler );
}
private function createScene():Group
{
// Create the root Group
var g:Group = new Group();
// the normal demon
arrayDemon[0] = new MD2 ( "dem", queue.data["demon"], 0.5 );
arrayDemon[0].appearance = new Appearance (new BitmapMaterial( queue.data["demonSkin"].bitmapData ));
arrayDemon[0].z = (Math.random()-0.5)*1000 + 500;
arrayDemon[0].x = (Math.random()-0.5)*600;
arrayDemon[0].pan = 90;
g.addChild( arrayDemon[0]);
for (var i:Number=1; i<7; i++){
arrayDemon[i] = arrayDemon[0].clone("dem");
arrayDemon[i].pan = 90;
arrayDemon[i].z = (Math.random()-0.5)*1000 + 500;
arrayDemon[i].x = (Math.random()-0.5)*600;
g.addChild( arrayDemon[i] );
}
// the demondie
arrayDemon[7] = new MD2 ("demdie", queue.data["demondie"], 0.5);
arrayDemon[7].appearance = new Appearance (new BitmapMaterial( queue.data["demondieSkin"].bitmapData ));
arrayDemon[7].pan = 90;
arrayDemon[7].visible = false;
g.addChild( arrayDemon[7]);
// let's concentrate on gun and bullets now:
var materialAttr:MaterialAttributes = new MaterialAttributes(
new LightAttributes( true, 0.1)
);
var material:Material = new ColorMaterial( 0x666666, 1, materialAttr );
material.lightingEnable = true;
var app:Appearance = new Appearance( material );
bullet.appearance = app;
gun.appearance = app;
bullet.visible = false;
g.addChild(bullet);
g.addChild(gun);
return g;
}
private function enterFrameHandler( event : Event ) : void {
// let's make the monster walking
for (var j:Number=0; j<7; j++) {
if (arrayDemon[j] != null) {
arrayDemon[j].frame += 0.3;
arrayDemon[j].z -= 1;
if (arrayDemon[j].z < -100) {
arrayDemon[j].x = (Math.random()-0.5)*600;
arrayDemon[j].z = 500;;
}
}
}
// let's fire the bullet
if(fire && bullet.z < 1000) {
bullet.z +=25;
bullet.x -=directionBall/2;
} else {
bullet.visible = false;
}
// let's check for hitting monster:
for (var k:Number=0; k<7; k++) {
if (arrayDemon[k] != null) {
var x1:Number = arrayDemon[k].x - bullet.x;
var x2:Number = arrayDemon[k].y - bullet.y;
var x3:Number = arrayDemon[k].z - bullet.z;
var dist = Math.sqrt(x1*x1+x2*x2+x3*x3);
if (dist < 60) {
arrayDemon[7].x = arrayDemon[k].x;
arrayDemon[7].y = arrayDemon[k].y;
arrayDemon[7].z = arrayDemon[k].z;
arrayDemon[k].visible = false;
arrayDemon[7].frame = 0;
arrayDemon[7].visible = true;
idInterval = setInterval( dieMonster, 100, k );
bullet.z = 1100;
score += 10;
myText.text = new String(score);
myText.setTextFormat(myFormat);
myText.textColor = 0xFFFFFF;
}
}
}
// let's clear interval now if any
if(idInterval > 0 && arrayDemon[7].frame>5){
clearInterval(idInterval);
idInterval = 0;
}
scene.render();
}
private function dieMonster(h:Number){
arrayDemon[7].frame += 0.3
if (arrayDemon[7].frame > 5){
arrayDemon[7].visible = false;
arrayDemon[h].x = (Math.random()-0.5)*600;
arrayDemon[h].z = 500;;
arrayDemon[h].visible = true;
}
}
private function mouseMovedHandler(event:MouseEvent):void {
gun.roll=(300-event.stageX)/7;
}
private function keyPressed(event:KeyboardEvent):void {
switch(event.keyCode) {
case Keyboard.SPACE:
directionBall = gun.roll;
fireBall();
break;
}
}
private function fireBall(){
bullet.x = 0;
bullet.y = 50;
bullet.z = -260;
fire = true;
bullet.visible = true;
}
}
}
First of all we need to load our M2D objects. For this tutorial we are loading two different objects: one “demon” that is walking and one “demon” that is dieing.
queue = new LoaderQueue(); queue.add( "demon", new URLRequest("demon/Demon.md2"), "BIN" ); queue.add( "demondie", new URLRequest("demon/Demondie.md2"), "BIN" ); queue.add( "demonSkin", new URLRequest("demon/Skindemon.jpg"), "IMG" ); queue.add( "demondieSkin", new URLRequest("demon/Skindemondie.jpg"), "IMG" ); queue.addEventListener(SandyEvent.QUEUE_COMPLETE, loadMD2Complete ); queue.start();
Now it's time to make the “demon” available to use it. To do so I have decided to model an Array() that will hold as many demon as you want to use. In my case we will hold 7 demon. So here is the code:
private var arrayDemon = new Array(); ... // the normal demon arrayDemon[0] = new MD2 ( "dem", queue.data["demon"], 0.5 ); arrayDemon[0].appearance = new Appearance (new BitmapMaterial( queue.data["demonSkin"].bitmapData )); arrayDemon[0].z = (Math.random()-0.5)*1000 + 500; arrayDemon[0].x = (Math.random()-0.5)*600; arrayDemon[0].pan = 90; g.addChild( arrayDemon[0]);
The above code will load a single demon. In order to “load” the other 6 we will use the clone(.) method.
for (var i:Number=1; i<7; i++){ arrayDemon[i] = arrayDemon[0].clone("dem"); arrayDemon[i].pan = 90; arrayDemon[i].z = (Math.random()-0.5)*1000 + 500; arrayDemon[i].x = (Math.random()-0.5)*600; g.addChild( arrayDemon[i] ); }
Now we need to use the M2D that represents the demon that is dieing. I have chosen to have only one demon die at a time, so we just need one object that we will place at the end of our Array(.).
// the demondie arrayDemon[7] = new MD2 ("demdie", queue.data["demondie"], 0.5); arrayDemon[7].appearance = new Appearance (new BitmapMaterial( queue.data["demondieSkin"].bitmapData )); arrayDemon[7].pan = 90; arrayDemon[7].visible = false; g.addChild( arrayDemon[7]);
It's now time to model our weapons. The tank is already there, but we need a gun to shoot with. So I will model it with a simple Cylinder. It could be more sophisticated, but for our tutorial it will do for us.
private var gun:Cylinder = new Cylinder( "gun", 3, 50, 13, 6 ); ... gun.x = 0; gun.y = 50; gun.z = -260; gun.rotateX = 90; gun.geometryCenter = new Vector(0,20,0); Mouse.hide();
As you might notice I have moved the gemoetryCenter in order to emulate the fact that the point of rotation is not at the center of the Cylinder. The bullet is a simple Sphere I will not teach you how to model a Sphere :).
Now it's time to model the interaction part.
Modeling object collision is not an easy job, and it involves lot of mathematics, but with games we can have shortcuts and almost noone will notice some imprecision. The idea is to compute the distance between the center of the two objects (in our case the bullet and each monsters), and check if this distance id less than a specific value. Here is the code:
for (var k:Number=0; k<7; k++) { if (arrayDemon[k] != null) { var x1:Number = arrayDemon[k].x - bullet.x; var x2:Number = arrayDemon[k].y - bullet.y; var x3:Number = arrayDemon[k].z - bullet.z; var dist = Math.sqrt(x1*x1+x2*x2+x3*x3); if (dist < 60) { arrayDemon[7].x = arrayDemon[k].x; arrayDemon[7].y = arrayDemon[k].y; arrayDemon[7].z = arrayDemon[k].z; arrayDemon[k].visible = false; arrayDemon[7].frame = 0; arrayDemon[7].visible = true; idInterval = setInterval( dieMonster, 100, k ); bullet.z = 1100; score += 10; myText.text = new String(score); myText.setTextFormat(myFormat); myText.textColor = 0xFFFFFF; } }
If we hit a monster, we will make the monster disappearing and we will run the M2D that emulates the monster dieing.
private function dieMonster(h:Number){ arrayDemon[7].frame += 0.3 if (arrayDemon[7].frame > 5){ arrayDemon[7].visible = false; arrayDemon[h].x = (Math.random()-0.5)*600; arrayDemon[h].z = 500;; arrayDemon[h].visible = true; } }
Well I guess all the important code has been analyzed, and I let you understand the rest of it by yourself.
And now let’s see the result !
Use the mouse to move the gun and use the space bar to fire the demons.
Click on the Stage to give the control to your tank for firing.