In part one of the series we created the initial Flex application class. In part two we will be adding states and a double buffered rendering process.
在这第二部分里我们将在程序里添加状态和两个缓存内容
States are quite self explanatory: they represent the different states that a program can be in. For example a shopping cart might have one state for browsing the store and another state while looking at the details of a particular item. Our game will also have a number of states which will include the main menu, the game play itself, the end of level summary and maybe a high score screen.
“状态”一词字面上就包解释了本身的含义:它表明在这个程序里面会出现不同的状态。例如(在商城购物时)你的购物车当你在浏览商品时会有一种状态(车上有商品),然而还有一种状态就是把它看成一个特定的物件时(车上不会有商品)。我们的游戏里面也可以加入几个状态在主菜单里,这个游戏在运行时,等级的简述和最高分这些信息都会根据状态显示。
Flex includes native support for states. These states were designed with a transition from one GUI to another in mind, but they have the functionality we need to change between states that don't necessarily have any GUI components. Modifying the currentState property of the Application will trigger a state change, and by adding the required startup and shutdown code to the functions associated with the enterState and exitState events we can update the internal game state to match.
Flex天生就支持状态。
Double Buffering is a technique used to remove the visual tearing associated with drawing directly to the screen. It gets its name because you use two graphics buffers to draw the final image: one that resides in-memory (the back buffer), and the buffer which is displayed on the screen (front buffer). You can think of the back buffer as a kind of scratch pad which is built up as the individual elements that make up the final scene write to it. Once a frame has been drawn it is copied to the front buffer in one operation. The screen then displays the contents of the front buffer.
(两个缓存的技术经常是用到把可见的信息直接显示到屏幕上。由于一个在内存里(称之为后台缓存),一个显示在当前屏幕上的(称之为屏幕缓存),你可以直接获取它的名称来把要显示的图像显示在屏幕上。你可以把后台缓存当成组建前台显示的一个过程。一但前台需要直接拷贝后台缓存里的内容,把后台内容赋值给前台显示!)
So let's look at how these concepts are implemented in Flex.
ok 让我们看一下如何用Flex实现我们理论上的东西。
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)"
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();
}
}
protected function startGameClicked(event:Event):void
{
currentState = "Game"
}
protected function enterGame(event:Event):void
{
GameObjectManager.Instance.startup();
inGame = true;
}
protected function exitGame(event:Event):void
{
inGame = false;
}
]]>
</mx:Script>
</mx:Application>
The first thing to notice is the addition of the currentState property to the mx:Application element. As mentioned above, the currentState property of the Application object defines the current state of the program. By setting it to MainMenu through this attribute we are saying that the program should start in the MainMenu state.
We have also added a mx:States element. This element defines the states that the program can take through the child mx:State elements. We have defined two states to start with: MainMenu and Game. In the MainMenu state the end user will see the starting screen of the game. The Game state represents the game play itself.
Both mx:State elements have a name property. This property is the name of the state, and by changing the currentState property of the Application object to this name we can transition into the state. The Game state also includes two more properties: enterState and exitState. By associating functions with these events we have an opportunity to manually 鈥渟ync鈥?the internal game logic to this state. As you can see we use the EnterGame function to startup the GameObjectManager (more on that class later) and set the internal flag inGame to true. The inGame flag is used during the rendering loop to allow the game to draw to the screen. The ExitGame function simply sets the inGame flag to false, which allows a GUI to be displayed.
Remember how I mentioned that the states in Flex were designed with GUI transitions in mind? The MainMenu state shows how easy this is. The mx:AddChild element is used to add a GUI element to the state. In this case we use it to add a button that the player can click to get into the game. But as soon as we leave the MainMenu state Flex will automatically remove the button without any additional code or effort.
To allow us to render to the screen we have added a mx:Canvas element. The canvas (or more specifically its graphics property) will serve as the front buffer in the double buffering rendering process. The back buffer exists in the GameObjectManager class. During the enterFrame function we call the GameObjectManager鈥檚 enterFrame function, which will allow it to draw the back buffer. Once the frame has been drawn we take the back buffer and draw it onto the canvas using the clear, beginBitmapFill, drawRect and endFill functions of the canvas鈥檚 graphics property.
GameObjectManager.as
package
{
import mx.core.*;
import mx.collections.*;
import flash.display.*;
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;
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
{
}
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;
drawObjects();
}
protected function drawObjects():void
{
backBuffer.fillRect(backBuffer.rect, clearColor);
}
}
}
The GameObjectManager object will be responsible for managing the elements that will make up the final game like the enemies, the player and the various background elements. It is also responsible for managing the back buffer to which these elements will draw themselves to. If you recall the front buffer was implemented as a canvas element. This was for convenience as a canvas can be added directly as a child of the Application object. The back buffer is implemented as a BitmapData object, which allows us to quickly and directly manipulate the pixels that make up the final image.
The clearColor property specifies the colour that will be used to wipe the back buffer before the scene is built up. Eventually the entire back buffer will be overwritten by the game elements, which makes this color irrelevant, but for now it is quite important because it will make up a good chunk of the final frame. The value 0xFF0043AB is a dark blue. The first two hex values (those after the 0x) represent the alpha: FF for opaque and 00 for transparent. The next 6 hex values make up the red (00), green (43) and blue (AB) components.
The static instance property is used with the Instance function to implement the Singleton design pattern. Basically we only ever want one GameObjectManager to exist in the program (hence the name), and by referencing the GameObjectManager through this instance property we can be assured that only one GameObjectManager will ever be created. The Singleton design is quite a common programming paradigm, and while ActionScript lacks support for a protected constructor it still useful as a self documentation tool (if you ever see an Instance property, chances are that the object is designed as a Singelton).
The lastFrame property simply stores the time when the last frame was renedered. By keeping a track of this time we can determine how long it has taken between the last frame and this current one, which (will eventually) in turn allows us to update the game elements by this amount. Even though we don't have any game elements yet the time between frames is calculated in seconds during the enterFrame function. The lastFrame time is reset during a call to startup. This is because the GameObjectmanager is not updated while the program is not in the Game state. If we didn't reset lastFrame the first frame of the next level would be equal to the time the player spent in the menus inbetween levels. The player could end up jumping halfway across the level during the first frame, which is definitely best avoided.
So, what have we achieved here? By implementing states we have created a menu screen, from which the player can enter the game by clicking a button. The 鈥済ame鈥?itself is just an implantation of a double buffer, which draws a blue background. However with the states and rendering implemented we can finally get to the fun stuff: drawing to the screen.