Unity使用Websocket实现WebGL在线游戏(无插件)

2/11/2016来源:C#应用人气:5779

开篇
 
Unity支持WebGL之前,想做页游的话需要转换成Flash平台发布,但是很麻烦中间会有很多东西需要特殊处理。或者不使用Flash那就需要Unity 的 WebPlayer,这是一个Web插件,运行之前要先在浏览器上安装此插件,才可以运行Unity程序。
BUT!!!现在Unity可以直接翻译成JS在浏览器上使用WebGL进行渲染,哈哈,Nice。解决了页游插件问题。但是联网呢?传统的Socket方式肯定不行,浏览器不会让你用Socket进行传输的。然儿HTML5还有一个新特性就是Websocket,可以在浏览器上建立Socket长连接了。怎么实现呢?鄙人尝试了很多方式,最后发现一个真正可行的方式,而且貌似只有这样才可以,遂分享之~~~
 
实践
 
!!!DEMO全源码http://www.cnblogs.com/yinlong1991/p/5090289.html !!!
我先描述下大概的逻辑:
Unity可以调用Web JS,然后JS里面使用WebSocket连接服务器,Websocket通过SendMessage给Unity 发回收到的消息包。代码如下:
    /*---------------- WebGl Platform ---------------------*/
    [DllImport("__Internal")]
    PRivate static extern void ConnectJS(string str);
    [DllImport("__Internal")]
    private static extern void SendMsgJS(byte[] data,int length);
    [DllImport("__Internal")]
    private static extern void CloseJS();
    [DllImport("__Internal")]
    private static extern void AlertJS(string str);
 
    private void Connect_2(string str)
    {
        ConnectJS(str);
    }
 
    private void SendMsg_2(MessageBase msg)
    {
        byte[] data = SerializeMsg(msg);
        SendMsgJS(data,data.Length);
    }
 
    private void Close_2()
    {
        CloseJS();
    }
 
    private void OnOpen_2()
    {
        OnOpen();
    }
 
    private void OnMessage_2(string msg)
    {
        string []byteIntS = msg.Split(' ');
        byte[] data = new byte[byteIntS.Length];
        for (int i = 0; i < byteIntS.Length; i++)
        {
            data[i] = (byte)int.Parse(byteIntS[i]);
        }
        OnMessage(data);
    }
 
    private void OnClose_2()
    {
        OnClose();
    }
    /*--------------------- Serialze ------------------------*/
    BinaryFormatter binFormat = new BinaryFormatter();//创建二进制序列化器
    private byte[] SerializeMsg(MessageBase msg)
    {
        MemoryStream stream = new MemoryStream();
        binFormat.Serialize(stream, msg);
        return stream.GetBuffer();
    }

 Web端使用的jslib,此文件要放在Plugins目录下,会被翻译成JS代码在浏览器上运行。我的路径是:Assets\Plugins\WebGL\WebsocketJS.jslib

var WebsocketJS = 
{
	$webSocket:{},
	ConnectJS : function(url)
	{
		var s_url = Pointer_stringify(url);
		webSocket = new WebSocket(s_url);
		webSocket.onmessage = function (e) 
		{
			if (e.data instanceof Blob)
			{
				var reader = new FileReader();
				reader.addEventListener("loadend", function() 
				{
					var array = new Uint8Array(reader.result);
					var msg = "";
					for(var i = 0;i<array.length;i++)
					{
						if(i == array.length - 1)
							msg += array[i];
						else
							msg += array[i]+" ";
					}
					SendMessage("WebSocket","OnMessage_2",msg);
				});
				reader.readAsArrayBuffer(e.data);
			}
			else
			{
				alert("msg not a blob instance");
			}
		};
		webSocket.onopen = function(e)
		{
			SendMessage("WebSocket","OnOpen_2");
		};
		webSocket.onclose = function(e)
		{
			SendMessage("WebSocket","OnClose_2");
		};
	},

	SendMsgJS: function (msg,length)
	{
		webSocket.send(HEAPU8.buffer.slice(msg, msg+length));
	},
	
	CloseJS: function ()
	{
		webSocket.close();
	},
	
	AlertJS:function (msg)
	{
		var s_msg = Pointer_stringify(msg);
		alert(s_msg);
	}
};

autoAddDeps(WebsocketJS, '$webSocket');
mergeInto(LibraryManager.library, WebsocketJS);

 注意jslib的写法格式比较特殊$webSocket:{}, 表示声明一个全局变量,autoAddDeps(WebsocketJS,'$webSocket'); 这个是使用该变量?反正要Add进去。

这里面所有的函数都是由Unity来调用的如上面的C#代码:private static extern void ConnectJS(string str); 这样的写法,就可以调用JS方法了。
细节:
    var s_url =Pointer_stringify(url); 所有的str类型内容在这里面要使用Pointer_stringify 函数转化一下。
    HEAPU8.buffer.slice(msg, msg+length) 所有byte[]类型的数据需要用HEAPU8.buffer.slice(byte[] data,int length)函数来转化。
    还有一些其他的数据类型转化,暂时没用到不详细解释,需要可也参考他人代码:https://github.com/hecomi/UWO/blob/master/Assets/Plugins/WebSocket.jslib 丫的这是国外网站最流行的一段jslib代码,但是我觉得不好!!!
重点要说一下 webSocket.onmessage 方法体的内容
if (e.data instanceof Blob)
{
	var reader = new FileReader();
	reader.addEventListener("loadend", function() 
	{
		var array = new Uint8Array(reader.result);
		var msg = "";
		for(var i = 0;i<array.length;i++)
		{
			if(i == array.length - 1)
				msg += array[i];
			else
				msg += array[i]+" ";
		}
		SendMessage("WebSocket","OnMessage_2",msg);
	});
	reader.readAsArrayBuffer(e.data);
}

 从代码中可以看出来接收到的数据是Blob块类型的,然后通过FileReader读字节流,然后把字节流转换成8位无符号整形数组 Uint8Array 然后放入到字符串里以空格间隔,为什么要这么写呢?,这样岂不是大大的增加了数据传输量(PS:非网络流量传输,只是JS到Unity之间)。原因是我要通过 SendMessage("WebSocket","OnMessage_2",msg); 函数吧数据串给Unity,我查了一大顿的文档居然这货不支持除了string类型的其他所有数据类型的传输,非文本类型的字符也不能传输比如'\0'字符就传输失败。我擦嘞,那我只能用如上所述的暴力方式封装String类型的字符串,然后到Unity中再去做解析了。这块也是为什么上面我说外国网站上用的那种jslib读取网络数据包不好的地方。

外国网站上的传输方式:
C#代码:
[DllImport("__Internal")]
private static extern void SocketRecv (int socketInstance, IntPtr ptr, int length);
[DllImport("__Internal")]
private static extern int SocketRecvLength (int socketInstance);
 
public byte[] Recv()
{
	int length = SocketRecvLength (m_NativeRef);
	if (length == 0)
		return null;
	byte[] buffer = new byte[length];
	IntPtr unmanagedPointer = Marshal.AllocHGlobal(length);
	SocketRecv (m_NativeRef, unmanagedPointer, length);
	Marshal.Copy (unmanagedPointer, buffer, 0, length);
	Marshal.FreeHGlobal(unmanagedPointer);
	return buffer;
}

 jslib代码:

SocketSend: function (socketInstance, ptr, length)
{
	var ptr = HEAPU32[ptr>>2];
	var socket = webSocketInstances[socketInstance];
	socket.socket.send (HEAPU8.buffer.slice(ptr, ptr+length));
},
 
SocketRecvLength: function(socketInstance)
{
	var socket = webSocketInstances[socketInstance];
	if (socket.messages.length == 0)
		return 0;
	return socket.messages[0].length;
},

 我只说下他的大概运作机理,Unity调用JS连接服务器,服务器返回包之后JS先缓存起来,然后Unity以循环方式调用JS读取接受到的数据的头指针,然后根据长度读取数据块数据。这种loop方式无疑增大了数据报的延迟,并且使用起来也很不方便!

注意:我的那种方式也要在C#中缓冲包数据,SendMessage函数回调是在另一个线程中,不能改变主线程Transform等组件的值。
服务器不限制语言,只要是符合Websocket规范的就可以接受发送数据。C#的参见: https://github.com/sta/websocket-sharp
!!!DEMO全源码:http://www.cnblogs.com/yinlong1991/p/5090289.html !!!
 
结束语
 
大家还有什么不懂的地方尽管问我。
最后,祝大家新年快乐,2016 找到漂酿女盆友~