Flash 多人在线游戏教程 - TicTacToe

 这个教程教您如何开始制作 自己的多人在线flash游戏!

 
本教程是基于一款经典的回合制游戏Tic-Tac-Toe 。
 
 
开发环境需求
Windows 操作系统
JRE
Flash / Flex3 development environment / FlashDevelop (http://www.flashdevelop.org)
免费 Pulse 开发包,可以从这下载:
         注册后,可在本论坛下载区下载 http://forum.gamantra.com/
在这个开发包中,有一个完整的清单,包含了SDK,代码和资源等。
 
编程基础
如果您已经进行过flash开发和AS3编程,那就足够啦!当然如果您熟悉面向对象方式的编程那就更好了!
 
开始
首先请您下载免费的Pulse 开发包并且安装它。开发包中包括了…
Pulse 服务器
诸多范例程序 (含完整源代码和资源)
Pulse Game 图像界面框架 (含代码和美术资源)
 
]来吧,先启动Pulse服务器!
首先从安装目录或者开始菜单运行Pulse服务器
 
 
 
图A01:从开始菜单开始运行Pulse Server
 
 
 
图A02:从安装目录开始运行 Pulse Server
 
导入SWC组件
首先您需要在您的开发环境(Flash / Flex3 development environment / FlashDevelop)中导入开发包中的SWC组件。
在安装目录下的“lib”目录中,我们提供了四个SWC文件:
pulse.swc ,pulseUI.swc是针对FLEX Builder 的组件。您可以新建自己的项目,并在项目属性的“Library Path”中导入我们的SWC文件。
pulse_cs3.swc,pulseUI_cs3.swc是供Flash CS 使用的组件。您可以将 SWC 文件复制到 Configuration/Components 目录中,然后重启Flash CS ,从组件面板中便可以看到pulse和pulseUI组件,便能使用了。
 
运行游戏范例
范例目录(samples目录)有三个范例:HelloWorld, jigsaw, tictactoe。 在此选择tictactoe做为本教程的游戏。
从范例目录(samples 目录)开始运行游戏范例。请注意在免费服务器版本中,您暂时只能在同一时间内运行一个游戏。如果您想切换不同的游戏范例,需要关闭前面一个游戏的所有客户端程序。
 
[color=beige]Pulse 图形界面框架[/color]
pulse图形界面框架提供了常用的图形界面和控件,如:登陆界面,房间管理界面,聊天界面,朋友关系界面,角色信息界面等等。它的出现让您花更多的时间在游戏编码和设计上,不用浪费时间在这些美术资源的上。对于开发新的多人在线游戏这是不错的方式。
在下载包中有所有这些图形资源;当然,如果你想更换风格或者美术资源,可以随时更换,非常方便。如果您在美术图形框架使用上有任何问题,可以随时发邮件给  [email]pulseui@gamantra.com[/email]。我们乐意帮助您。
 
开始开发游戏
首先您需要考虑的是:如何设计游戏状态(game states),因为它将被一个游戏房间中的所有玩家共用。 
 
为游戏建模
对于tic tac toe游戏,你需要向其他玩家发送玩家动作(player action)信息,这意味着,客户端需要发送x和y  位置信息。.
知道哪些信息是我们必需的之后,接下来我们来建造一个简单的xml文件,这个文件就是负责定义游戏中所需的游戏状态(game state)。
它分为两个小步骤:
1. 建造xml文件
2. 按照你建造的xml文件,使用安装目录下的PulseCodeGen.bat(图A02中第一个文件),自动生成AS3代码。
您可以在安装目录中找到,而且您可以参考整个目录,如图:图A03
 
 
 
图A03:tictactoe完成目录结构,请参考。
 
以下是xml文件内容,文件名我们命名为:tictactoeSchema.xml
[tt]<ObjectSchema>
<import>
<client import="pulse.gsrc.client.*" />
</import>
<class name="Put" parent="GameState" classId="601" >
<public>
<property index="0" name="PutRow" count="1" type="int"/>
<property index="1" name="PutColumn" count="1" type="int"/>
<property index="2" name="PutValue" count="1" type="int"/>
</public>
</class>
</ObjectSchema>[/tt]
 
在这个schema 文件中,我们所需要的东西很简单――想要和其他玩家的动作进行通讯。玩家选择一个位置点(行,列来表示)和值,在tic tac toe中就是画一个“X”或者画一个“O”,它的值由游戏开始时谁先创建了这个房间(游戏房间)的那个人(房间主)所决定。
在完成了游戏状态(game states)设计之后,我们需要通过PulseCodeGen.bat工具运行以上xml文件。以下是我们需要的一个批处理文件(名为:init.bat,您可以在图A03中找到),它用以将PulseCodeGen和tictactoeSchema.xml关联,并运行。
 
[tt]IF EXIST ./src/tictactoe/gsrc/client del ./src/tictactoe/gsrc/client/*.as
CALL "%GAMANTRA%"/bin/PulseCodeGen.bat ./tictactoeSchema.xml tictactoe.gsrc ./src/tictactoe/gsrc
IF NOT %ERRORLEVEL% == 0 GOTO ERROR
ECHO Success!
GOTO END
:ERROR
ECHO oops!
:END
Pause[/tt]
 
运行: init.bat  就可以了。
 
如果您想改变schema文件,那么只需重新运行以上批处理文件(init.bat),就可以迅速生成最新的AS3代码。
通过以上建模器读tictactoeSchema.xml文件生成的AS3类文件,可以在“samples/tictactoe/src/tictactoe/gsrc/client”中找到,如“PutClient.as”。
这个类就是继承自GameStateClient类,有一个地方你很可能用到这个对象:GameClient.sendGameStateAction(gameState:GameStateClient)方法。
可以在API中看到这个方法。 为什么要用这个方法呢?
其实,开发者可以用这个方法向同一个游戏房间中的另一个客户端发送消息(所需要的对象)。比如,在角色扮演的游戏中,这个房间里有两个玩家(玩家A,玩家B),我拖着我控制的小人(玩家A)移动了一段距离,这个操作是一个游戏状态的改变,必需即时让玩家B的屏幕上也显示这个移动。于是你就可以通过在代码中调用这个sendGameStateAction方法,向服务器发送消息,而服务器通过读取方法的参数PutClient(GameStateClient的子类),知道了玩家A的操作(想要传递给玩家B的对象信息)。服务器按要求会发给同房间的玩家B。
 
 
开始写代码
一旦您有了生成的AS3代码,你需要从以下四个类继承,也就是说生成以下四个类的子类:
 
[html]<img src="http://www.gamantra.com/images/tut_ttt.jpg" width="489" height="364" />[/html]
 
先让我们预览下子类
 
[i]TictactoeGame[/i]
这是游戏的主类,你的游戏代码从这个类开始。在这个类中,你也可以告诉PulseUI包中哪些类被重写(over-ridden)了。例如,以下三个类就重写(over-ridden)了PulseUI 中的三个类(pulseui.Skinner, pulseui. NewGameScreen, pulseui. GameScreen)。
您可以在范例目录中找到它们,以便参考。
 
[i]TictactoeSkinner[/i]
你需要生成Skinner类的子类,这样就能读取图像文件(skin),这些图像文件通常被用于画所有的屏幕图形界面。
 
[i]TictactoeNewGameScreen[/i]
是否生成这个类的子类是可选的。对于tic tac toe游戏来说,我们需要告诉pulse 服务器一些关于游戏房间的特定属性,比如:这个游戏是回合制的,每个房间允许最多或者最少有几个玩家等等信息。
 
[i]TictactoeGameScreen[/i]
最后,本类定义了游戏进行时,游戏房间中的所有行为。
 
子类细节
[u]TictactoeGame[/u]
这是游戏的主类,游戏代码由此开始。
 
重写构造函数:
 
[tt]   public function TictactoeGame() {
new TictactoeSkinner();
}[/tt]  
 
构造函数是初始游戏特定的skinner类(它基本功能就是显示外壳,有点像Winamp的Skins,可以随便换外观模板)的好地方。关于skinner类我们将在稍后讨论。
 
重写 PulseGame的getGameId方法:
 
[tt]   public override function getGameId():String {
return "Tictactoe";
}[/tt]  
 
由于Pulse服务器程序可以同时运行多个游戏,所以每个游戏需要一个唯一的游戏id(game id),比如:正在玩Tictactoe的玩家不能同时去玩另一个Jigsaw游戏。
 
重写start方法:
 
[tt] import tictactoe.gsrc.client.GNetClientObjectFactory;
protected override function start():void  {
var factory:GNetClientObjectFactory;
factory = new GNetClientObjectFactory();
m_netClient = new GameClient(factory, this);
s_instance = this;
super.start();
}
[/tt] 
Start 方法是是程序开始点。正如代码中所示,一个工厂对象(factory object)必需首先创建。这个工厂类(GNetClientObjectFactory)是由前面的PulseCodeGen 生成的。所以不用担心怎么写它的代码 : p 
接着,创建一个Game Client对象,它的第一个参数就是前面创建的工厂对象(facetory)。m_netClient 是在 PulseGame中定义的。在这里,你只需要将值赋给m_netClient,不用担心其他的变化。 
整个游戏中只需要一个GameClinet实例。最后,通过调用super.start()  ,在一个闪屏特效之后,玩家就进入了登陆屏幕。
程序到达这里,玩家面前就呈现出登陆屏幕。玩家可以选择注册新帐号或者以匿名身份登陆进入游戏大厅。
 
创建自定义类:
 
[tt] protected override function initGameScreen():void  {
m_gameScreen = new TictactoeGameScreen(this);
m_gameScreen.init();
}
 
protected override function initNewGameScreen():void  {
m_newGameScreen = new TictactoeNewGameScreen();
m_newGameScreen.init();
}
[/tt] 
以上两个重写的方法用来创建游戏特点的屏幕,前者用来创建新的游戏房间界面,后者是在游戏玩(game play)的期间。
 
回合制游戏设计:
 
由于tictacoe是一个典型的回合制游戏(它的设置请查看:TictactoeNewGameScreen类),Pulse 服务器 会发出一个回调(callback)到客户端,以让客户端知道现在轮到它的回合了,而与此同时,一个显著的标记会出现在客户端上,给玩家一个明显的告知。
[tt]
public override function onPlayerTurn():void {
(m_gameScreen as TictactoeGameScreen).onPlayerTurn();
}
[/tt]
让游戏屏幕(game screen)显示这个告知也是一个非常简单的范例,供参考。
 
与服务器通讯:
[tt]
public override function onGameStateAction(gameState:GameStateClient):void{
(m_gameScreen as TictactoeGameScreen).onPlayerMoved(gameState);
}
 
public function sendGameState(xPos:int,yPos:int,value:int):void {
var putMsg:PutClient = new PutClient();
putMsg.setStateType(GameConstants.GS_IS_UNIQUE);
putMsg.setPutRow(xPos);
putMsg.setPutColumn(yPos);
putMsg.setPutValue(value);
m_netClient.sendGameStateAction(putMsg);
m_netClient.nextTurn();
}
[/tt]
以上两个方法需要被实现,前者用来接受别的玩家发送的动作信息(action);而后者则用来通知其他玩家自己做了哪些动作。
为了接收另一个玩家的动作,在这里,我们就简单地重写了(override)onGameStateAc tion方法,并且将它传递给游戏屏幕(game screen)。
为了发送玩家动作信息,我们写了一个方便的方法sendGameState,它由游戏屏幕对象(game screen object)调用。在这个时候,我们也告诉服务器:这个回合已经结束了。这样服务器好通知下一个玩家:轮到他的回合了。注意:PutClient是在前面的xml schema文件中定义的,并且在code gen期间自动生成。
 
如果您想了解关于游戏状态(game states)类型的更多信息,可以直接下载参看《Pulse 开发者手册》。
 
[u]TictactoeSkinner[/u]
你需要子类化skinner(就是指:自己定义Skinner的子类),这样就可以读取图像文件(skin),这些将用于画屏幕的图形界面。
 
[tt] public class TictactoeSkinner extends Skinner
{
[Embed(source="tictactoe//rsrc//Outline.png")]
private static var OutlineClass:Class;
 
[Embed(source="tictactoe//rsrc//ui.png")]
private static var UIClass:Class;
 
[Embed(source="tictactoe//rsrc//frame.png")]
private static var FrameClass:Class;
 
public function TictactoeSkinner() {
}
 
protected override function load():void {
m_outline = (new OutlineClass() as BitmapAsset).bitmapData;
m_ui = (new UIClass() as BitmapAsset).bitmapData;
m_frame = (new FrameClass() as BitmapAsset).bitmapData;
}
}
[/tt]
定制Skinner非常简单,以上便是一个完整的代码清单。Load 方法被重写以创建outline, ui和frame 的BtimapAsset。这些protected属性的变量在Skinner类中定义。Skinner对象将负责除了图片等资源的裁剪,生成工件等操作。(开发者不用了解它细节,这样可以省些时间,多喝些咖啡,多打打WAR III。)
 
[u]TictactoeNewGameScreen[/u]
这个子类化是可选的,然后对于tic tac toe 游戏来说,我们需要告诉服务器一些房间的特定属性,比如:采用回合制,每房间容纳多少玩家等等。
 
[tt]public override function createNewRoom():void {
PulseGame.getInstance().setCreatingRoom();
var room:GameRoomClient = new GameRoomClient();
room.setRoomName(m_ti.text);
room.setMaxPlayerCount(2);
room.setRoomType(GameConstants.ROOM_TURN_BASED);
PulseGame.getInstance().getGameClient().createGameRoom(room);
}
[/tt]
这里的tictactoe new game screen 仅用来定义房间类型。以上是 tic tac toe游戏所特有的,其他游戏不一定要弄这个类。代码设置了房间名称(由玩家健入),每房间仅2个人,并且告诉服务器是回合制类型。
Pulse的行为方式是基于这些参数的,您可以查阅《Pulse 开发者手册》和《API 指南》或者直接给我们发Email,
Email:FAQ@gamantra.com ,以获取更详细的使用方法和更多设置。
 
[u]TictactoeGameScreen[/u]
最后,一旦游戏开始,所有的房间内的操作将由这个类决定。
游戏代码由另外两个类实现,在这我们没有列出,完全是游戏逻辑的设定,不复杂,您可以直接下载Pulse开发包,浏览范例(samples)目录中的tic tac toe 代码。这两个类是:
TictactoeHotspot, 是Sprite的子类,主要用于跟踪游戏面板上的9个区域,以及根据玩家操作确定是否增child (”O” 或者”X”)。
 
[u]TictactoeGameStatus[/u]
TictactoeGameStatus, 一个简单的类,主要用于定义一些常量。
重写的Init 方法中用来初始 hotspot 区域等操作,游戏客户端仅执行一次。这些代码不详细说明了。
 
[color=beige]初始化游戏屏幕(Game Screen):[/color]
以上init 方法仅仅被调用一次,然而,玩家进出房间(game screen)许多此。
每一次玩家进入房间,show 方法被调用;玩家离开房间的时候,hide 方法被调用。
 
[tt]public override function show():void {
super.show();
}
[/tt]
 
对于tictactoe游戏,在 游戏开始前,show 方法里,我们选择不在屏幕中画所有的东西,而是等待。super. show() 调用GameScreen类中的show方法。GameScreen 类显示“go”或者“wait”sprite。房主创建房间的时候自动生成了room creator ,PulseUI 将按照时候有其他玩家进入房间来显示“开始”或者“等待”。
如果没有特殊的操作,你可以忽略这个重写(override)。
 
当房间主点击“开始(go)”图标时,startGame被调用。m_putValue被初始化以确定“O”或者“X”哪一个被显示。
 
[tt]public override function startGame():void {
super.startGame();
if ( PulseGame.getInstance().getGameClient().isGameHost() ) {
m_putValue = 1;
}
else {
m_putValue = -1;
}
addChild(m_borderSprite);
for each (var hotspotArray:Array in m_hotspotArray) {
for each (var hotspot:TictactoeHotspot in hotspotArray) {
                m_borderSprite.addChild(hotspot);
            }
}
m_isGaming = true;
} [/tt]
父类的实现移除了“go”或者“wait”标识。游戏面板开始初始化。
 
在以下hide 方法中,我们确定是否移除了所有与游戏屏幕(game screen)有关的所有sprite。
 
[tt]public override function hide():void {
if (m_showingTurn) {
removeChild(m_turnSprite);
}
if(m_borderSprite.numChildren > 0) {
for each (var hotspotArray:Array in m_hotspotArray) {
for each (var hotspot:TictactoeHotspot in hotspotArray) {
if(hotspot.numChildren > 0)
hotspot.removeChildAt(0);
                 m_borderSprite.removeChild(hotspot);
             }
   }
}
if ( m_isGaming ) {
     finishRound();  
removeChild(m_borderSprite);
     }
     m_isGaming = false;
super.hide();
}[/tt]
 
当玩家选择退出游戏游戏屏幕(game screen)时,lobbyHit 方法被调用。通常游戏就结束了。对于一些情况,一个玩家退出后另一个玩家仍然在这个房间中,应此只要一个玩家退出了,我们就把玩家带回游戏大厅。你可以选择任何实现方式。
 
[tt] protected override function lobbyHit():void {
    //Host send finish game and non-host sent enterlobby
    if ( m_isGaming == true )
     m_tictactoeGame.getGameClient().finishGame();
super.lobbyHit();
        }[/tt]
 
因为我们已经看到屏幕管理,所以现在开始看怎样玩游戏的部分…
 
显示玩家回合:
[tt]
public function onPlayerTurn():void {
// show the turn indicator
m_showingTurn = true;
addChild(m_turnSprite);
}
[/tt]
 
PulseGame对象调用这个方法。当服务器声明轮到某个玩家的回合时,我们显示“Your Trun”给那个玩家。
 
让玩家产生移动:
 
[tt]private function playAction(event:MouseEvent):void {
var gc:GameClient = PulseGame.getInstance().getGameClient();
if (gc.isMyTurn() == true) {
var selectedSprite:TictactoeHotspot;
selectedSprite = event.target as TictactoeHotspot;
if ( selectedSprite == null )
return;
var xPos:int = selectedSprite.col;
var yPos:int = selectedSprite.row;
if(m_hotspotArray[xPos][yPos].value == 0) {
m_tictactoeGame.sendGameState(xPos, yPos, m_putValue);
m_showingTurn = false;
removeChild(m_turnSprite);
}
}
}
[/tt]
 
我们处理鼠标点击,并进行一些检查。首先检查是否轮到该玩家,然后我们检查点击是否有效,如果检查都没问题,我们发送玩家动作信息到服务器,服务器将在下一合中把游戏状态(game state)发送给玩家自己和其他玩家。
当客户端收到玩家动作信息后,TictactoeGame(前面介绍过)对象调用以下方法,这里这个方法简单地更新了屏幕以及检查游戏是否有输赢。
[tt]
public function onPlayerMoved(gameState:GameStateClient):void {
var putMsg:PutClient = gameState as PutClient;
showSprite(putMsg.getPutRow(),
putMsg.getPutColumn(),
putMsg.getPutValue());
checkGameEnd();
}[/tt]
 
[color=beige][i]这是一个典型的回合制多人游戏范例,希望大家好运!开发更多精彩的多人在线flash游戏[/i][/color]
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值