Flash Game Development with Flex and Actionscript(No.9)

In this article we implement a system to define a level within the game.

 

在这篇文章里我们将在游戏里实现一个自定义的一个游戏级别。

 

To this point our "level" has really just been one endless stream of randomly placed enemies. Obviously this is not ideal as it gives us as the developer no control over how the level plays out. In order for us to provide a structured level design we need a way to define and save that level structure. It sounds like a simple challenge, but we have many options each with their own advantages and disadvantages.

 

基于这个“等级”真应该会无限的随机出现敌机。显然这个对于我们程序员来说不是什么好主意,因为我们不知道玩家究竟能玩多少等级。而我们需要一种自定义且能保存的不可逆的级别设计。它听起来像是一个简单的挑战。(但要真正的想去实现它)说是一件事,做是另外一件事。

 

My first instinct was to define the level in XML. Actionscript has excellent support for XML, allowing you to declare an XML variable directly in the code. Similarly Actionscript also provides an easy interface for traversing an XML document. The downside to using XML is that you need to supply the code to interpret the XML nodes. For exampe you may have an XML node that defines an enemy placement. To turn this node into an actual object the XML attributes or child nodes need to be parsed, stored and then passed into the function used to actually create the enemy object. While not a difficult task, it is tedious to write.

 

我第一直觉是想把级别自定义到xml文件里。AS语言已经非常完美的支持XML,允许你去声明一个XML变量直接放在代码里。同样AS也提供一个穿越XML文档的一个接口。用XML你就要写代码去转译XML里的节点。例如:你可能有一个敌机布置节点的XML文件。把XML里的属性或子节点转化成一个实物对象需要支解析。存储的和目前函数用到的都要创建成实物。如果不是一个复杂的项目,那么去写的话是太啰嗦啦!

 

Thankfully Actionscript gives us another possibility. By using a Function object Actionscript can treat any function as an object, which can then be passed around and stored like any other object. What's more we can assign anonymous functions to the Function constructor. We can use this to create an anonymous function that directly creates a new enemy object, which is then stored in a Function object to be called at a certain point during the level. This sounds complicated, but will become clearer with some example code.

The first class we need to create to store our level definitions is the LevelDefinitionElement. Lets look at that code now.

 

LevelDefinitionElement.as

package

{

public class LevelDefinitionElement

{

public var time:Number = 0;

public var func:Function = null;

public function LevelDefinitionElement(time:Number, func:Function)

{

this.time = time;

this.func = func;

}

static public function sort(objectA:LevelDefinitionElement, objectB:LevelDefinitionElement):int

{

if (objectA.time < objectB.time) return -1;

if (objectA.time == objectB.time) return 0;

return 1;

}

}

}

 

The purpose of this class is to save a Function object that will be called after a certain amount of time has passed in the level. For example you might want to create an enemy fighter that appears 10 seconds into the game.

It has two properties: time and func. The time property defines the point during the level when the func Function will be called. The func property contains a function to be executed. This function can do anything from creating a new enemy, creating a new background element, playing a sound effect etc. In fact because we can assign any function to this property we have increadible flexibility over how we define the level structure.

The sort function is used to sort the LevelDefinitionElements in an array, with those with a smaller time appearing before those with a larger time.

The LevelDefinitions class will serve as a container for the many LevelDefinitionElements that we will eventually need to define a cmplete level structure. Lets look at that code now.

 

LevelDefinitions.as

package

{

import flash.geom.*;

import flash.utils.Dictionary;

public class LevelDefinitions

{

protected static var instance:LevelDefinitions = null;

protected var levelDefinitions:Dictionary = new Dictionary();

static public function get Instance():LevelDefinitions

{

if ( instance == null )

instance = new LevelDefinitions();

return instance;

}

public function LevelDefinitions()

{

}

public function addLevelDefinition(levelID:int, element:LevelDefinitionElement):void

{

if (levelDefinitions[levelID] == null)

levelDefinitions[levelID] = new Array();

(levelDefinitions[levelID] as Array).push(element);

levelDefinitions[levelID].sort(LevelDefinitionElement.sort);

}

public function getNextLevelDefinitionElements(levelID:int, lastTime:Number):Array

{

var returnArray:Array = new Array();

var nextTime:Number = -1;

if (levelDefinitions[levelID] != null)

{

for each (var levelDefElement:LevelDefinitionElement in levelDefinitions[levelID])

{

if (levelDefElement.time > lastTime && nextTime == -1)

{

returnArray.push(levelDefElement);

nextTime = levelDefElement.time;

}

else if (levelDefElement.time == nextTime)

{

returnArray.push(levelDefElement);

}

else if (levelDefElement.time > nextTime && nextTime != -1)

break;

}

}

return returnArray.length == 0?null:returnArray;

}

public function getNextLevelID(levelID:int):int

{

if (levelDefinitions[levelID + 1] == null) return 0;

return levelID + 1;

}

public function startup():void

{

GameObjectManager.Instance.addCollidingPair( CollisionIdentifiers.PLAYER, CollisionIdentifiers.ENEMY);

GameObjectManager.Instance.addCollidingPair( CollisionIdentifiers.ENEMY, CollisionIdentifiers.PLAYERWEAPON);

GameObjectManager.Instance.addCollidingPair( CollisionIdentifiers.PLAYER, CollisionIdentifiers.ENEMYWEAPON);

LevelDefinitions.Instance.addLevelDefinition(

1,

new LevelDefinitionElement(

1,

function():void {(Enemy.pool.ItemFromPool as Enemy).startupBasicEnemy(

ResourceManager.SmallBluePlaneGraphics,

new Point(100, -ResourceManager. SmallBluePlaneGraphics.bitmap.height),

55);}));

LevelDefinitions.Instance.addLevelDefinition(

1,

new LevelDefinitionElement(

4,

function():void

{

for each (var xPos:int in [150, 350])

{

(Enemy.pool.ItemFromPool as Enemy).startupBasicEnemy(

ResourceManager.SmallBluePlaneGraphics,

new Point(xPos, -ResourceManager. SmallBluePlaneGraphics.bitmap.height),

55);

}

}

));

LevelDefinitions.Instance.addLevelDefinition(

1,

new LevelDefinitionElement(

5,

function():void {(Enemy.pool.ItemFromPool as Enemy).startupBasicEnemy(

ResourceManager.SmallBluePlaneGraphics,

new Point(500, -ResourceManager. SmallBluePlaneGraphics.bitmap.height),

55);}));

 

LevelDefinitions.Instance.addLevelDefinition(

2,

new LevelDefinitionElement(

1,

function():void {(Enemy.pool.ItemFromPool as Enemy).startupBasicEnemy(

ResourceManager.SmallGreenPlaneGraphics,

new Point(100, -ResourceManager. SmallGreenPlaneGraphics.bitmap.height),

55);}));

LevelDefinitions.Instance.addLevelDefinition(

2,

new LevelDefinitionElement(

3,

function():void

{

for each (var xPos:int in [150, 350])

{

(Enemy.pool.ItemFromPool as Enemy).startupBasicEnemy(

ResourceManager.SmallGreenPlaneGraphics,

new Point(xPos, -ResourceManager. SmallGreenPlaneGraphics.bitmap.height),

55);

}

}

));

LevelDefinitions.Instance.addLevelDefinition(

2,

new LevelDefinitionElement(

5,

function():void {(Enemy.pool.ItemFromPool as Enemy).startupBasicEnemy(

ResourceManager.SmallGreenPlaneGraphics,

new Point(500, -ResourceManager. SmallGreenPlaneGraphics.bitmap.height),

55);}));

}

public function shutdown():void

{

}

}

}

 

Apart from the singelton property there is only one other property: levelDefinitions. This is a dictionary, where the key is the level ID (level 1, 2, 3 etc), and the value is an array of LevelDefinitionElements. Remember that a LevelDefinitionElement defines a function to be called after a certain amount of time has passed in a level. So a call to levelDefinitions[1][0].func() would effectively mean that we are calling the function (levelDefinitions[1][0].func()) stored by the earliest level element (levelDefinitions[1][0].func() - remember they are sorted in accending order by the time property) stored for level 1(levelDefinitions[1][0].func()).

This should become clearer once we look at the startup function. Lets disect one of the enemy placements.

LevelDefinitions.Instance.addLevelDefinition(

1,

new LevelDefinitionElement(

4,

function():void

{

for each (var xPos:int in [150, 350])

{

(Enemy.pool.ItemFromPool as Enemy).startupBasicEnemy(

ResourceManager.SmallBluePlaneGraphics,

new Point(xPos, -ResourceManager.SmallBluePlaneGraphics.bitmap.height),

55);

}

}

));

 

We are making a call to addLevelDefinition, which essentially just adds a LevelDefinitionElement to the levelDefinitions dictionary. The first variable we pass is the level ID. We are passing 1, which means that we are adding a placement to the first level.

The second variable we pass in is a LevelDefinitionElement. The first variable in that constructor is the time when this LevelDefinitionElement should be executed. We are passing in 4, which means that this LevelDefinitionElement will be executed 4 seconds into the level.

The second variable we pass to the LevelDefinitionElement constructor is an anonymous function. An anonymous function doesn't have a name, so you can only call it through a Function object. Otherwise the code is exactly the same as a normal function. As you can see the function we have here creates two new enemy objects.

The end result of this is that after 4 seconds in the first level two new enemies will be created.

Once you wrap you head around using functions as objects it should be clear that this allows us an amazingly simple way to define our level structure. We are not constrained by a simple XML format - anything that can be done with Actionscript can be scripted to occur at a certain point during a level. We have used the code to create a new enemy here, but the possibilities are endless.

(A small change too is that we have moved the definitions of the colliding GameObject pairs into the startup function from the Application object. This is to keep all game "definition" code in one place.)

Of course we need a way of calling these functions at the correct point in the game. The Level class will take care of that. Lets look at that code now.

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值