提要
今天做了一个移动设备的网络通信demo,分两个部分,一个是网络连接,一个是数据通信。
需要两台Android设备A,B。A作客户端,B作服务端。
最终的效果是玩家控制设备A中的方块,B中的方块也一起动,同时在A的加速度传感器的信息在B中也实时更新。
网络连接
首先两台设备要联网,且IP在同一个网段,比如连接在同一个路由上,或者通过笔记本发出wifi信号,然后把设备连在上面。
在Unity3d中创建一个新工程,在场景中创建两个空物体,一个Client,一个Server。
在client创建一个脚本client.cs
using UnityEngine;
using System.Collections;
public class client : MonoBehaviour {
private string IP = "10.66.208.191";
private string clientIp;
private string clientIpSplite;
private Vector3 acceleration;
public GameObject cube;
private bool cubeInitialed = false;
//Connet port
private int Port = 10000;
void Awake()
{
clientIp = Network.player.ipAddress;
string[] tmpArray = clientIp.Split('.');
clientIpSplite = tmpArray[0] + "." + tmpArray[1] + "." + tmpArray[2] + ".";
}
void OnGUI()
{
switch (Network.peerType)
{
case NetworkPeerType.Disconnected:
StartConnect();
break;
case NetworkPeerType.Server:
break;
case NetworkPeerType.Client:
OnConnect();
break;
case NetworkPeerType.Connecting:
break;
}
}
void StartConnect()
{
if (GUILayout.Button("Connect Server"))
{
NetworkConnectionError error = Network.Connect(IP, Port);
Debug.Log("connect status:" + error);
}
}
void OnConnect()
{
if(!cubeInitialed)
{
Network.Instantiate(cube, transform.position, transform.rotation, 0);
cubeInitialed = true;
}
}
}
客户端根据当前当前的状态来执行相应的动作。StartConnect负责连接,用到了
static NetworkConnectionError Connect(string[] IPs, int remotePort)
第一个参数是Ip,第二个参数是端口。
连接上之后调用OnConnect函数初始化一个方块。注意这个方块是在客户端初始化的,属于这个客户端,创建成功之后会在其他的一桶连接的设备上都实例化一个cube出来,但是只有在这个client上NetworkView.isMine才为true。
接下来是服务端的代码。
using UnityEngine;
using System.Collections;
public class server : MonoBehaviour {
private int serverPort;
public GUIText status;
void Awake()
{
serverPort = 10000;
}
//OnGUI方法,所有GUI的绘制都需要在这个方法中实现
void OnGUI()
{
//Network.peerType是端类型的状态:
//即disconnected, connecting, server 或 client四种
switch (Network.peerType)
{
//禁止客户端连接运行, 服务器未初始化
case NetworkPeerType.Disconnected:
StartServer();
break;
//运行于服务器端
case NetworkPeerType.Server:
OnServer();
break;
//运行于客户端
case NetworkPeerType.Client:
break;
//正在尝试连接到服务器
case NetworkPeerType.Connecting:
break;
}
GUILayout.Label(Network.player.ipAddress);
}
void StartServer()
{
//当用户点击按钮的时候为true
if (GUILayout.Button("创建服务器"))
{
//初始化本机服务器端口,第一个参数就是本机接收多少连接
NetworkConnectionError error = Network.InitializeServer(12, serverPort, false);
Debug.Log("错误日志" + error);
}
}
void OnServer()
{
GUILayout.Label("服务端已经运行,等待客户端连接");
int length = Network.connections.Length;
for(int i = 0; i < length; i++)
{
GUILayout.Label("客户端" + i);
GUILayout.Label("客户端ip" + Network.connections[i].ipAddress);
GUILayout.Label("客户端端口" + Network.connections[i].port);
}
}
void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
{
// Always send transform (depending on reliability of the network view)
if (stream.isWriting)
{
Vector3 pos = transform.localPosition;
Quaternion rot = transform.localRotation;
stream.Serialize(ref pos);
stream.Serialize(ref rot);
}
// When receiving, buffer the information
else {
// Receive latest state information
Vector3 pos = Vector3.zero;
Quaternion rot = Quaternion.identity;
stream.Serialize(ref pos);
stream.Serialize(ref rot);
}
}
}
点击屏幕上的创建服务器之后就在设备上创建了一个服务端,监听对应的端口,当有其他设备连接上来的时间,客户端的信息就会打印出来,可以支持多个设备的连接。
还要创建一个cube的prefab,用于动态创建。
CubeController用于控制方块的运动,NetWorkView用于数据通信。
数据通信
需要进行数据通信的GameObject都要添加一个NetworkView 组件,数据通信有两种方式,状态同步和RPC(远程过程调用)。在CubeController.cs中,两种方法都有用到。
using UnityEngine;
using System.Collections;
public class CubeController : MonoBehaviour {
private GUIText accelText;
void Start()
{
accelText = GameObject.FindGameObjectWithTag("AccelTip").GetComponent<GUIText>() as GUIText;
accelText.text = "";
}
void Update()
{
if(Network.isClient)
{
Vector3 acceleration = Input.acceleration;
accelText.text = "" + acceleration;
networkView.RPC("UpdateAcceleration", RPCMode.Others, acceleration);
}
Vector3 moveDir = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
Vector3 cubescreenPos = Camera.main.WorldToScreenPoint(transform.position);
if (Input.GetMouseButton(0))
{
moveDir = new Vector3(Input.mousePosition.x - cubescreenPos.x, Input.mousePosition.y - cubescreenPos.y, 0f).normalized;
}
Debug.Log("moveDir: " + moveDir);
float speed = 5;
transform.Translate(speed * moveDir * Time.deltaTime);
}
void OnSerializeNetworkView(BitStream stream, NetworkMessageInfo info)
{
if (stream.isWriting)
{
Vector3 pos = transform.position;
stream.Serialize(ref pos);
}
else
{
Vector3 receivedPosition = Vector3.zero;
stream.Serialize(ref receivedPosition);
transform.position = receivedPosition;
}
}
[RPC]
void UpdateAcceleration(Vector3 acceleration)
{
accelText.text = "" + acceleration;
}
}
function OnSerializeNetworkView(stream : BitStream, info : NetworkMessageInfo) {}
这是在Network class中提供的一个func. 主要负责message sent / receive,他会同步被network view所关注的script中的对象,也就是当你写了一个script内含OnSerializeNetworkView(){},并且丢到observed属性中,则OnSerializeNetworkView()裡的code就会开始运作。基本上他透过BitStream物件收发网路上的资讯,使用上不需要了解封包的问题,也不需要知道如何切割封包。在这的demo中,服务端只负责接收信息,所以只执行else后面的代码,客户端发送信息,执行if后面的代码。
这里cube的state synchronization选的是Unreliable,对应的通讯协议是UDP,特点是无连接,比较快。
RPC典型的应用场景就是聊天室,使用也非常简单,首先定义一个rpc函数在(在上面加上[RPC]),然后通过NetWork.RPC来调用就可以了。这里是把客户端重力传感器的数据传了出去,在界面上更新。
参考
unity3D的網路資料傳輸 & 角色控制 - http://ppb440219.blogspot.com/2011/12/unity3d.html
网络视图 Network View - http://game.ceeger.com/Components/class-NetworkView.html
远程过程调用的细节 RPC Details - http://game.ceeger.com/Components/net-RPCDetails.html
状态同步的细节 State Synchronization Detailshttp://game.ceeger.com/Components/net-StateSynchronization.html
Unity Networking Tutorial - http://www.palladiumgames.net/tutorials/unity-networking-tutorial/