第三人称控制

来源:互联网 发布:金融学大学知乎 编辑:程序博客网 时间:2024/04/27 16:01

第三人称控制

前言

经过一段时间的Unity 3D学习,练习制作了一个简单的游戏Demo。该Demo实现了一个简单的RPG副本,英雄杀死两种怪兽游戏即获胜。
该Demo使用Unity 3D的版本为 4.6.4f1。

游戏采用第三人称视角(3rd Person Controller),但是Standard Assert中的3rd Person Controller不能满足要求。因此,自己动手实现一个第三人称角色控制。下面将从角色(英雄)控制与Camera控制两方面介绍第三人称控制的实现。

英雄控制

旋转与移动

给英雄添加Character Controller组件。通过水平和垂直虚拟轴获取玩家输入,垂直虚拟轴控制前进后退,水平虚拟轴控制旋转(此过程也带动Camera绕英雄旋转)。只有当英雄在地上时方能操作,也就是controller.isGrounded==true
旋转的关键代码

//此段代码在英雄的脚本组件中//获取水平虚拟轴输入float horizontal = Input.GetAxis ("Horizontal");float rotateY = horizontal * heroRotateSpeed; //英雄自转float cameraRotateHero = horizontal * cameraRotateSpeed;//Camera绕英雄转controller.transform.Rotate(0, rotateY*Time.deltaTime, 0); //人物调整朝向mainCamera.transform.RotateAround(transform.position, Vector3.up,                                                   cameraRotateHero * Time.deltaTime); //摄像机也旋转

移动的关键代码。首先根据输入,获得移动的本地方向向量,然后将其转为世界坐标系中的方向。

float vertical = Input.GetAxis("Vertical");Vector3 targetDirection = new Vector3 (0.0f, 0.0f, vertical);  //本地Z方向if (targetDirection != Vector3.zero) {targetDirection = targetDirection.normalized;    }movingDirection = transform.TransformDirection (targetDirection) ;  //向量变换self -> world//...根据不同的操作设置移动速度movingSpeed,例如走、慢跑、快跑的速度是不同的movingDirection *= movingSpeed;if (Input.GetButton("Jump")) {...}movingDirection.y -= gravity * Time.deltaTime;  //重力if (movingDirection != Vector3.zero) {    collisionFlags = controller.Move (movingDirection * Time.deltaTime ); //当没有移动时,collisionFlags为None,所以if}CameraFollow (); //摄像机跟随移动

跳跃

考虑重力的影响,每一帧都会给英雄一个向下的速度movingDirection.y -= gravity * Time.deltaTime; //重力影响。当跳跃时,给英雄添加一个向上的速度初速度:

    //计算跳跃时的初速度float CalculateJumpVerticalSpeed (float targetJumpHeight) {        return Mathf.Sqrt(2 * targetJumpHeight * gravity); //位移公式Vo^2 / 2g = H -> Vo = Sqrt(2gH)}if (Input.GetButton("Jump")) {    _characterState = CharacterState.Jumping;    movingDirection.y = CalculateJumpVerticalSpeed(jumpHeight);    movingDirection.z = Mathf.Min (movingDirection.z, 0.2f);}

动画

英雄移动过程中,播放相应状态的动画。当玩家没有操作时,需要自动切换到Idle动画,由于攻击动画是触发式的,必须等待期播放完毕才能切换到其他状态的动画,使用_animation.IsPlaying(clip.name)可以判断某动画片段是否播放完毕。

死亡

英雄死亡时,需要播放死亡动画,之后并处理后续工作,例如通知游戏结束。这里采用协同程序实现。

    IEnumerator Dying()    {        _animation [dying.name].speed = 1f;        _animation [dying.name].wrapMode = WrapMode.Once;        _animation.CrossFade (dying.name);        yield return new WaitForSeconds (5.0f); //等待动画播放完毕        //transform.gameObject.SetActive (false);        //Destroy (transform.gameObject, 1.0f);        //Time.timeScale = 0f; //暂停        gameResult.SendMessage ("GameLoseEnable"); //输了    }

第三视角Camera控制

第三人称的主摄像机需要时刻跟随英雄移动,考虑到英雄的跳跃、动画时不确定的移动、死亡(可能销毁GameObject)等状态,游戏中没有将主摄像机作为英雄的子节点,降低耦合。同时,为了方便不同视角的观察角色,需要根据玩家的操作调整视角。因此,需要解决Camera的移动、相对英雄的位置和距离变化等问题。

旋转和移动

当英雄旋转时,Camera也同方向、同轴旋转,但是角速度要小于英雄旋转的角速度(避免旋转太快产生眩晕)。

Camera的移动向量由英雄的移动向量获取。但是考虑以下两种情况:1.英雄跳跃;2.英雄当前遇到障碍物。

游戏中,英雄跳跃时,Camera不会跳跃,英雄时刻受重力影响会有一个垂直向下的速度,但Camera不受重力的影响。因此,Camera在y方向的变换为0,只有根据玩家的操作变化。

当英雄遇到障碍物,但移动向量不为零时,Camera是不移动。具体代码如下所示:

//该函数在英雄脚本组件中void CameraFollow(){    //camera 移动    Vector3 cameraMove = movingDirection * Time.deltaTime;;    if ((collisionFlags & CollisionFlags.CollidedSides) != 0) {        //遇到障碍物,不移动,只是看着玩家        mainCamera.transform.LookAt (transform.position);    }     else {        cameraMove.y = 0.0F; //高度由玩家自己调整        mainCamera.transform.Translate (cameraMove, Space.World);    }}

视角调整

视角有三种变换:
1. 以英雄的up为轴,在xz平面旋转;
2. 调整高度,升降;
3. 推进或者远离角色;
camera的三种操作

方式1水平旋转比较简单,也没有限制。

    //水平旋转    float h = Input.GetAxis ("Mouse X");    h *= rotationXSpeed;    if (Mathf.Abs(h) > 0.01f){            mainCamera.transform.RotateAround(hero.transform.position, Vector3.up, h * Time.deltaTime);            mainCamera.transform.LookAt(hero.transform.position);            //如果是鼠标右键则人物也旋转            if (Input.GetMouseButton(1)){                hero.transform.Rotate(Vector3.up, h*Time.deltaTime);            }        }

其中,2和3需要做边界限定。2不能将camera降到地面以下或者升到英雄的正上方,3不能无限制的推进或者远离。
方式2高度通过向量(英雄,Camera)与水平面的夹角限制。该夹角的Tan值,也就是Camera相对英雄的高度H/水平距离D。

        //垂直变化        float v = -Input.GetAxis ("Mouse Y");        v *= changeYSpeed; //在y方向变化量        if (Mathf.Abs (v) > 0.01f)         {            float nextY = mainCamera.transform.position.y + v; //下一个位置的y值            if (nextY > minHeight || v > 0f) {                Vector3 xyHero = hero.transform.position;                Vector3 xyCamera = mainCamera.transform.position;                xyHero.y = xyCamera.y = 0.0f;                xyCamera -= xyHero;                float dist = Mathf.Max (minDistance, xyCamera.magnitude);                float tanA = nextY / dist;                if (tanA < Mathf.Tan(maxAngle/180*Mathf.PI) && tanA > Mathf.Tan(minAngle/180*Mathf.PI) )                 {                    //上下移动                    mainCamera.transform.Translate(0.0f, v, 0.0f);                    mainCamera.transform.LookAt(hero.transform.position);                }            }        }

方式3通过限制Camera与英雄的相对距离实现,调整距离过程中采用平滑插值Lerp,避免跳跃性太大。

        //滚动鼠标滚轮,推近摄像机        float deltaScroll = Input.GetAxis ("Mouse ScrollWheel");        if (Mathf.Abs(deltaScroll) >= 0.01f) {            Vector3 from = mainCamera.transform.position;            Vector3 to = hero.transform.position;            if (deltaScroll > 0) {                //远离                Vector3 closestPoint = (from - to).normalized * minDistance + to; //从摄像机到人物方向,最近处的点                mainCamera.transform.position = Vector3.Lerp(from, closestPoint, closingSpeed * Time.time);            }             else {                //远离                Vector3 farPoint = (from - to).normalized * maxDistance + to; //从人物到摄像机方向,最远处的点                mainCamera.transform.position = Vector3.Lerp(from, farPoint, closingSpeed * Time.time);            }            mainCamera.transform.LookAt(hero.transform.position);        }
0 0
原创粉丝点击