日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

前言:

Unity3D,相信很多人都知道是用來做游戲的。網上也有很多這類視頻的教程,我也試著學習過。但是當要實現多人實時對戰的教例比較少,而用 php 來做 Unity3d 的服務器端的就更少了。

我在網上看了一個作者用 C# 做聯機服務器端的文章后,就根據他的思路改了一個 PHP 版的。例子只是多個方塊在一個場景下移動,所有玩家可以實時看到。以下就以幾個小事例簡單介紹一下 PHP 與 Unity3D 通信的實現吧。(以下的環境只做參考,其他的版本也可以)

 

環境:

1. Unity Hub 3.3.0-c1

2. Unity3D 2019

3. PHP 7.3

4. Workman 4.1

 

Workman 介紹

workerman 是一款開源高性能 PHP 應用容器,他除了用于互聯網、即時通訊、App 開發、硬件通訊、智能家居、物聯網等領域的開發外,也可以用于游戲服務器端的開發,之前實現的一個五子棋多人聯機大戰雖然用的是 Swoole。但是實現思路類似,五子棋是給同房間內的玩家更新棋子的坐標,而這里也是用于實時傳遞玩家的位置。

 

實現

客戶端是 C#,就簡單先以和服務器端連接,發送,接收做例子,進一步就是方塊移動,坐標傳遞。

 

1. 簡單通訊

客戶端只是用面板畫出一個輸入框 (地址) 和顯示區域 (接收服務端發送的內容),而服務器端是創建 TCP 服務,接收與發送。

(1). 客戶端連接

//連接
    public void Co.NETion()
    {
        //清理text
        recvText.text = "";
        //Socket
        socket = new Socket(AddressFamily.InterNetwork,
                         SocketType.Stream, ProtocolType.Tcp);
        //Connect
        string host = hostInput.text;
        int port = int.Parse(portInput.text);
        socket.Connect(host, port);
        clientText.text = "客戶端地址1 " + socket.LocalEndPoint.ToString();
        //Recv
        socket.BeginReceive(readBuff, 0, BUFFER_SIZE, SocketFlags.None, ReceiveCb, null);
    }

(2). 客戶端接收

//接收回調
    private void ReceiveCb(IAsyncResult ar)
    {
        try
        {
            //count是接收數據的大小
            int count = socket.EndReceive(ar);
            //數據處理
            string str = System.Text.Encoding.UTF8.GetString(readBuff, 0, count);
            if (recvStr.Length > 300) recvStr = "";
            recvStr += str + "n";

            recvText.text = "接收的消息 " + recvStr;

            Debug.LogError("接收的消息 "+ recvStr);

            //繼續接收	
            socket.BeginReceive(readBuff, 0, BUFFER_SIZE, SocketFlags.None, ReceiveCb, null);
        }
        catch (Exception e)
        {
            recvText.text += "鏈接已斷開";
            socket.Close();
        }
    }

(3). 客戶端發送

//發送數據
    public void Send()
    {
        string str = textInput.text;
        byte[] bytes = System.Text.Encoding.Default.GetBytes("test:" + str);
        try
        {
            socket.Send(bytes);
        }
        catch { }
    }

2. workerman 安裝

(1). 新啟一個項目,進入該目錄,composer require workerman/workerman

 

(2). 創建一個 start.php

<?php

use WorkermanWorker;

require_once __DIR__ . '/vendor/autoload.php';

// #### 開啟TCP服務 ####
$worker = new Worker('tcp://0.0.0.0:1234');

// 4 processes
//$worker->count = 4;

// 客戶端連接回調
$worker->onConnect = function ($connection) {
    echo "New Connectionn";
};

// 接收客戶端消息
$worker->onMessage = function ($connection, $data) use ($worker) {
    // Send data to client
    echo json_encode($data) . "n";

     //$ip = $connection->getRemoteIp();

    foreach($worker->connections as $connection)
    {
        $connection->send($data);
    }


    //$connection->send("Hello $data n");
};

// 客戶端關閉回調
$worker->onClose = function ($connection) {
    echo "Connection closedn";
};

Worker::runAll();


?>

(3). 啟動,輸入 php start.php start,成功如下

 

(4). 打開客戶端的 6asyn 場景并運行,輸入 TCP 服務的地址和端口

 

(5). 點擊發送,就可以查看 workerman 接收到的信息。

 

2. 方塊移動案例

方塊移動服務器端幾乎不用修改,在連接成功后,將多個客戶端的坐標傳遞到服務器端,服務器處理后再給所有連接發送坐標,客戶端再將數據繪制到場景中。

(1). 前后端數據約定

 

POS 用于標識行為,比如 POS 為坐標移動,同理聊天可以用 IM,登陸用 LOGIN 做標識等 (攻擊)。第二個為客戶端連接標識,標識往后為坐標 X, Y, Z。

(2). 坐標的整合發送

服務器端在接收消息回調中,循環所有連接端,并給所有連接端發送從客戶端發送過來的坐標。

$worker->onMessage = function ($connection, $data) use ($worker) {

    // 循環連接
    foreach($worker->connections as $connection)
    {
        // 發送坐標
        $connection->send($data);
    }
};

客戶端維護一個名為 players 的字典,它將存放所有玩家的信息。msgList 是消息列表,接收到服務端的消息后,客戶端會將消息保存在 msgList 中,等待 Update 逐一進行處理。

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using UnityEngine.UI;

public class Walk : MonoBehaviour
{
    //socket和緩沖區
    Socket socket;
    const int BUFFER_SIZE = 1024;
    public byte[] readBuff = new byte[BUFFER_SIZE];
    //玩家列表
    Dictionary<string, GameObject> players = new Dictionary<string, GameObject>();
    //消息列表
    List<string> msgList = new List<string>();
    //Player預設
    public GameObject prefab;
    //自己的IP和端口
    string id;

    //添加玩家
    void AddPlayer(string id, Vector3 pos)
    {
        GameObject player = (GameObject)Instantiate(prefab, pos, Quaternion.identity);
        TextMesh textMesh = player.GetComponentInChildren<TextMesh>();
        textMesh.text = id;
        players.Add(id, player);
    }

    //發送位置協議
    void SendPos()
    {
        GameObject player = players[id];
        Vector3 pos = player.transform.position;
        //組裝協議
        string str = "POS ";
        str += id + " ";
        str += pos.x.ToString() + " ";
        str += pos.y.ToString() + " ";
        str += pos.z.ToString() + " ";

        byte[] bytes = System.Text.Encoding.Default.GetBytes(str);
        socket.Send(bytes);
        Debug.Log("發送 " + str);
    }

    //發送離開協議
    void SendLeave()
    {
        //組裝協議
        string str = "LEAVE ";
        str += id + " ";
        byte[] bytes = System.Text.Encoding.Default.GetBytes(str);
        socket.Send(bytes);
        Debug.Log("發送 " + str);
    }

    //移動
    void Move()
    {
        if (id == "")
            return;

        GameObject player = players[id];
        //上
        if (Input.GetKey(KeyCode.UpArrow))
        {
            player.transform.position += new Vector3(0, 0, 1);
            SendPos();
        }
        //下
        else if (Input.GetKey(KeyCode.DownArrow))
        {
            player.transform.position += new Vector3(0, 0, -1); ;
            SendPos();
        }
        //左
        else if (Input.GetKey(KeyCode.LeftArrow))
        {
            player.transform.position += new Vector3(-1, 0, 0);
            SendPos();
        }
        //右
        else if (Input.GetKey(KeyCode.RightArrow))
        {
            player.transform.position += new Vector3(1, 0, 0);
            SendPos();
        }
    }

    //離開
    void OnDestory()
    {
        SendLeave();
    }

    //開始
    void Start()
    {
        Connect();
        
        //請求其他玩家列表,略
        //把自己放在一個隨機位置
        UnityEngine.Random.seed = (int)DateTime.Now.Ticks;
        float x = 100 + UnityEngine.Random.Range(-30, 30);
        float y = 0;
        float z = 100 + UnityEngine.Random.Range(-30, 30);
        Vector3 pos = new Vector3(x, y, z);
        AddPlayer(id, pos);

        //同步
        SendPos();
    }

    //鏈接
    void Connect()
    {
        //Socket
        socket = new Socket(AddressFamily.InterNetwork,
                                 SocketType.Stream, ProtocolType.Tcp);
        //Connect
        socket.Connect("192.168.1.199", 1234);
        id = socket.LocalEndPoint.ToString();
        //Recv
        socket.BeginReceive(readBuff, 0, BUFFER_SIZE, SocketFlags.None, ReceiveCb, null);
    }

    //接收回調
    private void ReceiveCb(IAsyncResult ar)
    {
        try
        {
            int count = socket.EndReceive(ar);
            //數據處理
            string str = System.Text.Encoding.UTF8.GetString(readBuff, 0, count);
            msgList.Add(str);
            //繼續接收	
            socket.BeginReceive(readBuff, 0, BUFFER_SIZE, SocketFlags.None, ReceiveCb, null);
        }
        catch (Exception e)
        {
            socket.Close();
        }
    }

    void Update()
    {
        //處理消息列表
        for (int i = 0; i < msgList.Count; i++)
            HandleMsg();
        //移動
        Move();
    }

    //處理消息列表
    void HandleMsg()
    {
        //獲取一條消息
        if (msgList.Count <= 0)
            return;
        string str = msgList[0];
        msgList.RemoveAt(0);
        //根據協議做不同的消息處理
        string[] args = str.Split(' ');
        if (args[0] == "POS")
        {
            OnRecvPos(args[1], args[2], args[3], args[4]);
        }
        else if (args[0] == "LEAVE")
        {
            OnRecvLeave(args[1]);
        }
    }

    //處理更新位置的協議
    public void OnRecvPos(string id, string xStr, string yStr, string zStr)
    {
        //不更新自己的位置
        if (id == this.id)
            return;
        //解析協議
        float x = float.Parse(xStr);
        float y = float.Parse(yStr);
        float z = float.Parse(zStr);
        Vector3 pos = new Vector3(x, y, z);
        //已經初始化該玩家
        if (players.ContainsKey(id))
        {
            players[id].transform.position = pos;
        }
        //尚未初始化該玩家
        else
        {
            AddPlayer(id, pos);
        }
    }

    //處理玩家離開的協議
    public void OnRecvLeave(string id)
    {
        if (players.ContainsKey(id))
        {
            Destroy(players[id]);
            players[id] = null;
        }
    }
}

3. 演示效果

 


 

總結

以前只是從入門的角度簡單介紹了一個二者通訊的方法,其實 workerman 可以基于 TCP 自定義協議,這樣就可以實現特別的封包解包了。后面如果有時間的話,可能會分享一下用 workerman 實現一個小成品的 3D 游戲。

分享到:
標簽:unity3D
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定