在第五部分我们增加了一些敌机而且给游戏者增加了武器使它能射击。在第六部分我们将增加碰撞检测来允许游戏者确实能够击落敌机。
碰撞检测是当两个对象碰撞时能够检测到,然后正确地反应。在第五部分我们给游戏者射击接近的敌机的能力。唯一的问题是那些子弹只是越过敌机。在第六部分我们将增加必要的代码来实现碰撞检测,这将让我们能够把敌机射下来。
碰撞检测看起来是非常简单地概念,但实现起来却超乎寻常地难。你将发现有很多整本整本讲测试2维和3维图形交集的书。让我们感到幸运的是我们的碰撞检测将非常简单。在屏幕上的每个对象将有一个矩形区域来检测到碰撞(“碰撞区”)。为了简单起见这个区域将和用来在屏幕上显示子图形的基础图片大小一样。一旦这些矩形重叠我们将检测到碰撞。
为了得到最好的效果这些矩形将被剪裁地尽量和显示的图片一样大。下面是在游戏中可能被用到的两幅图片。这两幅图片将完全显示一样因为在飞机周围是透明的。然而上面一张图片对于碰撞检测系统是最优的,因为它裁剪得更接近实际的飞机图形大小。下面一张将会显示好像在应该碰到其它对象之前就碰到了,因为碰撞检测系统不会注意到透明的边界部分,而且认为整个图片区域被用来作为碰撞
现在让我们看看为了实现碰撞检测需要在GameObject类中做哪些改变。
使用FLEX和Actionscript开发FLASH 游戏-碰撞检测(第二页)
GameObject.as
package
{
import flash.display.*;
import flash.events.*;
import flash.geom.*;
/* The base class for all objects in the game */
public class GameObject
{
//object position
public var position:Point=new Point(0,0);
//higher ZOrder objects are rendered on top of lower ones
public var zOrder:int=0;
//the bitmap data to diaplay
public var graphics:GraphicsResource=null;
//true if the object is active in the game
public var inuse:Boolean=false;
public var collisionArea:Rectangle;
public var collisionName:String=CollisionIdentifiers.NONE;
public function get CollisionArea():Rectangle
{
return new Rectangle(position.x,position.y,collisionArea.width,collisionArea.height);
}
public function GameObject()
{
}
public function startupGameObject(graphics:GraphicsResource,position:Poin,z:int=0):void
{
if(!inuse)
{
this.graphics=graphics;
this.zOrder=z;
this.position=position.clone();
this.inuse=true;
GameObjectManager.Instance.addGameObject(this);
setupCollision();
}
}
public function shutdown():void
{
if(inuse)
{
graphics=null
inuse=false;
GameObjectManager.Instance.removeGameObject(this);
}
}
public function copyToBackBuffer(db:BitmapData):void
{
db.copyPixels(graphics.bitmap,graphics.bitmap.rect,position,
graphics.bitmapAlpha,new Point(0,0),true);
}
public function enterFrame(dt:Number):void
{
}
public function mouseDown(event:MouseEvent):void
{
}
public function click(event:MouseEvent):void
{
}
public function mouseUp(event:MouseEvent):void
{
}
public function mouseMove(event:MouseEvent):void
{
}
protected function setupCollision():void
{
collisionArea=graphics.bitmap.rect;
}
public function collision(other:GameObject):void
{
}
}
}
我们增加了两个新的属性:碰撞区(collisionArea)和碰撞名(collisionName)。碰撞区属性代表了上文所述的描述碰撞区的矩形。碰撞名属性是赋给游戏对象用来定义它是哪种对象的名字,至少对于碰撞检测系统而言的种类。例如:被游戏者发出的子弹的碰撞名为“游戏者武器”,而一架敌机的碰撞名则是“敌人”。缺省状态下我们把它设置为“None”,使用CollisionIdentifiers类的NONE属性。
另外我们也增加了三个新的函数:碰撞(collision)、碰撞区域(CollisionArea)和设置碰撞(setupCollision)。碰撞函数是一个空函数,以待被继承类方法所覆盖。设置碰撞函数被用来负责保存碰撞检测系统下的图片的大小。碰撞区域函数返回碰撞区矩形目前在屏幕上的位置。
你可能想问为什么我们还麻烦地设置一个collsionArea属性,它实际上和graphics.bitmap.rect是完全一样的。只是因为后面(在第七章)我们将增加游戏的动画部分。动画类将用自己的转门的逻辑覆盖设置碰撞函数。虽然现在碰撞区域和图片的矩形一样。
CollisionIdentifiers.as
package
{
public class CollisionIdentifiers
{
public static const NONE:String="None";
public static const PLAYER:String="Player";
public static const PLAYERWEAPON:String="PlayerWeapon";
public static const ENEMYWEAPON:String="EnemyWeapon";
public static const ENEMY:String="Enemy";
public static const POWERUP:String="Powerup";
}
}
就像ZOrders类一样,CollisionIdentifiers类被用来保存一些预定义的静态属性;这里就是碰撞名。这么做的目的还是为了促进自文档化。CollisionIdentifiers.PLAYER的意思不言自明,然而字符串“Player”却没有同样的内在含义。
现在让我们看看在GameObjectManager类中做的改变。
GameObjectManager.as
package
{
import flash.display.*;
import flash.events.*;
import flash.utils.*;
import mx.collections.*;
import mx.core.*;
public class GameObjectManager
{
//double buffer
public var backBuffer:BitmapData;
//colour to use to clear backbuffer with
public var clearColour:uint=0xFF0043AB;
//static instance
protected static var instance:GameObjectManager=null;
//the last frame time
protected var lastFrame:Date;
//a collection of the GameObjects
protected var gameObjects:ArrayCollection=new ArrayCollection();
// a collection where new GameObjects are placed,to void adding items
//to gameObjects while in the gameObjects collection while it is in a loop
protected var newGameObjects:ArrayCollection=new ArrayCollection();
//a collection where removed GameObjects are placed,to avoid removing items
//to gameObjects while in the gameObjects collection while it is in a loop
protected var removedGameObjects:ArrayCollection=new ArrayCollection();
protected var collisionMap:Dictionary=new Dictionary();
static public function get Instance():GameObjectManager
{
if(instance==null)
instance=new GameObjectManager();
return instance;
}
public function GameObjectManager()
{
if(instance!=null)
throw new Error("Only one Singleton instance should be instantiated");
backBuffer=new BitmapData(Application.application.width,Application.application.height,false);
}
public function startup():void
{
lastFrame=new Date();
}
public function shutdown():void
{
shutdownAll();
}
public function enterFrame():void
{
//Calculate the time since the last frame
var thisFrame:Date=new Date();
var seconds:Number=(thisFrame.getTime()-lastFrame.getTime())/1000.0;
lastFrame=thisFrame;
removeDeletedGameObjects();
insertNewGameObjects();
Level.Instance.enterFrame(seconds);
checkCollisions();
//now allow objects to update themselves
for each(var gameObject:GameObject in gameObjects)
{
if(gameObject.inuse)
gameObject.enterFrame(seconds);
}
drawObjects();
}
public function click(event:MouseEvent):void
{
for each(var gameObject:GameObject in gameObjects)
{
if(gameObject.inuse)
gameObject.click(event);
}
}
public function mouseDown(event:MouseEvent):void
{
for each(var gameObject:GameObject in gameObjects)
{
if(gameObject.inuse)
gameObject.mouseDown(event);
}
}
public function mouseUp(event:MouseEvent):void
{
for each(var gameObject:GameObject in gameObjects)
{
if(gameObject.inuse)
gameObject.mouseUp(event);
}
}
public function mouseMove(event:MouseMove):void
{
for each(var gameObject:GameObject in gameObjects)
{
if(gameObject.inuse)
gameObject.mouseMove(event);
}
}
protected function drawObjects():void
{
backBuffer.fillRect(backBuffer.rect,clearColor);
//draw the objects
for each(var gameObject:GameObject in gameObjects)
{
if(gameObject.inuse)
gameObject.copyToBackBuffer(backBuffer);
}
}
public function addGameObject(gameObject:GameObject):void
{
newGameObjects.addItem(gameObject);
}
public function removeGameObject(gameObject:GameObject):void
{
removedGameObjects.addItem(gameObject);
}
protected function shutdownAll():void
{
//don't dispose objects twice
for each(var gameObject:GameObject in gameObjects)
{
var found:Boolean=false;
for each(var removedObject:GameObject in removedGameObjects)
{
if(removedObject==gameObject)
{
found=true;
break;
}
}
if(!found)
gameObject.shutdown();
}
}
protected function insertNewGameObjects():void
{
for each(var gameObject:GameObject in newGameObjects)
{
for(var i:int=0;i
{
if(gameObjects.getItemAt(i).zOrder>gameObject.zOrder||
gameObjects.getItemAt(i).zOrder==-1
break;
}
gameObjects.addItemAt(gameObject,i);
}
newGameObjects.removeAll();
}
protected function removeDeletedGameObjects():void
{
//insert the object according to it's z position
for each(var removedObject:GameObject in removedGameObjects)
{
var i:int=0;
for (i=0;i
{
if(gameObjects.getItemAt(i)==removedObject)
{
gameObjects.removeItemAt(i);
break;
}
}
}
removedGameObjects.removeAll();
}
public function addCollidingPair(collider1:String,collider2:String):void
{
if(collisionMap[collider1]==null)
collisionMap[collider1]=new Array();
if(collisionMap[collider2]==null)
collisionMap[collider2]=new Array();
collisionMap[collider1].push(collider2);
}
protected function checkCollision():void
{
for(var i:int=0;i
{
var gameObjectI:GameObject=gameObjects.getItemAt(i) as GameObject;
for(var j:int=i+1;j
{
var gameObjectJ:GameObject=gameObjects.getItemAt(j) as GameObject;
//early out for non=colliders
var collisionNameNotNothing:Boolean=gameObjectI.collisionName!=
CollisionIdentifiers.NONE;
//objects can still exist in the gameObjects collection after being disposed,
so check
var bothInUse:Boolean=gameObjectI.inuse&&gameObjectJ.inuse;
//make sure we have an entry in the collisionMap
var collisionMapEntryExists:Boolean=collisionMap
[gameObject.collisionName]!=null;
//make sure the two objects are set to collide
var testForCollision:Boolean=collisionMapEntryExists&&collisionMap
[gameObjectI.collisionName].indexOf(gameObjectJ.collsionName)!=-1
if(collisionNameNotNothing&&
bothInUse&&
collisionMapEntryExists&&
testForCollision)
{
if(gameObjectI.CollisionArea.intersects(gameObjectJ.CollisionArea))
{
gameObjectI.collision(gameObjectJ);
gameObjectJ.collision(gameObjectI);
}
}
}
}
}
}
}
我们已经给GameObjectManager增加了一个属性:collisionMap。这是一个字典属性其关键码为游戏对象的碰撞名,而且值是一个所有可能与之碰撞的游戏对象的碰撞名的数组。当数量增加时它看起来像下面的形式。
关键码:”Player”值:{“Enemy”,”EnemyWeapon”,”Powerup”}
关键码:”Enemy”值:{“Player”,”PlayerWeapon”}
关键码:”PlayerWeapon”值:{“Enemy”}
关键码:”Powerup”值:{“Player”}
等等诸如此类。
addCollidingPair函数被用来填充collisionMap字典。我们将在main.mxml文件中的creationComplete函数中调用它。checkCollision函数用来实际检测出碰撞,而且同时游戏对象被告知。函数看起来比较复杂,实际上非常简单。
最初它在gameObjects集合(包括所有活跃的GameObjects)上循环两次,在这样的结构上来把所有的游戏对象和其它的对象比较一遍。它做了下面一系列的检查:
l 是否两个游戏对象的碰撞名都是”None”?两个游戏对象都需要其碰撞名为None来参与一个碰撞。
l 是不是两个游戏对象都是使用状态?(即它们在游戏中都是活跃的)这应该总是情况属实,但是也不影响检测。
l 是否游戏对象的碰撞名在collisionMap中都已经作为碰撞者注册了?collisionMap的目的是决定与之碰撞的对象是哪些
如果所有这些检测都是为真,那么我们使用矩形的重叠函数来看是否游戏对象实际上碰撞。如果他们是碰撞了那么通过它们的collision函数被告知。
正如我之前在论文中提过的碰撞检测是一个被用来整本整本探讨的主题。我们不会使用哪些很聪明的方式来优化碰撞检测系统。我们在这里的用到的是一种简单的检测系统,但是它也是很有用的因为我们在整个屏幕中在任何时候拥有的最多的游戏对象不会超过两打(24个)。
为了能够检测到所有的碰撞我们需要调用addColllidingPair函数。这将在我们的应用程序对象的creationComplete函数中调用。现在让我们看看所做的一些变化。
Level.as
package
{
import flash.events.*;
import flash.geom.*;
import flash.media.*;
import flash.net.*;
import flash.utils.*;
import mx.collections.ArrayCollection;
import mx.core.*;
public class Level
{
protected static var instance:Level=null;
protected static const TimeBetweenLevelElements:Number=2;
protected static const TimeBetweenEnemies:Number=3;
protected static const TimeBetweenClouds:Number=2.5;
protected static const TimeToLevelEnd:Number=2;
protected var timeToNextLevelElement:Number=0;
protected var levelElementGraphics:ArrayCollection=new ArrayCollection();
protected var timeToNextEnemy:Number=0;
protected var enemyElementGraphics:ArrayCollection=new ArrayCollection();
protected var timeToNextCloud:Number=0;
protected var timeToLevelEnd:Number=0;
public var levelEnd:Boolean=false;
static public function get Instance():Level
{
if(instance==null)
instance=new Level();
return instance;
}
public function Level(caller:Function=null)
{
if(Level.instance!=null)
throw new Error("Only one Singleton instance should be instantiated");
levelElementGraphics.addItem(ResourceManager.SmallIslandGraphics);
levelElementGraphics.addItem(ResourceManager.BigIslandGraphics);
levelElementGraphics.addItem(ResourceManager.VolcanoIslandGraphics);
enemyElementGraphics.addItem(ResourceManager.SmallBluePlaneGraphics);
enemyElementGraphics.addItem(ResourceManager.SmallGreenPlaneGraphics);
enemyElementGraphics.addItem(ResourceManager.SmallWhitePlaneGraphics);
}
public function startup():void
{
timeToNextLevelElement=0;
new Player().startupPlayer();
timeToLevelEnd=TimeToLevelEnd;
levelEnd=false;
}
public function shutdown():void
{
}
public function enterFrame(dt:Number):void
{
//add a background element
timeToNextLevelElement-=dt;
if(timeToNextLevelElement<=0)
{
timeToNextLevelElement=TimeBetweenLevelElements;
var graphics:GraphicsResource=levelElementGraphics.getItemAt
(MathUtils.randomInteger(0,levelElementGraphics.length))as GraphicsResource;
var backgroundLevelElement:BackgroundLevelElement=
BackgroundLevelElement.pool.ItemFromPool as BackgroundLevelElement;
backgroundLevelElement.startupBackgroundLevelElement(
graphics,
new Point(Math.random()*Application.application.width,-graphics.bitmap.height),
ZOrders.BackgroundZOrder,
50);
}
//add an enemy
timeToNextEnemy-=dt;
if(timeToNextEnemy<=0)
{
timeToNextEnemy=TimeBetweenEnemies;
var enemygraphics:GraphicsResource=enemyElementGraphics.getItemAt
(MathUtils.randomInteger(0,enemyElementGraphics.length))as GraphicsResource;
var enemy:Enemy=Enemy.pool.ItemFromPool as Enemy;
enemy.startupBasicEnemy(
enemygraphics,
new Point(Math.random()*Application.application.width,-
enemygraphics.bitmap.height),
55);
}
//add cloud
timeToNextCloud-=dt;
if(timeToNextCloud<=dt)
{
timeToNextCloud=TimeBetweenClouds;
var cloudsBackgroundLevelElement:BackgroundLevelElement=
BackgroundLevelElement.pool.ItemFromPool as BackgroundLevelElement;
cloudBackgroundLevelElement.startupBackgroundLevelElement(
ResourceManager.CloudGraphics,
new Point(Math.random()*Application.application.width,-
ResourceManager.CloudGraphics.bitmap.height),
ZOrders.CloudsBelowZOrder,
75);
}
if(levelEnd)
timeToLevelEnd-=dt;
if(timeToLevelEnd<=0)
Application.application.currentState="MainMenu";
}
}
}
在Level类中做的改变仅仅让它在游戏者死亡时通过levelEnd属性被告知。当在enterFrame函数中使用timeToLevelEnd属性设置一个倒数计数器时,而且当timeToLevelEnd为0时状态被返回到MainMenu让我们回到主菜单屏幕。
碰撞检测在任何动作游戏中都是基本的。在本系列论文中我们实现了一个简单的,但是有效的碰撞检测系统,使得游戏者现在可以和游戏中的其他元素交互作用了,看起来就好像能够射下敌机。不幸的是目前的射下敌机实际上不是很令人满意,因为它们只是消失了。在本系列第七部分我们将增加一些动画到游戏里,来做一个好的爆炸效果。
在 http://flexfighters.sourceforge.net/flexfighters6.html处可查看最终效果,从 https://sourceforge.net/project/showfiles.php?group_id=241490&package_id=293860&release_id=632042处可下载资源。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/literza/archive/2009/06/05/4241517.aspx
main.mxml
xmlns:ms="http://www.adobe.com/2006/mxml"
layout="absolute"
width="600"
height="400"
frameRate="100"
creationComplete="creationComplete()"
enterFrame="enterFrame(event)"
click="click(event)"
mouseDown="mouseDown(event)"
mouseUp="mouseUp(event)"
mouseMove="mouseMove(event)"
currentState="MainMenu">
name="Game"
enterState="enterGame(event)"
exitState="exitGame(event)"
protected var inGame:Boolean=false;
public function creationComplete():void
{
GameObjectManager.Instance.addCollidingPair(CollisionIdentifiers.PLAYER,CollisionIdentifiers.ENEMY);
GameObjectManager.Instance.addCollidingPair(CollisionIdentifiers.ENEMY,CollisionIdentifiers.PLAYERWEAPON);
GameObjectManager.Instance.addCollidingPair(CollisionIdentifiers.PLAYER,CollisionIdentifiers.ENEMYWEAPON);
}
public function enterFrame(event:Event):void
{
if(inGame)
{
GameObjectManager.Instance.enterFrame();
myCanvas.graphics.clear();
myCanvas.graphics.begingBitmapFill(GameObjectManager.Instance.backBuffer,null,false,false);
myCanvas.graphics.drawRect(0,0,this.width,this.height);
myCanvas.graphics.endFill();
}
}
privated function click(event:MouseEvent):void
{
GameObjectManager.Instance.click(event);
}
privated function mouseDown(event:MouseEvent):void
{
GameObjectManager.Instance.mouseDown(event);
}
privated function mouseUp(event:MouseEvent):void
{
GameObjectManager.Instance.mouseUp(event);
}
privated function mouseMove(event:MouseEvent):void
{
GameObjectManager.Instance.mouseMove(event);
}
protected function startGameClicked(event:Event):void
{
currentState="Game"
}
protected function enterGame(event:Event):void
{
Mouse.hide();
GameObjectManager.Instance.startup();
Level.Instance.startup();
inGame=true;
}
protected function exitGame(event:Event):void
{
Mouse.show();
Level.Instance.shutdown();
GameObjectManager.Instance.shutdown();
inGame=false;
}
]]>
就如你所见的要指定两个游戏对象碰撞只需要调用GameObjectManager的addCollidingPair函数。这里我们指定了游戏者会同敌机碰撞,敌机会同游戏者子弹碰撞,游戏者也会与敌机子弹碰撞。
那么现在既然我们可以检测到碰撞我们就需要更新游戏者、子弹和敌机类的代码,来设置他们的碰撞名以及当检测到时的反应。现在让我们看看游戏者类。