Unity同屏移动-客户端与服务端

1.Show Request.cs作用是发送给客户端自己的id

2.PlayerMagener.cs来管理所有的Player

其中Add方法,可以将玩家添加到玩家字典里面,以及有显示自己的请求。

这边的显示自己的功能由PlayerController.cs里面刚开始就直接加进去。

 

 

3.服务端处理Show的请求,ShowHandler.cs:拿到id,并且Event推送给其他的所有客户端。

 

4.客户端处理Show的Event,PhontonEngine.cs中:拿到新加入的玩家的id,并且调用ShowOther方法。

 

其中ShowOther方法在PlayerManager中,通过instantiate实例化的方式生成,并且将新显示出来的玩家加入到玩家字典中。

 

到此为止,就完成了能看到其他玩家的功能了

———————————————————————————————————————————

接着是移动:
1.MoveRequest是发送给客户端自己实时的坐标,通过PlayerManeger的GetMe方法去拿。

2.在FixUpdate里面,不断调用MoveRequest,不断的发送自己的坐标。

而下面的Update里面是一个控制自己移动的方法

 

 

3.服务端MoveHandler.cs:拿到准备移动的物体的位置和他的id。然后Event事件发送给其他的所有客户端,发送将要移动的物体的位置和id。

 

4.服务端处理Move的Event:拿到xyz和id,放在intValue里面,调用PlayerManeger的MoveOther方法,去更新刚才Move的物体的坐标。(因为Move的Request是每隔0.02s发一次的,因为放在FixUpdate里面了嘛,所以对坐标更新的情况也是0.02s更新一次,如果再短一点,我觉得这个就是帧同步了吧(大概或许hhhh)

 

PlayerManeger里面的MoveOther方法:通过id拿到字典里面的玩家,然后直接更新其坐标。

 

PlayerManager的好处:将player都管理起来了,有一个字典来存储所有的Player。一个Add方法来添加Player,并且如果me为null就显示自己。一个GetMe方法来获取自己。一个ShoweOtherPlayer来显示其他玩家。一个MoveOtherPlayer方法来显示其他玩家的移动。

_____________________________________________________________________________

最后附上这几个类的完整代码:

(如果配置文件你们也要的话,从我前面的文章里面找就行了。)

客户端

ShowRequest.cs:

using System.Collections;
using System.Collections.Generic;
using Common;
using ExitGames.Client.Photon;
using UnityEngine;

public class ShowRequest : Request
{
    public override void DefaultRequest()
    {
        //构造参数
        var data = new Dictionary<byte, object>();
        //构造参数
        data.Add((byte)ParaCode.Show, PlayerManager.Instance.GetMe().id);
        //发送
        PhotonEngine.peer.OpCustom((byte)OpCode, data, true);
    }

    public override void OnOprationRespionse(OperationResponse operationResponse)
    {
        if (operationResponse.ReturnCode == (byte)ReturnCode.Success)
        {
            Debug.Log("Show成功");
        }
        else
        {
            Debug.Log("Show失败");
        }
    }
}

MoveRequest.cs

using System.Collections;
using System.Collections.Generic;
using Common;
using ExitGames.Client.Photon;
using UnityEngine;

public class MoveRequest : Request
{
    public override void DefaultRequest()
    {
        //拿到自己
        var me = PlayerManager.Instance.GetMe().GetComponent<Transform>();
        //构造参数
        var data = new Dictionary<byte, object>();
        string location = me.position.x + "," + me.position.y + "," + me.position.z;
        //构造参数
        data.Add((byte)ParaCode.Move, location);
        //print(location);
        //发送
        PhotonEngine.peer.OpCustom((byte)OpCode, data, true);
    }

    public override void OnOprationRespionse(OperationResponse operationResponse)
    {
        //
    }
}

PlayerController.cs:

using Common;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour {
    [HideInInspector]
    public int id;
    private void Awake()
    {
        id = Random.Range(10000, 99999);
    }


    // Use this for initialization
    void Start () {
        PlayerManager.Instance.AddPlayer(this);    
	}

    public int i = 0;
    private void FixedUpdate()
    {

            if (id == PlayerManager.Instance.GetMe().id)
            {
                //发送自己的坐标
                PhotonEngine.Instance.GetRequest(OperationCode.Move).DefaultRequest();
            }

    }

    // Update is called once per frame
    void Update () {
        if (id == PlayerManager.Instance.GetMe().id)
        {
            GetComponent<Transform>().Translate(new Vector3(Input.GetAxis("Horizontal")/30, 0, 0));
        }
	}
}

PlayerManager.cs:

using Common;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerManager : MonoBehaviour {

    public static PlayerManager Instance;
    private PlayerController me;
    public Dictionary<int, PlayerController> PlayDic = new Dictionary<int, PlayerController>();
    public GameObject PlayerPf;
    private void Awake()
    {
        Instance = this;
    }

    public void AddPlayer(PlayerController player)
    {
        if (me == null)
        {
            me = player;
            //发送给服务器,显示自己
            GameObject.Find("GameManager").GetComponent<ShowRequest>().DefaultRequest();
        }
        PlayDic.Add(player.id, player);
    }

    public PlayerController GetMe()
    {
        return me;
    }

    public void ShowOtherPlayer(int id)
    {
        if (id == me.id) return;
        GameObject player = Instantiate(PlayerPf);
        player.GetComponent<PlayerController>().id = id;
        PlayDic.Add(id, player.GetComponent<PlayerController>());
    }

    public void MoveOtherPlayer(string param)
    {
        //解析参数 x,y,z,id
        string[] list = param.Split(',');
        int id = int.Parse(list[3]);
        Vector3 v = new Vector3(float.Parse(list[0]), float.Parse(list[1]), float.Parse(list[2]));
        //print("准备移动id:" + id + "  location:" + v);
        //设置对应id的player的坐标
        DicTool.GetValue(PlayDic, id).GetComponent<Transform>().position = v;
    }


    // Use this for initialization
    void Start () {
		
	}
	
	// Update is called once per frame
	void Update () {
		
	}
}

PhotonEngine.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using ExitGames.Client.Photon;
using Common;

public class PhotonEngine : MonoBehaviour,IPhotonPeerListener {
    public static PhotonEngine Instance;

    //用字典来缓存每次的请求和码
    //这里就能体现出来我们Request抽象类的好处了,不然每次这边都是不同的类型,使用抽象类就统一了
    private Dictionary<OperationCode, Request> RequestDic = new Dictionary<OperationCode, Request>();

    public static PhotonPeer peer;
    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            //当前Gameobject不会随场景的销毁而销毁
            DontDestroyOnLoad(this.gameObject);
        }
    }

    void Start () {
        peer = new PhotonPeer(this,ConnectionProtocol.Udp);
        peer.Connect("127.0.0.1:5055", "MyGame6");//通过ip连接到到对应的我们config中配置的Allpication
	}
	
	void Update () {
        peer.Service();//开启服务
	}

    private void OnDestroy()//销毁的时候
    {
        if(peer!=null&&peer.PeerState == PeerStateValue.Connected)//如果存在且保持连接状态
        {
            peer.Disconnect();//就断开连接
        }
    }

    public void DebugReturn(DebugLevel level, string message)
    {
    }

    public void OnEvent(EventData eventData)
    {
        if ((byte)OperationCode.Chat == eventData.Code)
        {
            var data = eventData.Parameters;
            object intValue;
            data.TryGetValue((byte)ParaCode.Chat, out intValue);
            Debug.Log("收到服务器:"+intValue);
        }
        else if ((byte)OperationCode.Show == eventData.Code)
        {
            var data = eventData.Parameters;
            object intValue;
            data.TryGetValue((byte)ParaCode.Show, out intValue);
            Debug.Log("收到Show:" + intValue);
            PlayerManager.Instance.ShowOtherPlayer((int)intValue);
        }
        else if ((byte)OperationCode.Move == eventData.Code)
        {
            if (PlayerManager.Instance == null)//判空
                return;
            var data = eventData.Parameters;
            object intValue;
            data.TryGetValue((byte)ParaCode.Move, out intValue);
            Debug.Log("收到Move:" + intValue);
            PlayerManager.Instance.MoveOtherPlayer((string)intValue);
        }
        return;
        switch (eventData.Code)
        {
            //case 1:
            //    //解析数据
            //    var data = eventData.Parameters;
            //    object intValue, StringValue;
            //    data.TryGetValue(1, out intValue);
            //    data.TryGetValue(2, out StringValue);
            //    Debug.Log("收到服务器的响应Event推送,OpCode:1" + intValue.ToString() + ":" + StringValue.ToString());
            //    break;

            default:
                break;
        }
    }

    public void OnOperationResponse(OperationResponse operationResponse)
    {
        //Request request = null;
        //此处就是工具类
        DicTool.GetValue(RequestDic, (OperationCode)operationResponse.OperationCode)
            .OnOprationRespionse(operationResponse);

        //下面注释的代码可以用上方工具类代替
        //bool b =RequestDic.TryGetValue((OperationCode)operationResponse.OperationCode, out request);
        //if (b)
        //{
        //    request.OnOprationRespionse(operationResponse);
        //}
        //else
        //{
        //    Debug.LogError("未找到对应code的请求request");
        //}

        return;
        //switch (operationResponse.OperationCode)
        //{
        //    case 1:
        //        Debug.Log("收到服务器的响应,OpCode:1");

        //        //解析数据
        //        var data = operationResponse.Parameters;
        //        object intValue;
        //        data.TryGetValue(1, out intValue);
        //        object StringValue;
        //        data.TryGetValue(2, out StringValue);
        //        Debug.Log("收到客户端的请求,OpCode:1" + intValue.ToString() + ":" + StringValue.ToString());
        //        break;
        //            default:
        //                break;
        //        }
        }

    public void OnStatusChanged(StatusCode statusCode)
    {
        Debug.LogError(statusCode);
    }

    //private Dictionary<OperationCode, Request> RequestDic = new Dictionary<OperationCode, Request>();

    public void AddRequest(Request r)
    {
        RequestDic.Add(r.OpCode, r);
    }
    public void RemoveRequest(Request r)
    {
        RequestDic.Remove(r.OpCode);
    }
    public Request GetRequest(OperationCode code)
    {
        return DicTool.GetValue(RequestDic, code);
    }

}

———————————————————————————————————————————服务器

ShowHandler.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Common;
using Photon.SocketServer;

namespace RRGameServer.Handler
{
    class ShowHandler : BaseHandler
    {
        public ShowHandler()
        {
            OpCode = Common.OperationCode.Show;
        }

        public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer)
        {
            int id = (int)DicTool.GetValue<byte, object>(operationRequest.Parameters, (byte)ParaCode.Show);
            MyGameServer.log.Info("收到Show的ID:" + id);
            peer.id = id;
            //返回:已经收到消息
            OperationResponse response = new OperationResponse(operationRequest.OperationCode);
            response.ReturnCode = (short)Common.ReturnCode.Success;
            peer.SendOperationResponse(response, sendParameters);

            //发送给其他的客户端 OnEvent
            //发给自己
            //推送一个Event
            EventData ed = new EventData((byte)OperationCode.Show);//操作码
            var data2 = new Dictionary<byte, object>();
            data2.Add((byte)ParaCode.Show, id);//参数码
            ed.Parameters = data2;
            //peer.SendEvent(ed, new SendParameters());
            foreach (var peerItem in MyGameServer.Instance.PeerList)
            {
                peerItem.SendEvent(ed, new SendParameters());
            }
        }



    }
}

MoveHandler.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Common;
using Photon.SocketServer;

namespace RRGameServer.Handler
{
    class MoveHandler : BaseHandler
    {
        public MoveHandler()
        {
            OpCode = Common.OperationCode.Move;
        }

        public override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters, ClientPeer peer)
        {
             string location= (string)DicTool.GetValue<byte, object>(operationRequest.Parameters, (byte)ParaCode.Move);
            MyGameServer.log.Info("收到Move的location:" + location);
           
            //返回:已经收到消息
            OperationResponse response = new OperationResponse(operationRequest.OperationCode);
            response.ReturnCode = (short)Common.ReturnCode.Success;
            peer.SendOperationResponse(response, sendParameters);

            //发送给其他的客户端 OnEvent
            //发给自己
            //推送一个Event
            EventData ed = new EventData((byte)OperationCode.Move);//操作码
            var data2 = new Dictionary<byte, object>();
            data2.Add((byte)ParaCode.Move, location+","+peer.id);//参数码
            ed.Parameters = data2;
            //peer.SendEvent(ed, new SendParameters());
            foreach (var peerItem in MyGameServer.Instance.PeerList)
            {
                if (peer == peerItem) continue;
                peerItem.SendEvent(ed, new SendParameters());
            }
        }
    }
}

ClientPeer.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Common;
using Photon.SocketServer;
using PhotonHostRuntimeInterfaces;
using RRGameServer.Handler;

namespace RRGameServer
{
    public class ClientPeer : Photon.SocketServer.ClientPeer
    {
        public int id;
        public ClientPeer(InitRequest initRequest) : base(initRequest)
        {
        }

        //当每个客户端断开时
        protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail)
        {
            
        }

        //当客户端发起请求的时候
        protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
        {
            BaseHandler handler = DicTool.GetValue(MyGameServer.Instance.HandlerDic, (OperationCode)operationRequest.OperationCode);
            if (handler != null)
            {
                handler.OnOperationRequest(operationRequest, sendParameters,this);
            }
            else
            {
                MyGameServer.log.Info("找不到操作码:" + (OperationCode)operationRequest.OperationCode);
            }

            return;
            //switch (operationRequest.OperationCode)
            //{
            //    case 1:
            //        //解析数据
            //        var data = operationRequest.Parameters;
            //        object intValue;
            //        data.TryGetValue(1, out intValue);
            //        object StringValue;
            //        data.TryGetValue(2, out StringValue);
            //        //输出参数
            //        MyGameServer.log.Info("收到客户端的请求,opcode:1"+intValue.ToString()+":"+StringValue.ToString());

            //        //返回响应
            //        OperationResponse opResponse = new OperationResponse(operationRequest.OperationCode);

            //        //构造参数
            //        var data2 = new Dictionary<byte, object>();
            //        data2.Add(1, 100);
            //        data2.Add(2, "这是服务端做的响应");
            //        opResponse.SetParameters(data2);
            //        //返回code,为发送过来的code,返回的参数,为发送过来的参数
            //        SendOperationResponse(opResponse, sendParameters);

            //        //推送一个Event
            //        EventData ed = new EventData(1);
            //        ed.Parameters = data2;
            //        SendEvent(ed, new SendParameters());
            //        break;
            //    default:
            //        break;
            //}
        }
    }
}

MyGameServer.cs:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Common;
using ExitGames.Logging;
using ExitGames.Logging.Log4Net;
using log4net.Config;
using Photon.SocketServer;
using RRGameServer.Handler;
using RRGameServer.Manager;

namespace RRGameServer
{
    //所有的Server,都要继承ApplicationBase,然后实现ApplicationBase的三个方法
    public class MyGameServer : ApplicationBase
    {
        public static MyGameServer Instance;
        public static readonly ILogger log = LogManager.GetCurrentClassLogger();
        public List<PeerBase> PeerList = new List<PeerBase>();//用户保存所有的peer,一个客户端连接就会有一个peer

        //当有一个客户端连接上以后,就会执行此方法
        protected override PeerBase CreatePeer(InitRequest initRequest)
        {
            log.Info("一个客户端连接");
            var p = new ClientPeer(initRequest);
            PeerList.Add(p);
            return p;
        }

        //服务器初始化函数
        protected override void Setup()
        {
            Instance = this;
            log4net.GlobalContext.Properties["Photon:ApplicationLogPath"] = Path.Combine(
                Path.Combine(this.ApplicationRootPath, "bin_Win64"), "log");
            FileInfo configFileInfo = new FileInfo(Path.Combine(this.BinaryPath, "log4net.config"));

            if (configFileInfo.Exists)
            {
                LogManager.SetLoggerFactory(Log4NetLoggerFactory.Instance);//photon知道日志输出
                XmlConfigurator.ConfigureAndWatch(configFileInfo);//读取配置
            }
            log.Info("服务器启动啦");

            //log.Info(UserManager.Instance.GetUserById(7).Username);
            InitHandler();//初始化
        }

        //服务器关闭函数
        protected override void TearDown()
        {
            log.Info("服务器关闭啦");
        }

        public Dictionary<OperationCode, BaseHandler> HandlerDic = new Dictionary<OperationCode, BaseHandler>();

        public void InitHandler()
        {
            //一开始就全部初始化
            LoginHandler LH = new LoginHandler();
            //log.Info("初始化操作码:" + LH.OpCode);
            HandlerDic.Add(LH.OpCode, LH);
            SignHandler handler = new SignHandler();
            HandlerDic.Add(handler.OpCode, handler);
            ChatHandler chathandler = new ChatHandler();
            HandlerDic.Add(chathandler.OpCode, chathandler);
            ShowHandler showhandler = new ShowHandler();
            HandlerDic.Add(showhandler.OpCode, showhandler);
            MoveHandler movehandler = new MoveHandler();
            HandlerDic.Add(movehandler.OpCode, movehandler);
        }

    }
}

AirParrot是第三方的Mac屏幕镜像软件,可将Mac的屏幕通过Apple TV镜像到电视机屏幕。在Mountain Lion发布以前,想到达到镜像目的的用户都用AirParrot。但是AirParrot不是一款完美无缺的软件。如今Mountain Lion自带了AirPlay镜像,到底是官方的好,还是第三方的更强?对比之后自然有结论。   从应用的范围来说,AirParrot显然更具优势。OS X 10.8自带的AirPlay镜像只局限于2011年之后的Mac,而且其他OS X系统也没有这样的功能。第三方的AirPlay限制可没那么多,它只要求OS X 10.6.8或更高版本的系统,对Mac的型号倒没有什么限制。可是在实际的使用中,哪一款令人更满意呢?   首先测试苹果官方的AirPlay镜像。在实际使用中,镜像到电视机屏幕的画面非常清晰、细腻,帧速率也很流畅。MacBook Pro的桌面镜像到电视机时,会让你感觉不到这是屏幕镜像,还误以为是内置屏幕的画面。无论是播放iTunes文件、浏览网页、编辑文本、观看演示幻灯片或者播放QuickTime视频,整个体验都非常流畅。无论是设置1920x1080分辨率,还是设置标准的“Retina”分辨率,坐在沙发上都可以识别出标准的用户界面文字。   第三方的AirParrot售价10美元,适用于任何英特尔处理器的Mac。但是型号越老的硬件,运行的效果当然越差。在一台2.0GHz Core 2 Duo的MacBook Air,AirParrot显得有些不稳定了。AirParrot也能支持Windows。   AirParrot的功能不仅仅的镜像,它也可以设置电视机屏幕为外部屏幕,优化设置屏幕镜像的视频质量和帧速率。与官方AirPlay镜像不同的是,AirParrot还可以开启或关闭镜像时的音频输出。开启时,它会提示用户下载一个声卡驱动,并重启电脑。   经过对比发现,虽然AirParrot功能更丰富,局限更少,但是苹果对AirPlay镜像所做的局限是有道理的,要将电脑屏幕镜像到电视机的确对硬件有一定的要求,否则用户体验直线下降,扫用户的兴。如果你对功能的数量不是特别讲究,官方的AirPlay将更适合你
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值