您的位置:首页 > 其它

使用FLEX和Actionscript开发FLASH 游戏-使用输入和动态背景

2009-06-10 15:19 591 查看
在本系列第三部分我们最终得以在屏幕上画一些图形。在第四部分我们将多画图形而且使得游戏对象呈现交互性。我们还将生成一个背景级别使得游戏者可以在其上面飞行。

大多数Flash游戏自然非常简单。它们是5至10分钟的小娱乐,人们在午餐时分可以玩它,或者在老板看不见的时候。这种简单性在Flash游戏的典型控制方案中反映出来:鼠标输入只使用左键单击。这是很直观的(人们抽出他们时间的几分钟来玩游戏可不想阅读一个写着复杂控制方案的帮助页面),总之Flash不会让你(简单地)使用鼠标右键。

谢天谢地我们游戏中的俯视角射手风格非常好地适于这种简单地控制方案。游戏者仅仅在屏幕上到处移动鼠标来移动游戏者飞船,通过单击鼠标左键来发射子弹。但是在我们生成一个代表游戏者飞船的游戏对象之前我们先需要一种能探测鼠标移动到哪了鼠标何时被点击了的方法。让我们看看完成上述功能需要在main.mxml文件中做些什么改变。

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

{

}

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.shutddown();

inGame=false;

}

]]>

</mx:Script>

</mx:Application>

我们增加了函数来侦听四个新的事件:mouseMove,click,mouseDown和mouseUp。MouseMove,就如同你所希望的,在鼠标移动时被调用。它允许我们通过flash游戏窗口来监听鼠标光标的位置。相似地click在鼠标按钮被点击(即按下并且释放)时监听。当鼠标按钮被按下mouseDown作用,而当鼠标按钮被释放时mouseUp作用。我们特别地需要监听mouseDown和mouseUp事件的能力(click事件与之不同,它在鼠标被点击而且接着释放时调用)因为最终用我们希望鼠标按钮被按下时游戏者开火而当鼠标按钮被释放时停止开火。

四个新函数的事件有着同样的名称作为各自的事件,仅仅将消息传给GameObjectManager。

GameObjectManager.as

package

{

import flash.display.*;

import flash.events.*;

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 of the 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();

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 shoule be instantiated");

backBuffer=new BitmapData (Appliction.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);

//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 drawObject():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):viod

{

removeGameObjects.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||gameObject.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.removedItemAt(i);

break;

}

}

}

removedGameObjects.removedAll();

}

}

}

正如你所看见的GameObjectManager有了4个新的函数来反映应用程序对象里的4个新的鼠标事件。GameObjectManager依次在gameObjects集合上循环给当前处于活动状态的任何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);
//heigher 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 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);
}
}
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
{
}
}
}
GameObject类也增加了4个同样的新函数。这些函数也是空函数因为,如同enterFrame函数一样,继承GameObject类的类将把自己的逻辑放进函数里。
所以现在我们能反应鼠标事件。让我们看看这是如何做到生成一个游戏者的飞船来在屏幕四处移动的。
Player.as
package
{
import flash.events.*;
import flash.geom.*;
import mx.core.*;
import mx.events.MoveEvent;
public class Player extends GameObject
{
public function Player()
{
}
public function startupPlayer():void
{
startupGameObject(ResourceManager.BrownPlaneGraphics,new Point (Application.application.width/2,Application.application.height/2),
ZOrders.PlayerZOrder);
}
override public function shutdown():void
{
super.shutdown();
}
override public function enterFrame(dt:Number):void
{
super.enterFrame(dt);
}
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);
}
}
}
在第三部分我们生成了一个类Bounce。它一直做着直线运动直到碰到屏幕边框,然后在它碰到边框后它朝着相反的方向走。游戏者类和它惊人地相似,除了这次不是以直线运动(在enterFrame函数里)而且游戏者移动到鼠标光标所在处(在mouseMove函数里)。不是碰到屏幕边缘向回移动,而是游戏者移动到屏幕边缘然后停止。
你可能注意到startupGameObject函数里提到ZOrders类。zOrder定义了在屏幕上对象的深度,低一些的zOrder对象被画在高一些的zOrder对象下面。ZOrders类仅仅保存了一些标准的zOrder值。这有助于自文档化的水平:ZOrders.PlayerZOrder指的是什么立即就显而易见了,然而要得知值为10的平地的意义则需要查文档或者直接查找源代码。
ZOrders.as
package
{
public final class ZOrders
{
public static const PlayerZOrder:int=10;
public static const BackgroundZOrder:int=0;
public static const CloudsBelowZOrder:int=5;
public static const CloudsAboveZOrder:int=15;
}
}
通过侦听鼠标事件我们能够用Player类来替换Bounce类来随着鼠标到处移动。现在让我们为游戏者飞机制造一个在海上飞行的幻觉。
BackgroundLevelElement.as
package
{
import flash.geom.Point;
import mx.core.*;
import mx.collections.*;
public class BackgroundLevelElement extends GameObject
{
static public var pool:ResourcePool=new ResourcePool(NewBackgroundLevelElement);
protected var scrollRate:Number=0;
static public function NewBackgroundLevelElement():GameObject
{
return new BackgroundLevelElement();
}
public function BackgroundLevelElement()
{
}
public function startupBackgroundLevelElement

(bitmap:GraphicsResource,position:Point,z:int,scrollRate:Number):void
{
startupGameObject(bitmap,position,z);
this.scrollRate=scrollRate;
}
public override function enterFrame(dt:Number):void
{
if(position.y>Application.application.height+graphics.bitmap.height)
this.shutdown();
position.y+=scrollRate*dt;
}
}
}
功能上BackgroundLevelElement和第三部分的Bounce类非常相似。它继承了GameObject类在屏幕上显示一幅图片,在enterFrame函数功能下做直线运动。不像Bounce对象,BackgroundLevelElement对象在shutdown函数调用时最终完全从屏幕中移走。这将一个对象返还到ResourcePool(资源池)中,以准备重用。
那么什么是资源池?要解释这个我们必须先解释内存管理的基本概念。
在最简单的形式,内存管理是一些进程,用来在内存中生成新对象以及在不再需要对象时把 它们从内存中移去(或者销毁它们)。高级语言比如C++希望你手动地保留任何你生成的新对象的轨迹,然后以后再手动地销毁它们。没有那样做的话将导致内存泄露。有种情况是对象从来不被销毁导致一个程序的内存使用量不断地增长。大部分的现代语言(包括ActionScript)使用一个垃圾收集器来销毁不再被引用的对象,这样自动调用销毁对象的进程。
表面上看来垃圾收集器像是一个理想的解决方案,但是它有自己的缺点。然而C++中如果对象不被手工销毁将存在一个内存泄漏,ActionScript中如果对象在不再被需要后无意中被引用将造成同样的问题。语义不同但结果一样:持续增长的内存使用量。垃圾收集器的另一个负面影响是对象不被确定地销毁,这是一个幻想的方式说:你作为一个开发者没有办法确切地知道一个不再被引用的对象实际上被何时销毁。垃圾收集器也导致性能下降。用垃圾收集器管理成千上百的对象并销毁那些不再被引用的对象需要花费时间,不过不管是否它可被察觉还是不可被察觉,它发生时都不会造成很大影响。
资源池解决许多这种问题。基本上它保留一个对象的集合在池里。当对象被需要时它们被取出资源池而且被初始化(注意初始化同新函数里被从头开始生成是不一样的)。一旦它们不再被需要它们就被清除而且放回到池里。
通过在池里保留对象你可以避免不断地生成新的对象。这反过来减少了在内存中存在的对象的数目,这将减少垃圾收集器的工作量。而且,通过用确定的方式来生成和销毁资源(当对象被从池中取出,且在它被返还之前)我们获得内存管理进程的更多控制。
ResourcePool.as
package
{
import mx.collections.*;
public class ResourcePool
{
protected var pool:ArrayCollection=new ArrayCollection();
protected var newObject:Function=null;
public function ResourcePool(newObject:Function)
{
this.newObject=newObject;
}
public function get ItemFromPool():GameObject
{
for each(var item:GameObject in pool)
{
if(!item.inuse)
return item;
}
var newItem:GameObject=newObject();
pool.addItem(newItem);
return newItem;
}
}
}
如果你认为资源池的文字表达有点猛,你会发现实际代码相当简单。资源池有两个属性。池属性包括一个由池对象生成的游戏对象的集合。新对象属性指向一个函数在池中没有自由的对象存在时生成一个新游戏对象。ItemFromPool函数被用来从池中获得一个没有使用的GameObject.它在池集合上循环来返回最先找到的没有使用的(也即inuse值为假)对象。如果没有找到则调用newObject函数来生成一个新的GameObject,把它加入到池中,然后返回它。
回顾一下BackgroundLevelElement类你将发现我们已经生成了一个函数名为NewBackgroundLevelElement.这个函数生成了一个新的BackgroundLevelElement,而且这个函数被传递给ResourcePool的构造器,这允许资源池在池被耗尽时生成新的BackgroundLevelElements。
生成了BackgroundLevelElement类后我们现在需要某种方式来把它们加到游戏中。为了这样我们生成Level类。让我们看看代码。
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 var timeToNextLevelElement:Number=0;
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);
}
public function startup():void
{
timeToNextLevelElement=0;
new Player().startupPlayer();
}
public function shutdown():void
{
}
public function enterFrame(dt:Number):void
{
timeToNextLevelElement-=dt;
if(timeToNextLevelElement<=0)
{
timeToNextLevelElement=TimeBetweenLevelElements;
var graphics:GraphicsResource=levelElementGraphics.getItemAt
(MathUtiles.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);
}
}
}
}
如同GameObjectManager一样,Level类用一个实例属性和一个实例函数来实现了单态设计战略。TimeBetweenLevelElements属性定义了在添加新的BackgroundLevelElements到屏幕之间要等待多久的时间,而timeToNextLevelElement属性保留了最后一个BackgroundLevelElement被添加到至今的数目。levelElementGraphics属性保留了当生成新的BackgroundLevelElements时我们随机选择的GraphicsResources 集合。
像大多数类一样Level类保留了startup和shutdown函数。正是在startup函数里(在应用程序对象中当我们转换到游戏状态时被调用)我们生成了游戏者。我们也有个enterFrame函数。就好像GameObject,Level类的enterFrame函数在每帧被GameObjectManager调用。正是在此处我们定期地从资源池中取出不用的BackgroundLevelElement而且用startupBackgroundLevelElement函数来初始化它。
通过增加用户输入和继承第三部分的Bounce类的概念生成一个滚动的背景我们生成了一个开始看起来像游戏的东西。在本系列第五部分我们将给屏幕增加一些敌人,给游戏者添加一些武器。
http://flexfighters.sourceforge.net/flexfighter4.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/4241484.aspx

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/literza/archive/2009/06/05/4241459.aspx
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: