unity3d 双人巡逻兵网络游戏

unity3d 双人巡逻兵网络游戏

github传送门如下
https://github.com/ddghost/unity3d_n/tree/%E7%BD%91%E7%BB%9C%E6%B8%B8%E6%88%8F

这次作业要做的是一个网络游戏,我不想重做一个新的游戏,感觉以前做过的游戏中巡逻兵会有意思些,所以我选择了做一个双人巡逻兵。由于开始不熟悉网络游戏客户端和主机是如何交互同步的,其实做完了这次作业也还是有些不懂的地方,所以代码有的地方会冗余,还有的地方实现起来很丑陋。

这个是开始的效果,刚开始开主机在中间的房子只有一个玩家,在另一个客户端连上主机后,出现了两个玩家(只能两个人玩,一个主机,一个客户端,而且客户端不能在连上后断开再连,否则会有奇葩的效果,等下会说明为什么)。
这里写图片描述

这个就是玩起来的效果,由于我是单人控制,因此只能分开玩,先玩左边,再玩右边,人物都死了之后,主机玩家可以选择重新开始。
这里写图片描述

由于本次是在之前的代码基础上改,所以关于巡逻兵游戏的代码细节就不再详述,只讲几个关键的问题和步骤。

准备工作

开始的时候我还是一脸懵逼的做,那就先从抄开始,首先抄课上老师给的步骤。先按ctrl+s场景保存,然后在左上方菜单栏选file->build setting。
这里写图片描述

然后添加场景,不然添加场景到networkManager会有问题。
这里写图片描述

然后给所有预置添加netWorkIndentity和networkTransform(一些静态对象也许不用加network transform),并且network transform的第一个属性send rate设置为25(高点的话,客户端就不会有卡顿的效果)。
这里写图片描述
player的netWorkIndentity选择local player authority,并且network transfrom的第二个属性不要选rigidbody 3d,选transform(选rigidbody的话人物被撞倒后重新开始,人物还是倒下的,原理的话不太清楚),同时还要加network animation组件来同步动画,老实说,不明白这些为什么也要手动加,开始搞了半天不知道为什么动画不同步,这些东西都要手动加真的挺麻烦,就不能全都自动同步吗?
这里写图片描述

然后创建一个空对象,命名为NetworkManager,并且添加NetworkManager和NetworkManager Hud组件。注意networkManager组件要改的有如图四个红圈,第一个红圈设置场景,如果刚才build setting没弄好是设置不了的。第二个红圈设置玩家预置,第三个设置玩家初始位置为round bin,第四个设置孵化的预置(把要用到的预置加进去好了,懒得想)。
这里写图片描述

为了有两个初始位置,添加两个空对象p1,p2,添加network start position组件,这样在network manager中玩家初始位置选择了round bin的情况下,每次玩家加入,玩家的位置都会在这两个位置轮流选,这也是为什么不能连了再重进,那样第三次进入的位置就会是第一次进入的位置,这样玩家就会重叠在一次,显然不行。
这里写图片描述

然后这个时候点菜单来的file->built&run,这会弹一个游戏框,游戏启动可以设置窗口化和分辨率,然后unity也点击运行,这样运行两个游戏,一个当主机,一个连上去,就可以看到两个倒霉的玩家出现在屏幕中,并且因为设置了刚体,有重力,所以一直往下掉。
这里写图片描述

加载场景

做到这里,麻烦才开始来了。首先第一个问题就是以前是使用场记加载场景的,但这次作业是网络游戏,那么场景该怎么加载呢?首先搞明白场记放哪里,场记应该要放在服务器那里,因为它应该是惟一的,不能放在客户端那里。那么我们和以前一样就在场景中先弄个空对象,命名为main,由于是网络游戏,这个main要添加networkIdentity组件,勾选server only,然后把场记脚本挂上。
这里写图片描述

同时注意到所有预置都是场记加载的,为了在客户端也显示,就要把它孵化出来,因此每个加载预置的语句下面都加上NetworkServer.Spawn(····)

    public void loadResources(){
        var map = (GameObject)Instantiate (Resources.Load ("Prefabs/map"), Vector3.zero, Quaternion.identity);
        NetworkServer.Spawn(map) /////!!!!!!!!
        checkerCtrl = new CheckerController[checkerLoc.Length];
        playerCtrlList = new List<PlayerController> ();
        for (int loop = 0 ; loop < checkerLoc.Length ; loop++) {
            GameObject checker = (GameObject)Instantiate (Resources.Load ("Prefabs/checker")
                , checkerLoc[loop] , Quaternion.identity) as GameObject;
            NetworkServer.Spawn(checker);  /////!!!!!!!!
            checker.name = "checker" + loop.ToString ();
            checkerCtrl[loop] = checker.AddComponent(typeof (CheckerController)) as CheckerController;
        }
    }

这样应该就能加载出场景(由于做的时候很混乱bug多,不确定有没有漏步骤)。

客户端的玩家操作

场景好了,接下来就是控制还有游戏的逻辑问题。首先是玩家的控制问题,要判定控制的是不是本地玩家,不然连别的玩家都能控制那就大错特错了。代码可参见如下,isLocalPlayer能判断控制的到底是不是本地玩家。

    void Update () {
        if (!isLocalPlayer)
            return;
        moveControl ();
    }

除此之外,注意到游戏结束后重新开始,要重新设置玩家的位置,动画等等,这些是要每个客户端都做的,因此函数前要加[ClientRpc]。如果不这样的话,各个客户端就不同步了。

    [ClientRpc]
    public void RpcResetPosition(Vector3 position){

        gameObject.GetComponent<Rigidbody> ().velocity = Vector3.zero;
        this.transform.rotation = Quaternion.identity;
        this.transform.position = position;
        ifRun = false;
        nowDirection = 0;
        status = true;
    }

    [ClientRpc]
    public void RpcSetAnimatorBool(string name , bool boolValue){
        if (isLocalPlayer)
            this.GetComponent<Animator> ().SetBool (name, boolValue);
    }

主机的场记

其次就是场记的逻辑,由于玩家是在场记脚本初始化后分开加入的,而场记要判断玩家是不是都死了才能决定游戏是否结束,因此场记要有一个队列存储玩家。玩家刚进入游戏的时候就要通知场记把玩家加入到这个队列中。通知场记的函数CmdAddPlayerToScene是要在主机而不是客户端上执行的,因为只有主机上才有场记,因此要在该函数上写 [Command]。
同时注意到,主机玩家创建游戏后,第二个玩家加入时,主机上会新加入第二个玩家控制的player,上面也有挂载player上的脚本,所以CmdAddPlayerToScene会在主机上的player调用一次,客户端上的player也调用一次,这样第二个玩家加入,仅一个玩家加入,场记却要加入两个玩家入队列,这是不对的,因此通知场记时要注意是否是本地玩家加入。

    void Start () {
        if(isLocalPlayer)
            CmdAddPlayerToScene();
        status = true;
    }

    [Command]
    void CmdAddPlayerToScene(){
        Director.getInstance ().CurrentSceneController.addPlayer (gameObject);
    }

最后,同时也是我比较头疼的地方,那就是游戏结束后,代表得分的红色小块要全部重新显现出来,正常来说直接setActive就好了,但是这里像以前那样只在场记set一下是不行的,因为只有主机有场记,所以只有主机玩家的红色块才会重新,会主机玩家然后我又试着在场记新弄了一个函数,前面加个[ClientRpc],希望每个客户端都会这样去做,结果报错。最后我就在场记调用主机玩家的一个[ClientRpc]的函数,这样就可以了,感觉很迷,难道是因为客户端中没有场记那个函数,所以场记的函数不能在所有客户端调用?最后我采取的这种方案感觉不是个好的方法。

    [ClientRpc] //player控制脚本
    public void RpcSetScore(GameObject score , bool boolValue){
        score.SetActive (boolValue);
    }

    //场记代码
    for (int loop1 = 0 ; loop1 < checkerLoc.Length ; loop1++) {
        checkerCtrl [loop1].reset ();
        playerCtrlList [0].playerScript.RpcSetScore (checkerCtrl [loop1].getScore() , true );//!!!!
    }   

结语

最后不得不说,网络游戏的同步还是个很头痛的东西,第一次设计感觉还是没有什么章法,代码写的有些乱,可能还有一些小bug,刚开始觉得只要把以前的代码改改就好了,结果这次作业还是做了很久。边写博客边发现代码冗余,又边改代码,感觉写博客还是能反思不少,发现不少问题。

猜你喜欢

转载自blog.csdn.net/DDghsot/article/details/80793904