最近开始捣鼓小游戏。本人一直很喜欢玩小游戏的HOHO,可惜一直没往开发上发展。呵呵,花了半天搞了个简单的经典小游戏——猜拳,和爱好者一起分享下,高手可以飘过哈。游戏使用stratus实现了游戏双方的p2p连接,游戏数据都是直接发送的,不经过服务器。在此基础上可以开发很多类似的双人互动小游戏。
准备工作:预装FLEX电脑一台,上网账号一个,好茶一杯-_-
下面正式开始,程序开发分为三大块:
一、p2p连接部分
该部分可参考前面的文章,已经介绍的很详细了。下面就直接上代码了,不清楚的点这里。
//初始化自己的相关流以及在流上定义的方法;
private function initStream():void { listenStream = new NetStream(netConnection,NetStream.DIRECT_CONNECTIONS); listenStream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); listenStream.publish(myName); var c:Object =new Object(); c.onPeerConnect = function(caller:NetStream):Boolean { incomingStream = new NetStream(netConnection,caller.farID); incomingStream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); incomingStream.play("caller"); var i:Object = new Object(); i.onIncomingCall = function(caller:String):void { show(caller + "已经成功与您连接上\n"); remoteName = caller; //outgoingStream.send("onCallAccepted", username); callState = CallOn; remote_input.visible = false; action_btn.enabled = true; //idManager.change(callState); } i.onIm = function(act:String):void { remoteAction = act; //trace("remoteAction: "+remoteAction); remote_action.text = "出招了"; my_action.text = ""; result_txt.text = ""; if(myAction != null) compare(myAction,remoteAction); } i.onDisconnected = function(caller:String):void { show(caller+"和你断开连接\n"); stop(); } incomingStream.client = i; outgoingStream = new NetStream(netConnection,NetStream.DIRECT_CONNECTIONS); outgoingStream.addEventListener(NetStatusEvent.NET_STATUS,callee_outgoingStreamHandler); outgoingStream.publish("callee"); return true; } listenStream.client = c; }
//初始化相关发送流和方法;
private function call():void { controlStream = new NetStream(netConnection,farPeerID); controlStream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); controlStream.play(remoteName); outgoingStream = new NetStream(netConnection,NetStream.DIRECT_CONNECTIONS); outgoingStream.addEventListener(NetStatusEvent.NET_STATUS,caller_outgoingStreamHandler); outgoingStream.publish("caller"); //outgoingStream.send("onIncomingCall",username); //info_txt.text += "正在与"+partnername+"建立连接\n"; //var o:Object = new Object(); //o.onPeerConnect = function(callee:NetStream):Boolean //{ incomingStream = new NetStream(netConnection,farPeerID); incomingStream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); incomingStream.play("callee"); var i:Object = new Object(); i.onIm = function(act:String):void { remoteAction = act; //trace("remoteAction: "+remoteAction); remote_action.text = "出招了"; my_action.text = ""; result_txt.text = ""; if(myAction != null) compare(myAction,remoteAction); } i.onCallAccepted = function(callee:String):void { show(callee+"已经成功与您连接上\n"); callState = CallOn; action_btn.enabled = true; //send_btn.enabled = true; } i.onDisconnected = function(callee:String):void { show(callee+"和你断开连接\n"); stop(); } incomingStream.client = i; // return true; // } // outgoingStream.client = o; }
二、数据库及远程调用部分
游戏中建立p2p连接是根据对方用户名查找对应的peerID来实现的。相关信息都保存在数据库中,如下图:
FLEX客户端和数据库交互是通过AMFPHP这个代理网关来实现的。AMFPHP在这下载,是PHP的哈。本人只学了PHP-_-
下载下来解压后得到amfphp 1.9这个文件夹,东东都在这里面了。找到gateway.php这个文件进行编辑,这个就是指的网关文件了。
有两个地方需要注意下:
<?php
define("PRODUCTION_SERVER", false);
include "globals.php";
include "core/amf/app/Gateway.php";
$gateway = new Gateway();
//设置PHP类的位置,如果不改默认位置就是services文件夹,在globals可定义
$gateway->setClassPath($servicesPath);
$gateway->setClassMappingsPath($voPath);
//设置php和数据库交互的字符编码集,都用utf8最好,数据库编码也要一致
$gateway->setCharsetHandler("iconv", "UTF-8", "UTF-8");
$gateway->setErrorHandling(E_ALL ^ E_NOTICE);
if(PRODUCTION_SERVER)
{
$gateway->disableDebug();
$gateway->disableStandalonePlayer();
}
$gateway->enableGzipCompression(25*1024);
$gateway->service();
?>
然后打开services文件夹,在下面写你的PHP类,也就是下面RemoteObject组件里的source属性。
贴上我自己的PHP类:
<?php
class MySql
{
function MySql()
{
$this->methodTable = array(
'registerData' => array(
'description' => '注册',
'access' => 'remote',
'arguments' => array("username","peerID")
),
'fetchData' => array(
'description' => '取值',
'access' => 'remote',
'arguments' => array("username")
)
);
$DATABASE_SERVER= "数据库地址" ;
$DATABASE_USERNAME= "用户名" ;
$DATABASE_PASSWORD= "密码" ;
$DATABASE_NAME= "数据库名" ;
$link = mysql_connect( $DATABASE_SERVER,
$DATABASE_USERNAME,
$DATABASE_PASSWORD)
or die( mysql_error() );
mysql_query("SET NAMES 'utf8'");
mysql_select_db( $DATABASE_NAME, $link);
}
function registerData($username,$peerID)
{
$query = "SELECT * FROM registrations WHERE m_username='$username' ";
$result = mysql_query( $query );
$isNew = ($result && mysql_num_rows($result) == 0);
if( $isNew ) {
$query = "INSERT INTO registrations SET ";
$query .= "m_username='$username', m_peerID='$peerID';";
} else {
$query = "UPDATE registrations SET ";
$query .= " m_peerID='$peerID' where m_username='$username';";
}
$result = mysql_query( $query );
return $result;
}
function fetchData($username)
{
$query = "select * from registrations where m_username = '$username' ;";
$result = mysql_query( $query );
$m = mysql_num_rows($result);
if($m != 0) return $result;
else return "no";
}
}
?>
里面的methodTable就是定义的方法列表,里面定义了两个方法,分别是登陆服务器时注册用户数据和进行连接时查找对方用户数据的两个方法,比较有特色的东东-_-
FLEX中使用RemoteObject组件进行远程调用:
<s:RemoteObject id="myService" destination="amfphp" endpoint="http://服务器地址,本地就是localhost/gateway.php" source="PHP类的名字" showBusyCursor="true"> //method就是定义的要调用的方法 <s:method name="registerData" result="registerData_Result(event)" fault="registerData_Fault(event)" /> <s:method name="fetchData" result="fetchData_Result(event)" fault="fetchData_Fault(event)" /> </s:RemoteObject>
使用的时候直接调用myService.methodname就行了。
三、游戏逻辑部分
真的很简单,主要是处理好用户双方的执行顺序就行了。简单地概括下吧我就不画图了,打个比方,比如我和你两个人玩猜拳,必须要两个同时出拳才能进行比较。程序里设置了两个变量:myAction和remoteAction,分别指得是我出的拳和对方出的拳。初始值都为null,当双方都出了拳后,进行比较,计算得分:
private function compare(myAct:String,remoteAct:String):void { my_action.text = myAction; remote_action.text = remoteAction; if(myAct == remoteAct) result_txt.text = "平手"; if((myAct == QuanTou && remoteAct == JianDao) || (myAct == JianDao && remoteAct == Bu) || (myAct == Bu && remoteAct == QuanTou)) { result_txt.text = "恭喜,胜利"; myScore ++; } if((remoteAct == QuanTou && myAct == JianDao) || (remoteAct == JianDao && myAct == Bu) || (remoteAct == Bu && myAct == QuanTou)) { result_txt.text = "杯具,失败"; remoteScore++; } myAction = null; remoteAction = null; action_btn.enabled = true; send_btn.enabled = false; }
Demo在这里:http://game1.online.cm/CaiQuanP2P.swf
上个单机测试截图:
其它的就不多说了,最后放上程序源代码在下面,感兴趣的自己看吧,欢迎跟帖交流,转载请注明出处,谢谢!by 疯狂的八神庵
import mx.collections.ArrayCollection; import mx.controls.Alert; import mx.controls.Menu; import mx.events.MenuEvent; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; [Bindable] private var myName:String; [Bindable] private var remoteName:String; [Bindable] private var myScore:uint; [Bindable] private var remoteScore:uint; [Bindable] private var loginState:int; private const LoginOut:int = 0; private const LoginIn:int = 1; [Bindable] private var callState:int; private const CallOn:int = 1; private const CallOff:int = 0; private const QuanTou:String = "拳头"; private const JianDao:String = "剪刀"; private const Bu:String = "布"; private var netConnection:NetConnection; private var listenStream:NetStream; private var controlStream:NetStream; private var outgoingStream:NetStream; private var incomingStream:NetStream; private var farPeerID:String; private var myAction:String; private var remoteAction:String; private var tempAction:String; private const SERVER:String="rtmfp://stratus.adobe.com/"; private const DEVKEY:String="8b0f114ef5a20c433d5c2a33-201aeea5601b"; private function init():void { loginState = LoginOut; callState = CallOff; connect_btn.enabled = false; my_input.visible = true; remote_input.visible = false; action_btn.enabled = false; send_btn.enabled = false; remoteName = null; myScore = 0; remoteScore = 0; info_txt.text = ""; remote_action.text = ""; my_action.text = ""; result_txt.text = ""; myAction = null; remoteAction = null; } protected function login_btn_clickHandler(event:MouseEvent):void { if(my_input.text == "") Alert.show("大哥,你得取个名!","杯具",Alert.OK,this); else { myName = my_input.text; netConnection=new NetConnection(); netConnection.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); netConnection.connect(SERVER+DEVKEY); } } private function statusHandler(evt:NetStatusEvent):void { trace(evt.info.code); switch(evt.info.code) { case"NetConnection.Connect.Success": onConnect(); break; case"NetConnection.Connect.Closed": onDisconnect(); break; case "NetConnection.Connect.Failed": case "NetConnection.Connect.Rejected": show("连接到服务器失败\n"); init(); break; //case "NetStream.Play.Start": //onPlay(); //break; //case "NetStream.Connect.Success": //break; case "NetStream.Connect.Rejected": case "NetStream.Connect.Failed": show("与"+remoteName+"连接失败\n"); break; } } private function caller_outgoingStreamHandler(e:NetStatusEvent):void { if (e.info.code == "NetStream.Play.Start" ) { outgoingStream.send("onIncomingCall",myName); } } private function callee_outgoingStreamHandler(e:NetStatusEvent):void { if (e.info.code == "NetStream.Play.Start" ) { outgoingStream.send("onCallAccepted",myName); } } private function onConnect():void { myService.registerData(myName,netConnection.nearID); } private function registerData_Result(evt:ResultEvent):void { var m:String = evt.result.toString(); if(m == "true") { loginState = LoginIn; my_input.visible = false; connect_btn.enabled = true; remote_input.visible = true; show("成功连接到服务器\n"); initStream(); } else { show("无法连接到服务器\n"); } } private function registerData_Fault(evt:FaultEvent):void { show("无法连接到服务器\n"); } private function initStream():void { listenStream = new NetStream(netConnection,NetStream.DIRECT_CONNECTIONS); listenStream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); listenStream.publish(myName); var c:Object =new Object(); c.onPeerConnect = function(caller:NetStream):Boolean { incomingStream = new NetStream(netConnection,caller.farID); incomingStream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); incomingStream.play("caller"); var i:Object = new Object(); i.onIncomingCall = function(caller:String):void { show(caller + "已经成功与您连接上\n"); remoteName = caller; //outgoingStream.send("onCallAccepted", username); callState = CallOn; remote_input.visible = false; action_btn.enabled = true; //idManager.change(callState); } i.onIm = function(act:String):void { remoteAction = act; //trace("remoteAction: "+remoteAction); remote_action.text = "出招了"; my_action.text = ""; result_txt.text = ""; if(myAction != null) compare(myAction,remoteAction); } i.onDisconnected = function(caller:String):void { show(caller+"和你断开连接\n"); stop(); } incomingStream.client = i; outgoingStream = new NetStream(netConnection,NetStream.DIRECT_CONNECTIONS); outgoingStream.addEventListener(NetStatusEvent.NET_STATUS,callee_outgoingStreamHandler); outgoingStream.publish("callee"); return true; } listenStream.client = c; } private function compare(myAct:String,remoteAct:String):void { my_action.text = myAction; remote_action.text = remoteAction; if(myAct == remoteAct) result_txt.text = "平手"; if((myAct == QuanTou && remoteAct == JianDao) || (myAct == JianDao && remoteAct == Bu) || (myAct == Bu && remoteAct == QuanTou)) { result_txt.text = "恭喜,胜利"; myScore ++; } if((remoteAct == QuanTou && myAct == JianDao) || (remoteAct == JianDao && myAct == Bu) || (remoteAct == Bu && myAct == QuanTou)) { result_txt.text = "杯具,失败"; remoteScore++; } myAction = null; remoteAction = null; action_btn.enabled = true; send_btn.enabled = false; } protected function connect_btn_clickHandler(event:MouseEvent):void { if(remote_input.text == "") Alert.show("大哥,你对象是谁啊?","杯具",Alert.OK,this); else { remoteName = remote_input.text; myService.fetchData(remoteName); show("正在与"+remoteName+"建立连接,请稍后\n"); } } private function fetchData_Result(evt:ResultEvent):void { if(evt.result.toString() == "no") show("没"+remoteName+"这个人,你输错了吧\n"); else { for each(var i:Object in evt.result) { farPeerID = i.m_peerID.toString(); remote_input.visible = false; } call(); } } private function fetchData_Fault(evt:FaultEvent):void { show("与"+remoteName+"连接失败\n"); } private function call():void { controlStream = new NetStream(netConnection,farPeerID); controlStream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); controlStream.play(remoteName); outgoingStream = new NetStream(netConnection,NetStream.DIRECT_CONNECTIONS); outgoingStream.addEventListener(NetStatusEvent.NET_STATUS,caller_outgoingStreamHandler); outgoingStream.publish("caller"); //outgoingStream.send("onIncomingCall",username); //info_txt.text += "正在与"+partnername+"建立连接\n"; //var o:Object = new Object(); //o.onPeerConnect = function(callee:NetStream):Boolean //{ incomingStream = new NetStream(netConnection,farPeerID); incomingStream.addEventListener(NetStatusEvent.NET_STATUS,statusHandler); incomingStream.play("callee"); var i:Object = new Object(); i.onIm = function(act:String):void { remoteAction = act; //trace("remoteAction: "+remoteAction); remote_action.text = "出招了"; my_action.text = ""; result_txt.text = ""; if(myAction != null) compare(myAction,remoteAction); } i.onCallAccepted = function(callee:String):void { show(callee+"已经成功与您连接上\n"); callState = CallOn; action_btn.enabled = true; //send_btn.enabled = true; } i.onDisconnected = function(callee:String):void { show(callee+"和你断开连接\n"); stop(); } incomingStream.client = i; // return true; // } // outgoingStream.client = o; } private function stop():void { if (callState == CallOn && outgoingStream) { outgoingStream.send("onDisconnected",myName); show("和"+remoteName+"断开连接\n"); } remoteName = null; myScore = 0; remoteScore = 0; remote_action.text = ""; my_action.text = ""; result_txt.text = ""; callState = CallOff; remote_input.visible = true; action_btn.enabled = false; send_btn.enabled = false; if (incomingStream) { incomingStream.close(); incomingStream.removeEventListener(NetStatusEvent.NET_STATUS, statusHandler); } if (outgoingStream) { outgoingStream.close(); outgoingStream.removeEventListener(NetStatusEvent.NET_STATUS, statusHandler); } if (controlStream) { controlStream.close(); controlStream.removeEventListener(NetStatusEvent.NET_STATUS, statusHandler); } } private function show(word:String):void { info_txt.text += word; info_txt.validateNow(); info_txt.verticalScrollPosition = info_txt.textHeight; } protected function send_btn_clickHandler(event:MouseEvent):void { myAction = tempAction; if(outgoingStream) { if(myAction == null) show("你得出个招\n"); else { outgoingStream.send("onIm",myAction); show("对"+remoteName+"发招了,等待对方发招\n"); action_btn.enabled = false; send_btn.enabled = false; } } else { show("对方已经和你断开连接\n"); stop(); } if(remoteAction != null) compare(myAction,remoteAction); } private function menuClickHandler(evt:MenuEvent):void { tempAction = evt.currentTarget.label; if(remoteAction == null) remote_action.text = ""; result_txt.text = ""; my_action.text = tempAction; send_btn.enabled = true; } private function initMenu():void { Menu(action_btn.popUp).selectedIndex = 0; } private function onDisconnect():void { show("与服务器断开连接\n"); if (incomingStream) { incomingStream.close(); incomingStream.removeEventListener(NetStatusEvent.NET_STATUS, statusHandler); } if (outgoingStream) { outgoingStream.close(); outgoingStream.removeEventListener(NetStatusEvent.NET_STATUS, statusHandler); } if (controlStream) { controlStream.close(); controlStream.removeEventListener(NetStatusEvent.NET_STATUS, statusHandler); } incomingStream = null; outgoingStream = null; controlStream = null; netConnection = null; init(); } protected function logout_btn_clickHandler(event:MouseEvent):void { show("正在与服务器断开连接\n"); if (callState == CallOn && outgoingStream) { outgoingStream.send("onDisconnected",myName); show("和"+remoteName+"断开连接\n"); } if(netConnection) { netConnection.close(); } } protected function disconnect_btn_clickHandler(event:MouseEvent):void { stop(); }