黑魂复刻游戏的摄像机控制——Unity随手记

600 阅读3分钟

今天实现的内容:

鼠标输入

在没有引入手柄操作前,将由鼠标来控制摄像机旋转,首先我们要获取到鼠标输入。

public class PlayerInput : MonoBehaviour
{
    // 摄像机控制轴
    public string cameraAxisX;
    public string cameraAxisY;

    // 摄像机控制信号
    public float cameraUp;
    public float cameraRight;

	// ...
	
    void Update()
    {
        // 摄像机信号
        cameraUp = Input.GetAxis(cameraAxisY);
        cameraRight = Input.GetAxis(cameraAxisX);

		// ...
	}
}

摄像机方案设计

老师的方案为,将摄像机挂载到游戏对象下作为子物体,同时在摄像机的上一级添加一个新对象CameraHandle作为摄像机的父物体。如果我们要旋转,就要旋转整个玩家的游戏对象,上下旋转只需要旋转CameraHandle就行了。 这是新增脚本CameraController ,挂载到Camera上。

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

public class CameraController : MonoBehaviour
{
    // 灵敏度
    public float horizontalSensitivity;
    public float verticalSensitivity;
    // 输入模块
    public PlayerInput pi;

    // PlayerController游戏对象
    private GameObject playerHandle;
    // CameraHandle游戏对象
    private GameObject cameraHandle;
    // 用于存储CameraHandle的欧拉角X值
    private float temp_eulerX;

    void Awake()
    {
        cameraHandle = transform.parent.gameObject;
        playerHandle = cameraHandle.transform.parent.gameObject;
        pi = playerHandle.GetComponent<PlayerInput>();
    }

    // Update is called once per frame
    void Update()
    {
        // 左右旋转时旋转PlayerHandle
        playerHandle.transform.Rotate(Vector3.up, pi.cameraRight * horizontalSensitivity * Time.deltaTime);
        // 上下旋转时旋转CameraHandle
        temp_eulerX -= pi.cameraUp * verticalSensitivity * Time.deltaTime;
        // 限制俯仰角
        temp_eulerX = Mathf.Clamp(temp_eulerX, -40, 30);
        // 赋值localEulerAngles
        cameraHandle.transform.localEulerAngles = new Vector3(temp_eulerX , 0, 0);
            
    }
}


旋转摄像机时的角色模型控制

当摄像机旋转时,角色模型不要跟着旋转。办法很简单,将模型在摄像机旋转前的欧拉角保存下来,在摄像机旋转后再将之前保存的欧拉角赋值回去就行了。所以这两个操作的位置千万别弄错。

    // 角色模型游戏对象
    private GameObject model;

    void Awake()
    {
        // ...
        model = playerHandle.GetComponent<PlayerController>().model;
    }

	// Update is called once per frame
    void Update()
    {
        // 得到摄像机旋转前的模型欧拉角
        Vector3 temp_modelEuler = model.transform.eulerAngles;

        // 摄像机旋转...
        
        // 摄像机旋转后将模型原来的欧拉角再赋给模型
        model.transform.eulerAngles = temp_modelEuler;
    }

当你这样做以后,会有一个惊喜发现,当你转动摄像机而不移动角色时,模型不会动,而当你移动时,模型会自动旋转到摄像机的朝向。原因是因为当你旋转摄像机时,PlayerController游戏对象也进行了旋转,这时整个玩家的前方已经变了。当移动时,会执行旋转代码,这时我们往前走,前方就是摄像机的朝向。所以模型会转到摄像机的朝向。

注意下面代码的dirVec为根据PlayerController游戏对象的方向得到的玩家移动方向,所以前方是PlayerController的前方。当旋转摄像机时,实际上是旋转PlayerController对象,然后摄像机跟着旋转。

		// 计算玩家的方向 这个transform.forward是PlayerInput挂载的游戏对象,也就是PlayerController的transform.forward
		// 所以dirVec得到的是以PlayerController的transform.forward为前方的方向
        dirVec = m_dirAxis.x * transform.right + m_dirAxis.y * transform.forward;

		// ...
        // 只在有速度时能够旋转 防止原地旋转
        if (pi.dirMag > 0.1f)
        {
            // 运用旋转 使用Slerp进行效果优化   
            model.transform.forward = Vector3.Slerp(model.transform.forward, pi.dirVec, 0.3f);
        }

摄像机延迟移动

为了更好的视觉效果,我们常常希望摄像机能相对于角色的移动有一个延迟移动。要实现这种效果,首先我们在摄像机上级再添加一个父对象CameraPos,用来作为摄像机的位置。好了,现在Camera是不是PlayerController的游戏对象都可以了。但是CameraController脚本要挂载到CameraPos身上了。

image.png

现在,我们要在CameraController中获取到Camera游戏对象,再通过CameraPos的位置信息来控制Camera游戏对象的位置。

        // 摄像机游戏对象的移动和旋转
        camera.transform.eulerAngles = this.transform.eulerAngles;
        // 摄像机的位置通过Lerp来实现一种延迟移动的效果
        camera.transform.position = Vector3.SmoothDamp(
            camera.transform.position, this.transform.position, ref temp_dampValue, 0.1f);