Skip to content

Leoi-JR/SandBox2023

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SandBox2023

Unity-Web-Tornado

1. server环境配置

需配置Python虚拟环境

virtualenv <envname>

{Project Root path}/websocketenv/Scripts/activate.bat

或者使用conda创建虚拟环境

conda create -n <envname> python=3.7

conda activate <envname>


2. unity client

UnitySocket.cs:unity与服务器通信的socket代码

2.1. 配置websocketSharp

将根目录下的websocket-sharp.dll添加到Unity项目文件夹中的Assets/Plugins/websocket-sharp.dll
UnitySocket.cs挂载到游戏物体上



3. server服务器文件夹架构

  • main.py # 入口文件
  • app.py # 应用程序主文件
  • info.py # 已连接的客户端
  • route.py # url路由映射
  • handlers # 处理请求的代码
    • main_handler.py # 基础请求处理器
    • webSocket_handler.py # 网页客户端请求处理器
    • unity_handler.py # unity客户端请求处理器
    • ... # 其他请求处理器
  • models # 数据库模型
    • user.py # 用户模型
    • ... # 其他模型
  • static # 静态文件
    • css
    • js
    • ...
  • templates # 模板文件
    • index.html # 基础模板
    • ... # 其他模板
  • test # 一些测试文件
  • requirements.txt # 依赖库清单文件

3.1. 安装python依赖库

pip install -r requirements.txt

3.2. 运行Tornado服务器

python ./main.py

3.3. 通信示范视频

assets/example.mp4



4. 通信逻辑定制

4.1. web客户端与tornado服务器

  • 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)

4.2. unity客户端与tornado服务器

  • 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')


5. 路由指定

参考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


6. web客户端开发建议

同学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),
]

这样不同同学间的开发逻辑就不会有冲突

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors