从Cocos到Unity

unity3D - 人物操作(3D视角控制 + 遥杆移动)

2022-01-11  本文已影响0人  lzyickobe

本文基于unity3D开发一个MMORPG游戏,阐述一下项目中研究3D自由视角控制 和 摇杆操作人物的经验。

整体分为五个部分:

一、3D视角控制

二、摇杆操作移动

三、3D视角结合摇杆一起操作

四、PC适配

五、多人网络同步

一、 3D视角控制(移动端)

3D视角控制的核心逻辑 : 根据用户滑动屏幕的操作,实时旋转摄像头的位置信息。

拆分一下,解决两件事就行:

1、识别并获取用户滑动屏幕的操作

2、实时旋转摄像头的位置信息

先来解决第一件事:如何识别和获取用户滑动屏幕的操作?

用户在屏幕上的是touch事件,这件事比较简单,直接拿用户滑动屏幕的距离即可,API:Input.GetTouch(0).deltaPosition

有可能多个手指在屏幕上滑动,所以这里还必须处理的另一件事是多指触控,代码如下:

//双指触控管理函数
    private void TwoFingerControl()
    {
        if (Input.touchCount == 0)
        {
             isInUITouch0 = false;
             isInUITouch1 = false;
        }
        else if (Input.touchCount == 1)
        {
            if (Input.GetTouch(0).phase == TouchPhase.Began)
            {
                OnFingerDown(Input.GetTouch(0).position, 0);
            }
            else if (Input.GetTouch(0).phase == TouchPhase.Moved || Input.GetTouch(0).phase == TouchPhase.Stationary)
            {
               OnFingerMove(Input.GetTouch(0).deltaPosition, 0);
            }
            else
            {
                if (Input.GetTouch(0).phase == TouchPhase.Moved || Input.GetTouch(0).phase == TouchPhase.Stationary)
                {
                    OnFingerMove(Input.GetTouch(0).deltaPosition, 0);
                }
            }
        }
        else if (Input.touchCount >= 2)
        {
            if (Input.GetTouch(0).phase == TouchPhase.Began)
            {
                OnFingerDown(Input.GetTouch(0).position, 0);

            }
            else if (Input.GetTouch(0).phase == TouchPhase.Moved || Input.GetTouch(0).phase == TouchPhase.Stationary)
            {
                    OnFingerMove(Input.GetTouch(0).deltaPosition, 0);
            }

            if (Input.GetTouch(1).phase == TouchPhase.Began)
            {
                OnFingerDown(Input.GetTouch(1).position, 1);
            }
            else if (Input.GetTouch(1).phase == TouchPhase.Moved || Input.GetTouch(1).phase == TouchPhase.Stationary)
            {
                OnFingerMove(Input.GetTouch(1).deltaPosition, 1);
            }
        }
    }

当按下的时候判断是哪个手指的序号,确认一下是否触摸在正确的位置上,如果是正确的位置,则将deltaPosition传入onFingerMove方法做接下来的操作。

接下来解决第二件事:实时旋转摄像头的位置信息

调用camera的RotateAround方法旋转摄像头。API:gameCamera.transform.RotateAround

但不能随意选择摄像头,会引起穿模,所以就需要限制摄像头的x坐标不能超过一定角度,在这个角度上不会影响穿模,具体角度可以自行设定,这里是[10,80]

代码如下:

//获取摄像机初始位置
            Vector3 pos = gameCamera.transform.position;
            //获取摄像机初始角度
            Quaternion rot = gameCamera.transform.rotation;

            //摄像机围绕player的位置延player的Y轴旋转,旋转的速度为鼠标水平滑动的速度
            gameCamera.transform.RotateAround(player.transform.position, player.transform.up, deltaPosition.x * rotaSpeed);
            //可以设定人物的方向要不要跟随转动
            //player.transform.RotateAround(player.transform.position, player.transform.up, deltaPosition.x * rotaSpeed);
            //摄像机围绕player的位置延自身的X轴旋转,旋转的速度为鼠标垂直滑动的速度
            gameCamera.transform.RotateAround(player.transform.position, gameCamera.transform.right, -deltaPosition.y * rotaSpeed);

            //获取摄像机x轴向的欧拉角
            float x = gameCamera.transform.eulerAngles.x;

            //如果摄像机的x轴旋转角度超出范围,恢复初始位置和角度
            if (x < 10 || x > 80)
            {
                gameCamera.transform.position = pos;
                gameCamera.transform.rotation = rot;
            }

把以上代码放到update()函数体中调用,即可完成3D视角控制的事情。

二、 摇杆操作移动(移动端)

摇杆操作移动的核心原理:制作一个摇杆按钮,记录用户触摸操作摇杆的实时位置,从而控制人物的方向和位置信息。

第一件事:制作一个摇杆按钮,记录用户触摸摇杆的实时位置

1、用两张图叠加一下,挂载到一个GameObject上,取名joystick

image.png

2、在这个物体上增加两个Event Trigger事件,分别是Drag和End Drag,让按钮可以拖动事件返回 和 结束拖动的事件返回


image.png

3、绑定一个joystick的脚本,处理这两个事件,拿到用户触摸摇杆的实时位置信息

//拖动
public void on_stick_drag() {
    Vector2 pos = Vector2.zero;
    //多指的判断就不再这里贴了
    RectTransformUtility.ScreenPointToLocalPointInRectangle(this.transform as RectTransform, Input.GetTouch(0).position, this.cs.worldCamera, out pos);
    float len = pos.magnitude;
        if (len <= 0) {
            this.touch_dir = Vector2.zero;
            return;
        }

        // 归一化处理,方便后续做其他逻辑
        this.touch_dir.x = pos.x / len; // cos(r)
        this.touch_dir.y = pos.y / len; // (sinr) cos^2 + sin ^ 2 = 1;
        if (len >= this.max_R) { // this.max_R / len = x` / x = y` / y;
            pos.x = pos.x * this.max_R / len;
            pos.y = pos.y * this.max_R / len;
        }
        this.stick.localPosition = pos;
}
//结束拖动
public void on_stick_end_drag() {
    this.stick.localPosition = Vector2.zero;
    this.touch_dir = Vector2.zero;
}

这样通过这两个事件触发就拿到了拖动和结束拖动的信息,并且将拖动的坐标归一化处理。

第二件事:根据拖动的归一化数据,控制人物移动

private void onJoyStickEvent(string uname, object udata) {
        if (this.state != (int)RoleState.Idle && this.state != (int)RoleState.Run) {
            return;
        }
        if (dir.x == 0 && dir.y == 0) {
            this.setState((int)RoleState.Idle);
            return;
        }

        this.setState((int)RoleState.Run);

        float vx = this.speed * dir.x;
        float vz = this.speed * dir.y;

        float dt = Time.deltaTime;
        Vector3 pos = this.transform.position;
        pos.x += (vx * dt);
        pos.z += (vz * dt);
        if (!this.astar.isWordPosCanGo(pos)) {
            return;
        }

        this.transform.LookAt(pos);
        this.transform.position = pos;
    }

拿到归一化的x和y坐标,乘以速度,然后更新玩家的朝向和位置信息。

到这里,3D视角控制和摇杆操作这两部分就完成了,但有个很大的问题,目前不能一个手指控制摇杆,一个手指控制视角,所以接下来看第三部分。

三、 3D视角结合摇杆一起操作

这个双指同时操作的核心原理:先判断摄像头旋转后的方向信息,然后将摇杆移动的坐标信息旋转一个角度得到正确的移动坐标位置。

先判断哪个手指是摇杆,哪个手指是视角控制。这件事就不阐述了

来看下核心点:如何判断摄像头旋转后的方向信息,然后将移动的坐标信息旋转一个角度得到正确的移动坐标位置。

从数学上来讲,就是已知圆心(0,0)的情况下,将一个点(x,y),旋转一个角度,得到一个新的点(x1,y1)

具体介绍可以看这篇文章:https://www.zhihu.com/question/58468471

公式是:

/** 旋转公式
* x1=xcosθ-ysinθ
* y1=xsinθ+ycosθ
*/

直接上代码:

//因为我的摄像头是顺时针旋转的,这个公式求的是逆时针旋转的新位置,所以需要将摄像头顺时针转向的角度 转换成 逆时针转向的角度,顺时针旋转一个角度就等于逆时针旋转360减去这个角度
        float rotateAngel = 360 - gameCamera.transform.eulerAngles.y;
        //计算一个点 逆时针旋转了rotateAngel角度之后,得到的新点坐标
        //这个意思是,摄像机旋转一个角度之后,要将人物操作杆的坐标重定向到新方向的坐标位置
        /** 旋转公式
         * x1=xcosθ-ysinθ
         * y1=xsinθ+ycosθ
         */
        float x1 = dir.x * Mathf.Cos(rotateAngel * Mathf.Deg2Rad) - dir.y * Mathf.Sin(rotateAngel * Mathf.Deg2Rad);
        float y1 = dir.x * Mathf.Sin(rotateAngel * Mathf.Deg2Rad) + dir.y * Mathf.Cos(rotateAngel * Mathf.Deg2Rad);
        dir.x = x1;
        dir.y = y1;

这样的话,就搞定了摇杆移动和视角控制混合操作了。

四、 PC适配

简单说一下PC适配的两个逻辑,移动控制是将AWSD按键换算成摇杆操作的4个归一化方向,分别是(0,1),(1,0),(0,-1),(-1,0)

代码如下:

if (Input.GetKey(KeyCode.W))
        {
            EventMgr.Instance.Emit("JoyStick", new Vector2(0, 1));
        }
        else if (Input.GetKey(KeyCode.S))
        {
            EventMgr.Instance.Emit("JoyStick", new Vector2(0, -1));
        }
        else if (Input.GetKey(KeyCode.D))
        {
            EventMgr.Instance.Emit("JoyStick", new Vector2(1, 0));
        }
        else if (Input.GetKey(KeyCode.A))
        {
            EventMgr.Instance.Emit("JoyStick", new Vector2(-1, 0));
        }
        //如果鼠标抬起来,就取消人物的动作
        if (Input.GetKeyUp(KeyCode.W) || Input.GetKeyUp(KeyCode.S) || Input.GetKeyUp(KeyCode.A) || Input.GetKeyUp(KeyCode.D))
        {
            EventMgr.Instance.Emit("JoyStick", new Vector2(0, 0));
        }

另一个逻辑是,视角控制是将移动端的touch事件转换成PC的鼠标事件,用MouseX代替touch.deltaPos其他逻辑是一致的

代码如下:

private void RotateView()
    {
        #if UNITY_EDITOR || UNITY_STANDALONE_WIN
        if (Input.GetMouseButtonDown(1))
        {
            isRotaing = true;
        }
        if (Input.GetMouseButtonUp(1))
        {
            isRotaing = false;
        }
        if (isRotaing)
        {
            //获取摄像机初始位置
            Vector3 pos = gameCamera.transform.position;
            //获取摄像机初始角度
            Quaternion rot = gameCamera.transform.rotation;

            //摄像机围绕player的位置延player的Y轴旋转,旋转的速度为鼠标水平滑动的速度
            gameCamera.transform.RotateAround(player.transform.position, player.transform.up, Input.GetAxis("Mouse X") * rotaSpeed);
            player.transform.RotateAround(player.transform.position, player.transform.up, Input.GetAxis("Mouse X") * rotaSpeed);
            //摄像机围绕player的位置延自身的X轴旋转,旋转的速度为鼠标垂直滑动的速度
            gameCamera.transform.RotateAround(player.transform.position, gameCamera.transform.right, -Input.GetAxis("Mouse Y") * rotaSpeed);

            //获取摄像机x轴向的欧拉角
            float x = gameCamera.transform.eulerAngles.x;

            //如果摄像机的x轴旋转角度超出范围,恢复初始位置和角度
            if (x < 10 || x > 80)
            {
                gameCamera.transform.position = pos;
                gameCamera.transform.rotation = rot;
            }

        }
        cameraOffset = gameCamera.transform.position - player.transform.position;//更新
#elif UNITY_ANDROID
        TwoFingerControl();
        cameraOffset = gameCamera.transform.position - player.transform.position;//更新    
#endif
    }

五、 多人网络同步(状态同步)

多人网络移动同步涉及前后端通信,不是本文重点

简单描述下状态同步流程:

1、客户端将joystick拿到的触摸信息归一化,加上玩家摄像头转向信息合并,就是第三部分的混合计算出来的转向+位置信息,发送给后台

2、后台根据客户端的这些信息来计算这个用户的坐标位置(可以设置处理间隔,比如1秒处理20次,就是50ms一次计算,根据后台处理能力和客户端位置同步帧率来平衡这个间隔)

3、后台计算得到的坐标发送给客户端,客户端根据这个位置调整玩家的朝向和位置。

这里面有个点,实际上后台只计算玩家的朝向和位置,客户端摄像头的3D视角的角度是不用后台关心的。

上一篇下一篇

猜你喜欢

热点阅读