Unity:类RTS相机+类编辑器相机+第三人称相机+风景相机的实现 附详细设置步骤和代码

一、简介

此文分享个人在项目中实现几种相机控制、并让这几种相机状态共存(可手动切换)的实现。
抛砖引玉,若发现问题或有改进方案还请不吝赐教。

如果你希望你的项目有更多的相机行为,如玩家可以切换几种控制模式,来使用不同的相机,那么这篇文章也许对你有所帮助。

我提供以下几种相机模式供你参考:
1. 正常相机模式(NormalViewMode)
默认模式。定义游玩时的相机的主要行为,类似《ANNO》系列和一些RTS类型游戏。目的是让玩家便捷直观地控制游戏,并和各种模块产生正常游戏行为,观景并不作为主要目的。
a)WASD使相机在固定Y轴高度平移;
b)按住中键拖动,同上;
c)滚轮拉低(约0°)和抬升视角(约90°),略带缩放效果;
d)右键旋转视角(只限上下),直观上跟滚轮有点类似;
e)相机有相对于城镇中心的距离限制。在我的项目中城镇中心设置为(0f,0f,0f);

2. 自由相机模式(FreeViewMode):
玩家可以手动进入的模式。在此模式下会获得类似编辑器内相机的操作手感。
a)按住右键 + WASD 移动
b)Q/E 上升下降
c)按住中键 平移
d)滚轮 缩放(其实是延蓝轴移动)

3.第三人称相机(ThirdPersonViewMode):
灵感来自于ANNO1800,在其游戏中按R可以由上帝视角变成一个第一人称控制器,在城里四处转悠。
我非常喜欢这个功能,希望可以实现出来。并且想搞个第三人称。
a)任何状态下切换到第三人称相机,会设置bool变量“CamCanBeControll”为false来屏蔽本类(相机控制类)的操作。生成一个带第三人称控制脚本的NPC的Prefab到固定的出生点,对相机的控制由该脚本来“接管”;
b)任何时候退出第三人称相机,将会销毁那个生成的Prefab,并将bool变量设为true,恢复类本身对相机的操控;
c)第三人称控制脚本则并不负责相机的操控;
d)(第三人称控制脚本)WASD移动NPC;
e) 鼠标移动 控制视角以NPC身上挂载的一个子对象为中心进行旋转。

4.风景相机(SpotCutViewMode):
本质上就是将cam的parent设置为一些放置好的空对象下,并将localPosition和localRotation设置为000。(其实没必要把相机设置为这些空对象的子对象,只需要赋值transfrom即可,但我在自己的项目中还要对这些点位对象有一些操作,所以将相机作为他们的子对象了。)
a)按键“,” 上一个机位
b)按键“.” 下一个机位
c)按键“/” 退出风景相机,回到正常相机

视频演示:
相机基本功能演示视频
优化效果后的演示视频

二、相机状态机

相机状态示意
注:本文中只涉及图中左下角四个状态的定义和切换。
我使用了最基本的enum搭配switch进行状态的定义及状态间的切换,代码不免有点冗余,但好处是很好理解。

三、设置流程

  1. 设置一个空场景,plan为ground,cube为Building,capsul为NPC
    在这里插入图片描述

    扫描二维码关注公众号,回复: 15064961 查看本文章
  2. 设置空对象如以下层级:在这里插入图片描述

  3. 其中camera、swivel、stick都是空对象,stick的子对象是MainCamera也就是真正的主相机。
    此设置主要控制正常视角下的相机。camera负责在xz轴平移,swivel负责旋转,stick负责缩放。

  4. 设置transfom:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  5. 制作第三人称视角下控制的NPC,tag必须为“NPC”,设置Animator(非必须),且navmeshAgent启用(必须),有碰撞体(必须);

在这里插入图片描述

创建c#脚本"ThirdPersonController.cs"

挂在Npc上。脚本内容如下:

using UnityEngine;

public class ThirdPersonController : MonoBehaviour
{
    
    
    private float currentVelocity;
    public float smoothTime = 0.2f;
    public float walkSpeed = 5f;
    //private Animator anim;
    private Transform cameraTransform;

    private void Start()
    {
    
    
        //anim = this.GetComponent<Animator>();
        cameraTransform = Camera.main.transform;
    }

    private void Update()
    {
    
    
        Vector2 input = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
        Vector2 inputDir = input.normalized;
        float targetRotation = Mathf.Atan2(inputDir.x, inputDir.y) * Mathf.Rad2Deg + cameraTransform.eulerAngles.y;
        if (inputDir != Vector2.zero)
        {
    
    
            transform.eulerAngles = Vector3.up * Mathf.SmoothDampAngle(transform.eulerAngles.y, targetRotation,
               ref currentVelocity, smoothTime);
            transform.Translate(transform.forward * walkSpeed * Time.deltaTime , Space.World);
            //anim.SetFloat("Speed",2f);
        }
        else
        {
    
    
            //anim.SetFloat("Speed",0f);
        }
    }
}
注:我这里针对胶囊,把Animator相关的操作都注释掉了。如果你游戏里的NPC使用动画,那么需要自己做一个Animator(主要目的是让NPC在不同状态时播放动画),并在这里进行你自己的修改。
  1. 烘焙navmesh,有了navmesh,在第三人称下可以不必依托实时碰撞来移动角色(前提是角色拥有navmeshAgent组件),从而可以关闭一些碰撞体提升性能。(agent不会到达没有蓝色覆盖的地方)
    在这里插入图片描述
  2. 创建脚本“Singleton.cs”

这是一个抽象单例工具类,可供相机控制类鼠标控制类甚至游戏控制类使用,这些类都可以继承它,而保证游戏中同时只存在一个自己。
这个是麦扣的教程学的…顺便安利一下,如果你跟我一样是个初学者,那么推荐在B站搜M_Studio。

using UnityEngine;

public class Singleton<T> : MonoBehaviour where T : Singleton<T>
{
    
    
    private static T instance;
    public static T Instance
    {
    
    
        get {
    
     return instance; }
    }

    //protected 只允许继承类访问的方法
    //virtual 可以被重写 当重写时,使用“protected override void Awake…”
    protected virtual void Awake()
    {
    
    
        if (instance != null)
            Destroy(gameObject);
        else
            instance = (T)this;
    }
    //返回当前的泛型单例模式是否已经生成了的bool 可以被外部调用
    public static bool IsInitialized
    {
    
    
        get {
    
     return instance != null; }
    }

    //对象销毁时会自动调用OnDestory,可以被重写。在此项目中,当场景里存在多个单例时,就可能要销毁单例
    protected virtual void OnDestory()
    {
    
    
        if (instance == this)
        {
    
    
            instance = null;
        }
    }
}
  1. 给NPC做一个空子对象,作为第三人称控制下相机旋转的中心点,一般放在人物后背的高度
    注:由于要生成这个npc对象,所以不能在界面事先指定,而是要在代码中通过GetChild(0)来获取,这时如果你的人物里面还有骨骼、mesh等等东西,那留意这个获取的下标是否正确。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    注:这里的0.399是我刻意手动太高了一点,我希望第三人称下镜头略微有点过肩视角的感觉。

  2. 在场景里创建空对象“cameraPosition”,此空对象定义切换到第三人称相机时相机的初始位置。
    可以在胶囊附近找一个合适的地方,选中此空对象,然后按Ctrl+Shift+F把此空对象摆放到一个合适的地方。
    如下图,我觉得在这个位置控制胶囊就差不多:
    在这里插入图片描述
    这时选中cameraPosition对象并Ctrl+Shift+F后:
    在这里插入图片描述
    此对象的位置(Gizimo是local,z轴表示前方)

  3. 使用Ctrl+Shift+F继续创建和摆放4个spot空对象在场景各处,如图
    spot1:
    spot1
    spot2:
    在这里插入图片描述
    spot3:
    在这里插入图片描述
    spot4:
    在这里插入图片描述
    这4个位置将作为预定的机位,可以在SpotCutViewMode中被切换

  4. 创建代码“CameraController.cs”

将如下代码复制:

using UnityEngine;
using System.Collections;
using System;

public class CameraController: Singleton<CameraController>
{
    
    
    //是否可以控制相机
    public static bool camCanBeControl = true;
    //相机模式
    public enum CamModeEnum {
    
     NormalView,FreeView,ThirdPersonView,SpotCutView};
    private CamModeEnum camModeEnum;
    
    //通过这三个东西在正常相机中控制cam,其中cam是camera本身
    private Transform swivel, stick, cam;
    //保存一些既定按键
    private KeyCode forwardKey = KeyCode.W;
    private KeyCode backKey = KeyCode.S;
    private KeyCode leftKey = KeyCode.A;
    private KeyCode rightKey = KeyCode.D;
    private KeyCode upKey = KeyCode.E;
    private KeyCode downKey = KeyCode.Q;
    private KeyCode normalViewKey = KeyCode.P;
    private KeyCode freeViewKey = KeyCode.O;
    private KeyCode thirdPersonViewKey = KeyCode.I;
    private KeyCode spotCamViewKey = KeyCode.Slash;
    private KeyCode nextcamSpotKey = KeyCode.Period;
    private KeyCode previouscamSpotKey = KeyCode.Comma;
    private readonly string mouseXStr = "Mouse X";
    private readonly string mouseYStr = "Mouse Y";

    [Header("正常相机参数")]
    public float stickMinZoom = -90f;//缩放范围
    public float stickMaxZoom = -50f;
    public float swivelMaxZoom = 90f; //旋转范围
    public float swivelMinZoom = 10f;
    public float moveSpeedMinZoom = 100;//移动速度范围
    public float moveSpeedMaxZoom = 80;
    public float pingYiSpeed = 0.1f;//中键拖动速度
    public float minimumY = -10F;//上下最大视角(Y视角) 
    public float maximumY = 90F;
    public float rotateSensitivityY = 5f; //旋转敏感度
    [Header("自由相机参数")]
    public float smoothMoveSpeed = 5;  //相机移动平滑值
    public float zoomSpeed = 50; //缩放速度
    public float moveSpeed = 5f; //移动速度
    public float rotateSpeed = 5f; //旋转速度
    public float dragSpeed = 20f; //拖动速度
    public float radios = 134f;//距离Vector3(0,0,0)的距离限制
    public float MinHeight = 10f; //相机最低点限制
    [Header("第三人称漫游相机参数")] 
    public GameObject NPCPrefab; //NPC预制件,将会生成
    public Transform bornPlace; //出生点
    public float mouseMoveSpeed = 2f; //视角移动速度
    public float camDistance = 6f; //相机距离
    public Transform camPosition; //相机初始位置
    [Header("风景相机参数")] 
    public Transform[] camspotArray = new Transform[4];

    #region 正常相机的一些参数
    private Vector3 cameraX= Vector3.zero; //相机的x轴方向向量
    private Vector3 cameraZ= Vector3.zero; //相机的z轴方向向量
    private Vector3 initPosition= Vector3.zero; //平移时用于存储平移的起点位置
    private Vector3 initScreenPos= Vector3.zero; //中键刚按下时鼠标的屏幕坐标(第三个值其实没什么用)
    private Vector3 curScreenPos= Vector3.zero; //当前鼠标的屏幕坐标(第三个值其实没什么用)
    private Vector3 targetOnScreenPosition= Vector3.zero; //目标的屏幕坐标,第三个值为z轴距离
    private Vector3 cameraTargetPosition= Vector3.zero;
    private float zoom;//范围为0-1,0代表拉到最远,1代表拉到最近
    private float rotationY;//旋转时用于储存此次旋转的起点位置
    #endregion

    #region 自由相机的一些参数
    private Vector3 moveOffset = Vector3.zero;
    private Vector3 moveTarget = Vector3.zero; //真正的camera的位置,只用于FreeView相机
    private bool invertDrag = false;
    #endregion

    #region 第三人称相机的一些参数
    //ThirdPerson一些参数
    private GameObject thirdPersonObjController = null; //第三人称相机操纵物,需要从thirdObject中获取值。后面要改进成从NPC表中获取数据来生成,如玩家要哪套suit和哪个Hair
    private float yaw;
    private float pitch;
    #endregion

    #region Spot一些参数
    private int SpotNum = 0;
    #endregion

    private void Awake()
    {
    
    
        base.Awake(); //在父类singleton里的基本Awake方法之上…
        DontDestroyOnLoad(this);
        swivel = transform.GetChild(0);
        stick = swivel.transform.GetChild(0);
        cam = stick.transform.GetChild(0);
        moveTarget = new Vector3(0f, 35.35534f, -35.35534f);
        camModeEnum = CamModeEnum.NormalView;
        cameraTargetPosition = transform.position; //当前挂载脚本的对象的position,用于正常相机中的平移操作
    }

    void Update()
    {
    
    
        camCanBeControl = true;
        if (camCanBeControl)
        {
    
    
            //只负责状态切换; 但由于只在这里接收KeyDown,所以有方法只希望在相机模式切换时只执行一次的可以放在这里。
            switch (camModeEnum)
            {
    
    
                case CamModeEnum.NormalView:
                    if (Input.GetKeyDown(normalViewKey)) ReSetCameraAll();
                    else if (Input.GetKeyDown(freeViewKey))
                    {
    
    
                        ReSetCameraAll();
                        camModeEnum = CamModeEnum.FreeView;
                    }
                    else if (Input.GetKeyDown(thirdPersonViewKey))
                    {
    
    
                        ThirdPersonGameObjSpawner();
                        camModeEnum = CamModeEnum.ThirdPersonView;
                    }
                    else if (Input.GetKeyDown(spotCamViewKey))
                    {
    
    
                        ReSetCameraAll();
                        camModeEnum = CamModeEnum.SpotCutView;
                    }
                    break;
                case CamModeEnum.FreeView:
                    if (Input.GetKeyDown(normalViewKey))
                    {
    
    
                        ReSetCameraAll();
                        camModeEnum = CamModeEnum.NormalView;
                    }
                    else if (Input.GetKeyDown(freeViewKey)) ReSetCameraAll();
                    else if (Input.GetKeyDown(thirdPersonViewKey))
                    {
    
    
                        ThirdPersonGameObjSpawner();
                        camModeEnum = CamModeEnum.ThirdPersonView;
                    }
                    else if (Input.GetKeyDown(spotCamViewKey))
                    {
    
    
                        ReSetCameraAll();
                        camModeEnum = CamModeEnum.SpotCutView;
                    }
                    break;
                case CamModeEnum.ThirdPersonView:
                    if (Input.GetKeyDown(normalViewKey) ||Input.GetKeyDown(freeViewKey) ||
                        Input.GetKeyDown(thirdPersonViewKey) || Input.GetKeyDown(spotCamViewKey))
                    {
    
    
                        ThirdPersonObjectDestroy();
                        camModeEnum = CamModeEnum.NormalView;
                    }
                    break;
                case CamModeEnum.SpotCutView:
                    if (Input.GetKeyDown(normalViewKey) || Input.GetKeyDown(spotCamViewKey))
                    {
    
    
                        cam.SetParent(stick);
                        ReSetCameraAll();
                        camModeEnum = CamModeEnum.NormalView;
                    }
                    else if (Input.GetKeyDown(freeViewKey))
                    {
    
    
                        cam.SetParent(stick);
                        ReSetCameraAll();
                        camModeEnum = CamModeEnum.FreeView;
                    }
                    else if (Input.GetKeyDown(thirdPersonViewKey))
                    {
    
    
                        cam.SetParent(stick);
                        ThirdPersonGameObjSpawner();
                        camModeEnum = CamModeEnum.ThirdPersonView;
                    }
                    break;
            }
        }
    }

    private void LateUpdate()
    {
    
    
        //负责一对一调用方法.注意这里的方法都是会重复执行的。
        if (camCanBeControl)
        {
    
    
            switch (camModeEnum)
            {
    
    
                case CamModeEnum.NormalView:
                    NormalViewController();
                    break;
                case CamModeEnum.FreeView:
                    FreeViewCamController();
                    break;
                case CamModeEnum.ThirdPersonView:
                    ThirdPersonViewController();
                    break;
                case CamModeEnum.SpotCutView:
                    SpotCutViewController();
                    break;
            }
        }
    }

    void OnGUI()
    {
    
    
        switch (camModeEnum)
        {
    
    
            case CamModeEnum.NormalView:
            {
    
    
                GUI.Label(new Rect(10.0f, 10.0f, 600.0f, 400.0f),
                    "当前是:正常相机\n" +
                    "WASD 平移相机\n " +
                    "滚轮 缩放\n" +
                    "鼠标右键 旋转\n" +
                    "P 复位本相机\n" +
                    "O 自由相机\n" +
                    "I 第三人称相机\n " +
                    "/ 风景相机");
                break;
            }
            case CamModeEnum.FreeView:
            {
    
    
                GUI.Label(new Rect(10.0f, 10.0f, 600.0f, 400.0f),
                    "当前是:自由相机\n" +
                    "右键 + WASD 移动相机\n " +
                    "滚轮 缩放\n" +
                    "鼠标右键 控制方向\n" +
                    "P 正常相机\n" +
                    "O 复位本相机\n" +
                    "I 风景相机");
                break;
            }
            case CamModeEnum.ThirdPersonView:
            {
    
    
                GUI.Label(new Rect(10.0f, 10.0f, 600.0f, 400.0f),
                    "当前是:第三人称相机\n" +
                    "WASD 移动角色\n" +
                    "鼠标 移动相机\n" +
                    "I 退出第三人称相机\n");
                break;
            }
            case CamModeEnum.SpotCutView:
            {
    
    
                GUI.Label(new Rect(10.0f, 10.0f, 600.0f, 400.0f),
                    "当前是:风景相机模式\n" +
                    ", 上一个机位 \n" +
                    ". 下一个机位\n" +
                    "P 正常相机\n" +
                    "O 自由相机\n" +
                    "I 第三人称相机\n" +
                    "/ 退出风景相机");
                break;
            }
        }
    }

    private void SpotCutViewController()
    {
    
    
        int arrayLength = camspotArray.Length -1;
        //直接切换摄像机到Spot01位置 / Switch camra  position to the Spot[i]
        //使用< > 在数组中 切换机位 / Use KeyBoard , . Switch Spot 
        if (Input.GetKeyDown(nextcamSpotKey))
        {
    
    
            SpotNum++;
            if (SpotNum >arrayLength ) SpotNum = 0;
            cam.SetParent(camspotArray[SpotNum]);
            cam.localPosition = Vector3.zero;
            cam.localRotation = Quaternion.Euler(Vector3.zero);
        }
        else if (Input.GetKeyDown(previouscamSpotKey))
        {
    
    
            SpotNum--;
            if (SpotNum < 0) SpotNum = arrayLength;
            cam.SetParent(camspotArray[SpotNum]);
            cam.localPosition = Vector3.zero;
            cam.localRotation = Quaternion.Euler(Vector3.zero);
        }
    }
    
    public static IEnumerator WaitForSeconds(float duration, Action action = null)
    {
    
    
        yield return new WaitForSeconds(duration);
        action?.Invoke();
    }
    
    /// <summary>
    /// 生成第三人称视角操控对象
    /// </summary>
    private void ThirdPersonGameObjSpawner()
    {
    
    
        camCanBeControl = false;
        thirdPersonObjController = Instantiate(NPCPrefab, bornPlace.position, bornPlace.rotation);
        if (thirdPersonObjController)
        {
    
    
            cam.SetPositionAndRotation(camPosition.position, camPosition.rotation);
        }
    }

    private void ThirdPersonViewController()
    {
    
    
        if (thirdPersonObjController != null)
        {
    
    
            yaw += Input.GetAxis(mouseXStr) * mouseMoveSpeed;
            pitch -= Input.GetAxis(mouseYStr) * mouseMoveSpeed;
            cam.transform.eulerAngles = new Vector3(pitch, yaw, 0);
            //这里的子对象15是follow点,位于人物腰部.  3的意思是摄像机距离
            cam.transform.position = thirdPersonObjController.transform.GetChild(0).position - cam.transform.forward * camDistance;
        }
    }

    private void ThirdPersonObjectDestroy()
    {
    
    
        thirdPersonObjController.SetActive(false);
        Destroy(thirdPersonObjController.gameObject);
        thirdPersonObjController = null;
        ReSetCameraAll();
        camCanBeControl = true;
    }
    
    private void FreeViewCamController()
    {
    
    
        Rotate();
        Move();
        Zoom();
        Drag();
    }
    
    /// <summary>
    /// 正常视角下的视角操作,包含平移、缩放
    /// </summary>
    private void NormalViewController()
    {
    
    
        float zoomDelta = Input.GetAxis("Mouse ScrollWheel");
        float xDelta = Input.GetAxis("Horizontal");
        float zDelta = Input.GetAxis("Vertical");
        
        //滚轮缩放
        if ( zoomDelta != 0)
        {
    
    
            AdjustZoom(zoomDelta);
        }
        //WASD平移
        if (xDelta != 0f || zDelta != 0f)
        {
    
    
            AdjustPosition(xDelta, zDelta);
        }
        //旋转
        if (Input.GetMouseButton(1) )
        {
    
    
            rotationY += Input.GetAxis("Mouse Y") * rotateSensitivityY;
            rotationY = Mathf.Clamp(rotationY, minimumY, maximumY);
            stick.localEulerAngles = new Vector3(-rotationY, 0f, 0f);
        }
        //中键平移
        if (Input.GetMouseButtonDown(2))
        {
    
    
            cameraX = transform.right;
            cameraZ = transform.forward;

            initScreenPos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, targetOnScreenPosition.z);
			
            //targetOnScreenPosition.z为目标物体到相机xmidbuttonDownPositiony平面的法线距离
            targetOnScreenPosition = cam.GetComponent<Camera>().WorldToScreenPoint(cameraTargetPosition);
            initPosition = cameraTargetPosition;
        }
        if (Input.GetMouseButton(2))
        {
    
    
            curScreenPos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, targetOnScreenPosition.z);
            Vector3 vec = initPosition - pingYiSpeed * ((curScreenPos.x - initScreenPos.x) * cameraX + (curScreenPos.y - initScreenPos.y) * cameraZ);
            //vec.x *= -1f;
            vec.y = 0f;
            //重新计算位置
            transform.localPosition = clamPosition(vec);
        }
        if (Input.GetMouseButtonUp(2))
        {
    
    
            //平移结束把cameraTargetPosition的位置更新一下,不然会影响缩放与旋转功能
            cameraTargetPosition = transform.localPosition;
        }
    }
    
    /// <summary>
    /// 限制xz轴移动范围
    /// </summary>
    /// <param name="xDelta"></param>
    /// <param name="zDelta"></param>
    private void AdjustPosition(float xDelta, float zDelta)
    {
    
    
        //将传递过来的参数转换为一个XZ单位向量
        Vector3 dir = new Vector3(xDelta, 0f, zDelta).normalized;
        //以取最大值的函数和取绝对值的方法联用,来取得两个变化量汇中的最大值作为阻尼(移动系数),以避免出现移动异常的情况
        float damping = Mathf.Max(Mathf.Abs(xDelta), Mathf.Abs(zDelta));
        //这里用了一个zoom来避免视角在高点时平移速度会比低处慢的问题
        float distance = Mathf.Lerp(moveSpeedMinZoom,moveSpeedMaxZoom, zoom)* damping * Time.deltaTime;
        
        Vector3 position = transform.localPosition;
        position += dir * distance;
        transform.localPosition = clamPosition(position);
        cameraTargetPosition = transform.localPosition;
    }
    
    /// <summary>
    /// 限制平移范围
    /// </summary>
    /// <param name="position">待调整坐标</param>
    /// <returns></returns>
    private Vector3 clamPosition(Vector3 position)
    {
    
    
        position.x = Mathf.Clamp(position.x , -100 ,100);
        position.z = Mathf.Clamp(position.z , -100 ,100);
        
        return position;
    }

    /// <summary>
    /// 限制缩放范围
    /// </summary>
    /// <param name="delta"></param>
    private void AdjustZoom(float delta)
    {
    
    
        //拉近或拉远stick,产生缩放的效果
        zoom = Mathf.Clamp01(zoom + delta);
        float distance = Mathf.Lerp(stickMaxZoom, stickMinZoom, zoom);
        stick.localPosition = new Vector3(0f, 0f, distance);
        
        //让swivel随着缩放产生旋转 
        float angle = Mathf.Lerp(swivelMinZoom, swivelMaxZoom, zoom);
        swivel.localRotation = Quaternion.Euler(angle, 0f, 0f);
    }
    
    /// <summary>
    /// 复位相机位置和旋转
    /// </summary>
    private void ReSetCameraAll()
    {
    
    
        transform.localPosition = Vector3.zero;
        transform.localRotation = Quaternion.Euler(0f,0f,0f);
        
        swivel.localRotation = Quaternion.Euler(45f,0f,0f);
        swivel.localPosition = Vector3.zero;
        
        stick.localPosition = new Vector3(0f, 0f, -50);
        stick.localRotation = Quaternion.Euler(0f,0f,0f);
        
        cam.localPosition = Vector3.zero;
        cam.localRotation = Quaternion.Euler(0f,0f,0f);
        cam.localScale = Vector3.one;
        if (camModeEnum == CamModeEnum.NormalView)
        {
    
    
            //在正常相机里 平移的两种方法中 使用的cam上次结束平移的位置 这里也需要更新
            cameraTargetPosition = transform.localPosition;
        }

        if (camModeEnum == CamModeEnum.FreeView)
        {
    
    
            moveTarget = cam.transform.position;
        }
    }

    #region 自由视角相机方法
    private void Move()
    {
    
    
        moveOffset = Vector3.zero;
        if (Input.GetMouseButton(1))
        {
    
    
            Vector3 direction = Vector3.zero;
            if (Input.GetKey(forwardKey))
                direction = cam.transform.forward;
            if (Input.GetKey(backKey))
                direction = -cam.transform.forward;
            if (Input.GetKey(leftKey))
                direction = -cam.transform.right;
            if (Input.GetKey(rightKey))
                direction = cam.transform.right;
            if (Input.GetKey(upKey))
                direction = cam.transform.up;
            if (Input.GetKey(downKey))
                direction = -cam.transform.up;
            moveOffset += direction * moveSpeed;
        } 
        if (moveOffset != Vector3.zero)
            moveTarget = cam.transform.position + moveOffset;
        Vector3 destination = Vector3.Lerp(cam.transform.position, moveTarget, smoothMoveSpeed * Time.deltaTime);
        float length = Vector3.Distance(Vector3.zero, destination);
        //距离中心点的距离小于指定半径 且 目标点的Y值高度大于15
        if (length < radios && destination.y >MinHeight)
        {
    
    
            cam.transform.position = destination;
        }
        else if (length < radios && destination.y <= MinHeight)
        {
    
    
            destination.y = MinHeight;
            cam.transform.position = destination;
        }  
    }
 
    private void Rotate()
    {
    
    
        if (Input.GetMouseButton(1))
        {
    
    
            float mouseX = Input.GetAxis("Mouse X");
            float mouseY = Input.GetAxis("Mouse Y");
            cam.transform.RotateAround(cam.transform.position, Vector3.up, mouseX * rotateSpeed);
            cam.transform.RotateAround(cam.transform.position, -cam.transform.right, mouseY * rotateSpeed);
        }
    }
 
    private void Zoom()
    {
    
    
        float moveAmount = Input.GetAxis("Mouse ScrollWheel");
        if (moveAmount == 0) return;
        moveOffset = Vector3.zero;
        if (moveAmount != 0)
        {
    
    
            moveOffset += moveAmount * zoomSpeed * cam.transform.forward;
        }
        if (moveOffset != Vector3.zero)
            moveTarget = cam.transform.position + moveOffset;
        Vector3 destination = Vector3.Lerp(cam.transform.position, moveTarget, smoothMoveSpeed * Time.deltaTime);
        float length = Vector3.Distance(Vector3.zero, destination);
        //距离中心点的距离小于指定半径 且 目标点的Y值高度大于15
        if (length < radios && destination.y >MinHeight)
        {
    
    
            cam.transform.position = destination;
        }
        else if (length < radios && destination.y <= MinHeight)
        {
    
    
            destination.y = MinHeight;
            cam.transform.position = destination;
        }
        
    }
 
    private void Drag()
    {
    
    
        moveOffset = Vector3.zero;
        if (Input.GetMouseButton(2))
        {
    
    
            float invert = invertDrag ? 1 : -1;
            float mouseX = Input.GetAxis("Mouse X") * invert;
            float mouseY = Input.GetAxis("Mouse Y") * invert;
            moveOffset += cam.transform.right * mouseX * dragSpeed;
            moveOffset += cam.transform.up * mouseY * dragSpeed;
            if (moveOffset != Vector3.zero)
                moveTarget = cam.transform.position + moveOffset;
        }
        Vector3 destination = Vector3.Lerp(cam.transform.position, moveTarget, smoothMoveSpeed * Time.deltaTime);
        float length = Vector3.Distance(Vector3.zero, destination);
        //距离中心点的距离小于指定半径 且 目标点的Y值高度大于15
        if (length < radios && destination.y >MinHeight)
        {
    
    
            cam.transform.position = destination;
        }
        else if (length < radios && destination.y <= MinHeight)
        {
    
    
            destination.y = MinHeight;
            cam.transform.position = destination;
        }
        
    }
    
    #endregion

}
  1. 检查游戏场景,将“CameraController.cs”脚本赋予camera组件。(不是main camera)
    并设置脚本的对象如下:(大多数值已设置了初始值)
    在这里插入图片描述
    14.这时可以运行试试看了。留意左上角GUI的提示。
    如果没有GUI提示,请检查下是不是窗口缩放太大了。

一些建议

  1. 我在自己的项目里对相机增添了一些效果,如黑屏淡入淡出,轻轻晃动等,使用了FEEL插件,此插件可以自定义包装一些效果在脚本中调用,推荐大家了解看看。
  2. 部分代码参考了一些别的帖子,自己在改时为图方便,进行了一些取巧处理,可能有的地方有很大优化空间。如一处相机移动的代码(应该是在正常相机操作的方法里),我获取了全部的位移后,将Y直接等于0,再赋给相机使相机不能上下移动,这个地方就可以改进得更优雅一点。

猜你喜欢

转载自blog.csdn.net/THRoot/article/details/127467875