Unity-Web-Tornado
需配置Python虚拟环境
virtualenv <envname>
{Project Root path}/websocketenv/Scripts/activate.bat或者使用conda创建虚拟环境
conda create -n <envname> python=3.7
conda activate <envname>
UnitySocket.cs:unity与服务器通信的socket代码
将根目录下的websocket-sharp.dll添加到Unity项目文件夹中的Assets/Plugins/websocket-sharp.dll
将UnitySocket.cs挂载到游戏物体上
main.py# 入口文件app.py# 应用程序主文件info.py# 已连接的客户端route.py# url路由映射handlers# 处理请求的代码main_handler.py# 基础请求处理器webSocket_handler.py# 网页客户端请求处理器unity_handler.py# unity客户端请求处理器...# 其他请求处理器
models# 数据库模型user.py# 用户模型...# 其他模型
static# 静态文件cssjs...
templates# 模板文件index.html# 基础模板...# 其他模板
test# 一些测试文件requirements.txt# 依赖库清单文件
pip install -r requirements.txtpython ./main.py
assets/example.mp4
- web客户端参考:
server/templates/index.html
为你的客户端起个名字,即指定第三行的name - 在
web client文件夹编写web客户端时,建立与服务器的连接参考如下代码。web客户端相关文件不要在templates文件夹下编写,即服务器与web客户端解耦
var conn = function connect() {
var name = document.getElementById("sender").value;
var socket = new WebSocket("ws://127.0.0.1:5000/websocket?name=" + name);
// {# 建立连接 #}
socket.onopen = function (event) {
console.log("WebSocket opened");
};
// {# 接收消息 #}
socket.onmessage = function (event) {
console.log("Received message: " + event.data);
document.getElementById("output").innerHTML += "<p>" + event.data + "</p>";
};
// {# 发送消息 #}
document.getElementById("send").addEventListener("click", function (event) {
// {# 发送给谁 发送给recipient #}
var recipient = document.getElementById("recipient").value;
var message = recipient + ":" + document.getElementById("message").value;
console.log("Sending message: " + message);
socket.send(message);
});
// {# 断开连接 #}
socket.onclose = function (event) {
console.log("WebSocket closed");
};
// {# 连接错误 #}
socket.onerror = function (event) {
console.error("WebSocket error: " + event);
};
}
document.getElementById("connect").addEventListener("click", conn);- tornado服务器:
server/handlers/webSocket_handler.py
连接时获取客户端的名字name,添加到clients字典:{name: ID},供指定客户端发送消息时使用
# 网页客户端的通信逻辑
class WebSocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
# 新客户端连接
# 获取客户端的名称
self.web_name = self.get_argument("name", "")
if self.web_name:
print("Web connected", self.web_name)
# 记录web的标识
clients[self.web_name] = self
print(self.web_name, ':', self)
else:
print('该web客户端没有名称。。。')
def on_message(self, message):
# 接收到客户端的消息
print("Received message: {}".format(message))
# 将消息转发到指定的客户端
message_parts = message.split(":")
# 获取需要接收信息的客户端名称
recipient = message_parts[0]
# 获取需要接收信息的客户端标识
recipient = clients[recipient]
message = message_parts[1]
# 向目标发送消息
recipient.write_message(message)
def on_close(self):
# 客户端断开连接
print(self.web_name, "Client disconnected")
clients.pop(self.web_name)- unity客户端参考:
unity client/UnitySocket.cs
这里假设了只会有一个unity客户端,所以不需要指定unity客户端的名字,固定叫unity
// 构建发送消息的结构,这里的例子是消息体包含 事件类型 和 游戏对象相关信息
public class MessageObj
{
public string EventType;
public string GameObject;
public MessageObj(string eventType, string gameObject)
{
EventType = eventType;
GameObject = gameObject;
}
}
// socket相关的代码
public class UnitySocket : MonoBehaviour
{
private WebSocket ws;
void Start()
{
ws = new WebSocket("ws://127.0.0.1:5000/unity");
ws.OnOpen += OnOpenHandler;
ws.OnClose += OnCloseHandler;
ws.OnMessage += OnMessageHandler;
ws.OnError += OnErrorHandler;
ws.ConnectAsync();
}
void Update()
{
// 这里的例子是,点击游戏物体(绑定了该脚本)时,发送游戏物体的名字
if (Input.GetMouseButtonDown(0))
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out hit, 100.0f))
{
GameObject clickedObject = hit.transform.gameObject;
SendGameObjectClick(clickedObject.name);
}
}
}
void OnDestroy()
{
if (ws != null)
{
ws.Close();
}
}
private void OnOpenHandler(object sender, EventArgs e)
{
Debug.Log("WebSocket connected!");
}
private void OnCloseHandler(object sender, CloseEventArgs e)
{
Debug.Log("WebSocket closed with reason: " + e.Reason);
}
// 接收消息,接收到来自服务器的消息时触发的逻辑
private void OnMessageHandler(object sender, MessageEventArgs e)
{
Debug.Log("WebSocket received a message: " + e.Data);
}
private void OnErrorHandler(object sender, ErrorEventArgs e)
{
Debug.Log("WebSocket error: " + e.Message);
}
// 发送消息,给tornado服务器发送消息时的逻辑
private void SendGameObjectClick(string gameObjectName)
{
if (ws.IsAlive)
{
string jsonMessage = JsonUtility.ToJson(
new MessageObj("game_object_click", gameObjectName)
);
Debug.Log(jsonMessage);
ws.Send(jsonMessage);
}
}
}- tornado服务器:
server/handlers/unity_handler.py
unity客户端的名字name固定为unity,添加到clients字典:{unity: ID},供指定客户端发送消息时使用
# unity的通信逻辑,当前逻辑仅支持一个unity客户端连接服务器
# 若出现第二个unity客户端连接服务器,将覆盖第一个,有风险
class UnityHandler(tornado.websocket.WebSocketHandler):
# unity与服务器建立连接
def open(self):
print("Unity connected")
# 记录unity的标识
clients['unity'] = self
print('unity:', self)
# unity发送消息给服务器
def on_message(self, message):
data = json.loads(message)
# 信息中包含“事件类型”和“事件信息”,根据开发需求修改c#传输的数据内容以及以下内容
if 'EventType' in data and data['EventType'] == 'game_object_click':
print(f"Game object clicked: {data['GameObject']}")
# unity连接断开
def on_close(self):
print("WebSocket disconnected")
clients.pop('unity')参考server/route.py
from handlers.main_handler import MainHandler
from handlers.unity_handler import UnityHandler
from handlers.webSocket_handler import WebSocketHandler
routes = [
(r'/', MainHandler),
(r'/websocket', WebSocketHandler),
(r"/unity", UnityHandler),
]即
- 访问
127.0.0.1:5000/时,通信逻辑走MainHandler - 访问
127.0.0.1:5000/websocket时,通信逻辑走WebSocketHandler - 访问
127.0.0.1:5000/unity时,通信逻辑走UnityHandler
同学A开发 图表 客户端
可以在handlers新建graph_handler.py文件,graph_handler.py中新建多个类处理不同的请求,如
├── graph_handler.py
│ ├── 类 GraphHandler1
│ ├── 类 GraphHandler2
│ ├── 类 GraphHandler3
│ ├── ......
在route.py中添加
routes = [
...
(r'/graph/h1', GraphHandler1), # 即访问127.0.0.1:5000/graph/h1时的通信逻辑由GraphHandler1指定
(r'/graph/h2', GraphHandler2),
(r"/graph/h3", GraphHandler3),
]同学B开发 沙盘 客户端
可以在handlers新建box_handler.py文件,box_handler.py中新建多个类处理不同的请求,如
├── box_handler.py
│ ├── 类 BoxHandler1
│ ├── 类 BoxHandler2
│ ├── 类 BoxHandler3
│ ├── ......
在route.py中添加
routes = [
...
(r'/box/h1', BoxHandler1), # 即访问127.0.0.1:5000/box/h1时的通信逻辑由BoxHandler1指定
(r'/box/h2', BoxHandler2),
(r"/box/h3", BoxHandler3),
]这样不同同学间的开发逻辑就不会有冲突