使用Flex和Actionscript開發Flash遊戲——碰撞檢測

這一部分,我們加入碰撞檢測,讓玩家能夠真正的攻擊敵機。
    顧名思義,碰撞檢測就是能夠探測兩個物體碰撞,並且做出相應的反應。第五部分裏,我們的飛船已
經能夠將子彈射向敵機。唯一的問題就是子彈穿過了敵機。這部分中,我們要用代碼實現碰撞檢測,將敵
機擊落。
    表面上看碰撞檢測很簡單,但是實際上,這是一個非常難實現的概念。你能發現有的書整本都在講述
二維物體間及三維物體間的相交性。幸好,我們使用的碰撞檢測比較簡單。每一個畫面上的物體都擁有一
個矩形包圍盒,用來進行碰撞檢測。這些矩形包圍盒與下面圖像的長寬一致。當矩形重疊時,我們就認爲
是碰撞了。
     最好是將圖片裁剪到沒有空白邊,以便更加精確的進行碰撞檢測。例如:上圖就比下圖更優。下圖
的空白部分同樣會被用於碰撞檢測。
[img[/img]
接下來,讓我們看一下GameObject類的改變:
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 display
  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:Point, 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 click(event:MouseEvent):void
{
}
public function mouseDown(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
{
}
}
}
Read more: http://www.brighthub.com/internet/web-development/articles/11889.aspx?
p=2#ixzz0Q3kP0KCc
複製代碼我們添加了兩個屬性:collisionArea和collisionName。collisionArea表示之前我們說過的矩
形。collisionName定義了物體的類型。例如:武器擁有名字“PlayerWeapon”,敵人擁有名字“Enemy”
。默認名字爲“None”,用CollisionIdentifiers.NONE來指定。
    我們還添加了3個方法:collision, CollisionArea和setupCollision。collision方法是另一個空方
法,需要子類去覆寫。當碰撞檢測到時,它將被GameObjectManager調用。setupCollision方法用於保存
用於碰撞檢測系統的圖像大小。CollisionArea返回矩形包圍盒當前的屏幕位置。(未完待續)
你或許會奇怪,既然包圍盒與圖像尺寸一樣,爲什麼還要單獨使用一個collisionArea屬性呢?原因是在
第7部分我們要將動畫加入遊戲。動畫類將重寫setupCollision方法。
CollisionIdentifiers.aspackage
{
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";
}
}
Read more: http://lp2.fanqiang.org/browse.php?
u=Oi8vd3d3LmJyaWdodGh1Yi5jb20vaW50ZXJuZXQvd2ViLWRldmVsb3BtZW50L2FydGljbGVzLzExODg5LmFzcHg%
2FcD0y&b=5#ixzz0Q6NoySPG
複製代碼和ZOrders類一樣,CollisionIdentifiers類用於保存一些預先設定的靜態值。爲了防止名字撞
車。CollisionIdentifiers.PLAYER進行自我解釋,就是說“Player”字符串並不代表其本身的意思。
接下來看一下GameObjectManager類。
GameObjectManager.aspackage
{
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 clearColor: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 avoid 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:MouseEvent):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 < gameObjects.length; ++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 acording to it's z position
for each (var removedObject:GameObject in removedGameObjects)
{
var i:int = 0;
for (i = 0; i < gameObjects.length; ++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);
collisionMap[collider2].push(collider1);
}
protected function checkCollisions():void
{
for (var i:int = 0; i < gameObjects.length; ++i)
{
var gameObjectI:GameObject = gameObjects.getItemAt(i) as GameObject;
for (var j:int = i + 1; j < gameObjects.length; ++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[gameObjectI.collisionName] != null;
// make sure the two objects are set to collide
var testForCollision:Boolean = collisionMapEntryExists && collisionMap
[gameObjectI.collisionName]. indexOf(gameObjectJ.collisionName) != -1
if ( collisionNameNotNothing &&
bothInUse &&
collisionMapEntryExists &&
testForCollision)
{
if (gameObjectI.CollisionArea. intersects(gameObjectJ.CollisionArea))
{
gameObjectI.collision(gameObjectJ);
gameObjectJ.collision(gameObjectI);
}
}
}
}
}
}
}
Read more: http://lp2.fanqiang.org/browse.php?
u=Oi8vd3d3LmJyaWdodGh1Yi5jb20vaW50ZXJuZXQvd2ViLWRldmVsb3BtZW50L2FydGljbGVzLzExODg5LmFzcHg%
2FcD0z&b=5#ixzz0Q6QyuGRg
我們添加了collisionMap屬性。這是一個Dictionary類型,查找key是GameObject的碰撞名稱,值是一個
由其它所有將可能與它產生碰撞的GameObjects數組。寫出來大概就是這個樣子:
Key: "Player" Value: {"Enemy", "EnemyWeapon", "Powerup"}
Key: "Enemy" Value: {"Player", "PlayerWeapon"}
Key: "PlayerWeapon" Value: {"Enemy"}
Key: "Powerup" Value: {"Player"}
等等。
addCollidingPair方法用於寫入collisionMap Dictionary。我們將在main.mxml文件中的
creationComplete方法中調用。
checkCollison方法是碰撞檢測進行的地方。看上去有些複雜,實際上很簡單。
首先是循環gameObjects容器兩次,把每一個GameObject與其它進行一次比較,進行下列檢查:
·GameObject的collisionName是否是‘None’?非‘None’才參與碰撞檢測。
·GameObject是否是活動狀態?
·collisionNames是否註冊到collisionMap的colliders中了?collisionMap決定了哪些GameObject進行
碰撞。
    當這些檢查都爲真,我們使用矩形相交算法來判斷物體間是否真的碰撞了。如果碰撞了,就執行他們
各自的collision方程。
如我之前所提到,碰撞檢測是一個相當複雜的課題,有很多方法可以進行優化。我們目前使用的是一個非
常簡單的方法,並不是這篇文章中的亮點。不過由於我們畫面中的對象大約24個而已,因此該方法是有效
的。
爲了檢測所有的碰撞,我們需要在createComplete方法中加入addCollidingPair的調用。看看做了哪些改
變:
main.mxml<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="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">
<mx:states>
<mx:State
name="Game"
enterState="enterGame(event)"
exitState="exitGame(event)">
</mx:State>
<mx:State name="MainMenu">
<mx:AddChild relativeTo="{myCanvas}" position="lastChild">
<mx:Button x="525" y="368" label="Start" id="btnStart" click="startGameClicked(event)"/>
</mx:AddChild>
</mx:State>
</mx:states>
<mx:Canvas x="0" y="0" width="100%" height="100%" id="myCanvas"/>
<mx:Script>
<![CDATA[
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. beginBitmapFill(GameObjectManager.Instance.backBuffer, null, false,
false);
myCanvas.graphics.drawRect(0, 0, this.width, this.height);
myCanvas.graphics.endFill();
}
}
private function click(event:MouseEvent):void
{
GameObjectManager.Instance.click(event);
}
private function mouseDown(event:MouseEvent):void
{
GameObjectManager.Instance.mouseDown(event);
}
private function mouseUp(event:MouseEvent):void
{
GameObjectManager.Instance.mouseUp(event);
}
private 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;
}
]]>
</mx:Script>
</mx:Application>
Read more: http://lp2.fanqiang.org/browse.php?
u=Oi8vd3d3LmJyaWdodGh1Yi5jb20vaW50ZXJuZXQvd2ViLWRldmVsb3BtZW50L2FydGljbGVzLzExODg5LmFzcHg%
2FcD00&b=5#ixzz0Q6b4QzFf
複製代碼任意兩個物體只需要調用一次addCollidingPair方法。玩家飛船將與敵人檢測碰撞,敵人將和子
彈進行碰撞,玩家飛船也將與敵人進行碰撞。
接下來我們我們要更新Player, Weapon和Enemy類的collisionName和響應碰撞。接下來我們看Player類
Player.aspackage
{
import flash.events.*;
import flash.geom.*;
import mx.core.*;
public class Player extends GameObject
{
protected static const TimeBetweenShots:Number = 0.25;
protected var shooting:Boolean = false;
protected var timeToNextShot:Number = 0;
public function Player()
{
}
public function startupPlayer():void
{
startupGameObject(ResourceManager.BrownPlaneGraphics, new Point
(Application.application.width / 2, Application.application.height / 2),
ZOrders.PlayerZOrder);
shooting = false;
timeToNextShot = 0;
this.collisionName = CollisionIdentifiers.PLAYER;
}
override public function shutdown():void
{
super.shutdown();
}
override public function enterFrame(dt:Number):void
{
super.enterFrame(dt);
timeToNextShot -= dt;
if (timeToNextShot <= 0 && shooting)
{
timeToNextShot = TimeBetweenShots;
var weapon:Weapon = Weapon.pool.ItemFromPool as Weapon;
weapon.startupBasicWeapon(
ResourceManager.TwoBulletsGraphics,
new Point(
position.x + graphics.bitmap.width / 2 - ResourceManager.TwoBulletsGraphics.bitmap.width /
2,
position.y - graphics.bitmap.height + ResourceManager.TwoBulletsGraphics.bitmap.height * 2),
150,
true);
}
}
override public function mouseMove(event:MouseEvent):void
{
// move player to mouse position
position.x = event.stageX;
position.y = event.stageY;
// keep player on the screen
if (position.x < 0)
position.x = 0;
if (position.x > Application.application.width - graphics.bitmap.width)
position.x = Application.application.width - graphics.bitmap.width;
if (position.y < 0)
position.y = 0;
if (position.y > Application.application.height - graphics.bitmap.height )
position.y = Application.application.height - graphics.bitmap.height ;
}
override public function mouseDown(event:MouseEvent):void
{
shooting = true;
}
override public function mouseUp(event:MouseEvent):void
{
shooting = false;
}
override public function collision(other:GameObject):void
{
Level.Instance.levelEnd = true;
this.shutdown();
}
}
}
Read more: http://lp2.fanqiang.org/browse.php?
u=Oi8vd3d3LmJyaWdodGh1Yi5jb20vaW50ZXJuZXQvd2ViLWRldmVsb3BtZW50L2FydGljbGVzLzExODg5LmFzcHg%
2FcD01&b=5#ixzz0Q6eJzeNp
複製代碼有兩處改變讓其對碰撞檢測系統進行適應。第一個是startup方法中設置了collisionName。第二
個是加入collision方法,這個方法將被GameObjectManager在碰撞發生時調用。這裏我們注意,Level在
設置levelEnd爲true後應當停止(玩家掛了),我們還要調用shutdown來去掉玩家。
除了levelEnd的改變外,Enemy和Weapon類完全一致,這裏我(作者,不是譯者)偷懶就不再展示了。
最後是Level類的改變,如下:
Level.aspackage
{
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.BackgoundZOrder,
50);
}
// add an emeny
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 cloudBackgroundLevelElement: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";
}
}
}
Read more: http://lp2.fanqiang.org/browse.php?
u=Oi8vd3d3LmJyaWdodGh1Yi5jb20vaW50ZXJuZXQvd2ViLWRldmVsb3BtZW50L2FydGljbGVzLzExODg5LmFzcHg%
2FcD02&b=5#ixzz0Q6g1pVM4
複製代碼主要改變就是當玩家掛掉時通過levelEnd通知Level。當被設置爲true時,enterFrame中使用
timeToLevelEnd屬性,進行倒數,當timeToLevelEnd爲0時,state回到MainMenu(菜單畫面)。
碰撞檢測在任何一個動作遊戲中都很重要。我們目前用得比較簡單,不過很有效。不幸的是,現在擊中敵
機只會簡單的消失,在第7部分中,我們將加入動畫效果。
結果: http://flexfighters.sourceforge.net/flexfighters6.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章