使用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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值