使用Flex和Actionscript开发Flash游戏―(十)
2010年07月11日
这一部分,我们要加入一个可以滚动的重复的背景。
第9部分中,我们添加了关卡的按时调用功能构成的关卡结构。这个绘制主要用来显示界面上的敌机,但是对于背景的显示并不是很有用。这个部分,我们要绘制预定义的可重复的背景。
重复背景由一系列很小的可重复的图片排列而成,在这里,就是按网格排列。这样做有几个优点,最重要的就是可以减小内存的需求。用一张很小的图像来渲染背景,每一关能减小内存好几兆,同时保留了高细节的呈现。只不过背景并不响应单块的互动事件,只是被关卡载入。与此相对,重复背景占用内存小,设计师同样可以利用这一特点制作出相当好看的背景。
第一步就是绘制一个可自重复的背景图片。我发现了很多不错的图片 http://lostgarden.com/labels/free%20game%20graphics.html。站点上还有一些其它有趣的资源。
下一步是找一个关卡编辑器,提供我们交互界面操作。当然也可以自己开发(这个可以再写一系列文章了)。幸好,有人已经做了这个工作。TaT自重复地图编辑器:http://kotisivu.dnainternet.net/ttilli/tilemapedit or/download.htm
其中的层编辑和xml导出都很好用。
当然,我们需要在游戏中加代码了~首先是存储背景数据。TiledBackgroundDefinition类来了~看看代码:
[b]TiledBackgroundDefinition.as
[/b]package
{
public class TiledBackgroundDefinition
{
public var tiles:Array = null;
public var tileScrollRate:Number = 0;
public var tileWidth:int = 0;
public var tileHeight:int = 0;
}
} 复制代码
tiles属性是一个多维数组,包含了GraphicsResources的引用,这些用来绘制背景,三维分别表示层、行和列。例如:tiles[1][4][5]指向GraphicsResource到第六列,第五行,第二层――从0开始索引的。tileWidth和tileHeight属性定义了关卡北京的尺寸。tileScrollRate定义了关卡滚动速度。
现在我们可以存储定义自重复背景了。LevelDefinitions类用来存储定义。让我们看看代码:
[b]LevelDefinitions.as [/b]
LevelDefinitions.as
package
{
import flash
.geom.*;
import flash.utils.*;
public class LevelDefinitions
{
protected static var instance:LevelDefinitions = null;
protected var levelDefinitions:Dictionary = new Dictionary();
public var levelTileMaps: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(LevelDefinitionElem ent.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);
defineLevel1();
defineLevel2();
}
public function shutdown():void
{
}
protected function defineLevel1():void
{
var level1Tiles:TiledBackgroundDefinition = new TiledBackgroundDefinition();
levelTileMaps[1] = level1Tiles;
level1Tiles.tileScrollRate = 25;
level1Tiles.tileHeight = 40;
level1Tiles.tileWidth = 40;
level1Tiles.tiles =
[
[
[ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1]
...
,[ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1]
]
,[
[null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]
...
,[null, ResourceManager.GreenGraphicsID62, ResourceManager.GreenGraphicsID63, ResourceManager.GreenGraphicsID64, null, ResourceManager.GreenGraphicsID48, ResourceManager.GreenGraphicsID49, null, null, null, null, null, null, null, null]
]
];
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);
}
}
));
...
}
protected function defineLevel2():void
{
...
}
}
}
我们添加了一个新的属性levelTileMaps,这是一个Dictionary类型,用来将TiledBackgroundDefinitions映射至LevelID(和第9部分levelDefinitions属性类似)。
同样的,我们增加了两个新方法:defineLevel1和defineLevel2。这两个方法用于分别定义levels,看上去新的自重复的背景定义有点累赘。在这两个方法中,我们创建TiledBackgroundDefinition对象,初始化它们并赋值到levelTileMaps属性上。
当你使用TaT创建关卡时,会得到两个很重要的文件:tileset.xml(定义了单个的字重复图像)和一个 你关卡的名字.xml文件(定义了图片在关卡中的排列情况)。我们要把 你关卡的名字.xml文件的数据放到tiles数组中。
你可能会奇怪,怎样才能把xml数据存到多维数组中。简单说,我创建另一个程序,将xml转换成Actionscript代码,然后创建数组。为什么不选择在程序中解析XML呢?理由和我不选择用xml定义关卡结构是一样的:解析xml并转换数据类型是一个很复杂的过程,而转换xml为actionscript要简单很多。这里我就不讲解转换程序的代码了,你可以在这里下载到:http://flexfighters.svn.sourcefo ... /TatResourceParser/
TaT编辑器创建的第二个xml定义了组成关卡的单个的自重复图像。使用上面的程序,我们也可以将其转换成actionscript代码,嵌入图像然后用ResourceManager创建与之对应的GraphicsResource对象。新的代码重复部分很多,下面我只写一小部分不一样的:
[b]ResourceManager.as (新代码示例) [/b]
[Embed(source="../media/Green26.png")]
public static var GreenID65:Class;
public static var GreenGraphicsID65:GraphicsResource = new GraphicsResource(new GreenID65());
[Embed(source="../media/Green5.png")]
public static var GreenID11:Class;
public static var GreenGraphicsID11:GraphicsResource = new GraphicsResource(new GreenID11(), 1, 1, new Rectangle(0, 0, 40, 40));
public static var GreenGraphicsID12:GraphicsResource = new GraphicsResource(new GreenID11(), 1, 1, new Rectangle(40, 0, 40, 40));
public static var GreenGraphicsID17:GraphicsResource = new GraphicsResource(new GreenID11(), 1, 1, new Rectangle(0, 40, 40, 40));
public static var GreenGraphicsID18:GraphicsResource = new GraphicsResource(new GreenID11(), 1, 1, new Rectangle(40, 40, 40, 40));
代码与其它资源定义相同,除了传给ResourceManager新的参数。这是为了适应TaT引用的结构――加入关卡的图片比自重复的图片尺寸大。所以,例如一个字重复图片大小是40x40像素,但是整个树的图像可以是40x80像素。在这种情况下,树的图片要引用两个独立的图片,按照定义,一个在上面,一个在下面。为了让GraphicsResouce指向相同大小的区域,我们可以将其指向相同的自重复图片。
现在,我们可以定义并保存自重复图像了。接下来就是要绘制了。请看代码:
[b]TiledBackground.as [/b]
package
{
import flash.display.*;
import flash.geom.*;
import mx.collections.*;
import mx.core.*;
public class TiledBackground extends BaseObject
{
public var scrolling:Boolean = true;
protected var yOffset:Number = 0;
protected var definition:TiledBackgroundDefinition = null;
static public var pool:ResourcePool = new ResourcePool(NewTiledBackground);
static public function NewTiledBackground():TiledBackground
{
return new TiledBackground();
}
public function TiledBackground()
{
super();
}
public function startupTiledBackground(definition:TiledBackgroundD efinition):void
{
super.startupBaseObject(ZOrders.BACKGROUNDZORDER);
this.definition = definition;
this.yOffset = 0;
this.scrolling = true;
}
override public function shutdown():void
{
super.shutdown();
}
override public function enterFrame(dt:Number):void
{
if (scrolling)
{
var mapHeight:int = definition.tiles[0].length * definition.tileHeight;
var mapOverlap:int = mapHeight - Application.application.height;
yOffset += definition.tileScrollRate * dt;
if (yOffset > mapOverlap)
{
scrolling = false;
yOffset = mapOverlap;
}
}
}
override public function copyToBackBuffer(db:BitmapData):void
{
var startRow:int = yOffset / definition.tileHeight;
var startRowNumber:Number = yOffset / definition.tileHeight;
var startRowHeight:int = definition.tileHeight * (startRowNumber - startRow);
var drawnHeight:int = 0;
var drawnWidth:int = 0;
var layer:int = 0;
var row:int = startRow;
var col:int = 0;
// loop through each layer
for (layer = 0; layer = 0 ; --row)
{
// loop through each column of the current row
for (col = 0; col = Application.application.width)
break;
}
drawnWidth = 0;
drawnHeight += definition.tileHeight;
if (drawnHeight >= Application.application.height + definition.tileHeight)
break;
}
drawnHeight = 0;
}
}
}
}
2010年07月11日
这一部分,我们要加入一个可以滚动的重复的背景。
第9部分中,我们添加了关卡的按时调用功能构成的关卡结构。这个绘制主要用来显示界面上的敌机,但是对于背景的显示并不是很有用。这个部分,我们要绘制预定义的可重复的背景。
重复背景由一系列很小的可重复的图片排列而成,在这里,就是按网格排列。这样做有几个优点,最重要的就是可以减小内存的需求。用一张很小的图像来渲染背景,每一关能减小内存好几兆,同时保留了高细节的呈现。只不过背景并不响应单块的互动事件,只是被关卡载入。与此相对,重复背景占用内存小,设计师同样可以利用这一特点制作出相当好看的背景。
第一步就是绘制一个可自重复的背景图片。我发现了很多不错的图片 http://lostgarden.com/labels/free%20game%20graphics.html。站点上还有一些其它有趣的资源。
下一步是找一个关卡编辑器,提供我们交互界面操作。当然也可以自己开发(这个可以再写一系列文章了)。幸好,有人已经做了这个工作。TaT自重复地图编辑器:http://kotisivu.dnainternet.net/ttilli/tilemapedit or/download.htm
其中的层编辑和xml导出都很好用。
当然,我们需要在游戏中加代码了~首先是存储背景数据。TiledBackgroundDefinition类来了~看看代码:
[b]TiledBackgroundDefinition.as
[/b]package
{
public class TiledBackgroundDefinition
{
public var tiles:Array = null;
public var tileScrollRate:Number = 0;
public var tileWidth:int = 0;
public var tileHeight:int = 0;
}
} 复制代码
tiles属性是一个多维数组,包含了GraphicsResources的引用,这些用来绘制背景,三维分别表示层、行和列。例如:tiles[1][4][5]指向GraphicsResource到第六列,第五行,第二层――从0开始索引的。tileWidth和tileHeight属性定义了关卡北京的尺寸。tileScrollRate定义了关卡滚动速度。
现在我们可以存储定义自重复背景了。LevelDefinitions类用来存储定义。让我们看看代码:
[b]LevelDefinitions.as [/b]
LevelDefinitions.as
package
{
import flash
.geom.*;
import flash.utils.*;
public class LevelDefinitions
{
protected static var instance:LevelDefinitions = null;
protected var levelDefinitions:Dictionary = new Dictionary();
public var levelTileMaps: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(LevelDefinitionElem ent.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);
defineLevel1();
defineLevel2();
}
public function shutdown():void
{
}
protected function defineLevel1():void
{
var level1Tiles:TiledBackgroundDefinition = new TiledBackgroundDefinition();
levelTileMaps[1] = level1Tiles;
level1Tiles.tileScrollRate = 25;
level1Tiles.tileHeight = 40;
level1Tiles.tileWidth = 40;
level1Tiles.tiles =
[
[
[ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1]
...
,[ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1, ResourceManager.GreenGraphicsID1]
]
,[
[null, null, null, null, null, null, null, null, null, null, null, null, null, null, null]
...
,[null, ResourceManager.GreenGraphicsID62, ResourceManager.GreenGraphicsID63, ResourceManager.GreenGraphicsID64, null, ResourceManager.GreenGraphicsID48, ResourceManager.GreenGraphicsID49, null, null, null, null, null, null, null, null]
]
];
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);
}
}
));
...
}
protected function defineLevel2():void
{
...
}
}
}
我们添加了一个新的属性levelTileMaps,这是一个Dictionary类型,用来将TiledBackgroundDefinitions映射至LevelID(和第9部分levelDefinitions属性类似)。
同样的,我们增加了两个新方法:defineLevel1和defineLevel2。这两个方法用于分别定义levels,看上去新的自重复的背景定义有点累赘。在这两个方法中,我们创建TiledBackgroundDefinition对象,初始化它们并赋值到levelTileMaps属性上。
当你使用TaT创建关卡时,会得到两个很重要的文件:tileset.xml(定义了单个的字重复图像)和一个 你关卡的名字.xml文件(定义了图片在关卡中的排列情况)。我们要把 你关卡的名字.xml文件的数据放到tiles数组中。
你可能会奇怪,怎样才能把xml数据存到多维数组中。简单说,我创建另一个程序,将xml转换成Actionscript代码,然后创建数组。为什么不选择在程序中解析XML呢?理由和我不选择用xml定义关卡结构是一样的:解析xml并转换数据类型是一个很复杂的过程,而转换xml为actionscript要简单很多。这里我就不讲解转换程序的代码了,你可以在这里下载到:http://flexfighters.svn.sourcefo ... /TatResourceParser/
TaT编辑器创建的第二个xml定义了组成关卡的单个的自重复图像。使用上面的程序,我们也可以将其转换成actionscript代码,嵌入图像然后用ResourceManager创建与之对应的GraphicsResource对象。新的代码重复部分很多,下面我只写一小部分不一样的:
[b]ResourceManager.as (新代码示例) [/b]
[Embed(source="../media/Green26.png")]
public static var GreenID65:Class;
public static var GreenGraphicsID65:GraphicsResource = new GraphicsResource(new GreenID65());
[Embed(source="../media/Green5.png")]
public static var GreenID11:Class;
public static var GreenGraphicsID11:GraphicsResource = new GraphicsResource(new GreenID11(), 1, 1, new Rectangle(0, 0, 40, 40));
public static var GreenGraphicsID12:GraphicsResource = new GraphicsResource(new GreenID11(), 1, 1, new Rectangle(40, 0, 40, 40));
public static var GreenGraphicsID17:GraphicsResource = new GraphicsResource(new GreenID11(), 1, 1, new Rectangle(0, 40, 40, 40));
public static var GreenGraphicsID18:GraphicsResource = new GraphicsResource(new GreenID11(), 1, 1, new Rectangle(40, 40, 40, 40));
代码与其它资源定义相同,除了传给ResourceManager新的参数。这是为了适应TaT引用的结构――加入关卡的图片比自重复的图片尺寸大。所以,例如一个字重复图片大小是40x40像素,但是整个树的图像可以是40x80像素。在这种情况下,树的图片要引用两个独立的图片,按照定义,一个在上面,一个在下面。为了让GraphicsResouce指向相同大小的区域,我们可以将其指向相同的自重复图片。
现在,我们可以定义并保存自重复图像了。接下来就是要绘制了。请看代码:
[b]TiledBackground.as [/b]
package
{
import flash.display.*;
import flash.geom.*;
import mx.collections.*;
import mx.core.*;
public class TiledBackground extends BaseObject
{
public var scrolling:Boolean = true;
protected var yOffset:Number = 0;
protected var definition:TiledBackgroundDefinition = null;
static public var pool:ResourcePool = new ResourcePool(NewTiledBackground);
static public function NewTiledBackground():TiledBackground
{
return new TiledBackground();
}
public function TiledBackground()
{
super();
}
public function startupTiledBackground(definition:TiledBackgroundD efinition):void
{
super.startupBaseObject(ZOrders.BACKGROUNDZORDER);
this.definition = definition;
this.yOffset = 0;
this.scrolling = true;
}
override public function shutdown():void
{
super.shutdown();
}
override public function enterFrame(dt:Number):void
{
if (scrolling)
{
var mapHeight:int = definition.tiles[0].length * definition.tileHeight;
var mapOverlap:int = mapHeight - Application.application.height;
yOffset += definition.tileScrollRate * dt;
if (yOffset > mapOverlap)
{
scrolling = false;
yOffset = mapOverlap;
}
}
}
override public function copyToBackBuffer(db:BitmapData):void
{
var startRow:int = yOffset / definition.tileHeight;
var startRowNumber:Number = yOffset / definition.tileHeight;
var startRowHeight:int = definition.tileHeight * (startRowNumber - startRow);
var drawnHeight:int = 0;
var drawnWidth:int = 0;
var layer:int = 0;
var row:int = startRow;
var col:int = 0;
// loop through each layer
for (layer = 0; layer = 0 ; --row)
{
// loop through each column of the current row
for (col = 0; col = Application.application.width)
break;
}
drawnWidth = 0;
drawnHeight += definition.tileHeight;
if (drawnHeight >= Application.application.height + definition.tileHeight)
break;
}
drawnHeight = 0;
}
}
}
}