目录
一、简介
此文分享个人在项目中实现几种相机控制、并让这几种相机状态共存(可手动切换)的实现。
抛砖引玉,若发现问题或有改进方案还请不吝赐教。
如果你希望你的项目有更多的相机行为,如玩家可以切换几种控制模式,来使用不同的相机,那么这篇文章也许对你有所帮助。
我提供以下几种相机模式供你参考:
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进行状态的定义及状态间的切换,代码不免有点冗余,但好处是很好理解。
三、设置流程
-
设置一个空场景,plan为ground,cube为Building,capsul为NPC
-
设置空对象如以下层级:
-
其中camera、swivel、stick都是空对象,stick的子对象是MainCamera也就是真正的主相机。
此设置主要控制正常视角下的相机。camera负责在xz轴平移,swivel负责旋转,stick负责缩放。 -
设置transfom:
-
制作第三人称视角下控制的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在不同状态时播放动画),并在这里进行你自己的修改。
- 烘焙navmesh,有了navmesh,在第三人称下可以不必依托实时碰撞来移动角色(前提是角色拥有navmeshAgent组件),从而可以关闭一些碰撞体提升性能。(agent不会到达没有蓝色覆盖的地方)
-
创建脚本“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;
}
}
}
-
给NPC做一个空子对象,作为第三人称控制下相机旋转的中心点,一般放在人物后背的高度
注:由于要生成这个npc对象,所以不能在界面事先指定,而是要在代码中通过GetChild(0)来获取,这时如果你的人物里面还有骨骼、mesh等等东西,那留意这个获取的下标是否正确。
注:这里的0.399是我刻意手动太高了一点,我希望第三人称下镜头略微有点过肩视角的感觉。 -
在场景里创建空对象“cameraPosition”,此空对象定义切换到第三人称相机时相机的初始位置。
可以在胶囊附近找一个合适的地方,选中此空对象,然后按Ctrl+Shift+F把此空对象摆放到一个合适的地方。
如下图,我觉得在这个位置控制胶囊就差不多:
这时选中cameraPosition对象并Ctrl+Shift+F后:
此对象的位置(Gizimo是local,z轴表示前方) -
使用Ctrl+Shift+F继续创建和摆放4个spot空对象在场景各处,如图
spot1:
spot2:
spot3:
spot4:
这4个位置将作为预定的机位,可以在SpotCutViewMode中被切换 -
创建代码“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
}
- 检查游戏场景,将“CameraController.cs”脚本赋予camera组件。(不是main camera)
并设置脚本的对象如下:(大多数值已设置了初始值)
14.这时可以运行试试看了。留意左上角GUI的提示。
如果没有GUI提示,请检查下是不是窗口缩放太大了。
一些建议
- 我在自己的项目里对相机增添了一些效果,如黑屏淡入淡出,轻轻晃动等,使用了FEEL插件,此插件可以自定义包装一些效果在脚本中调用,推荐大家了解看看。
- 部分代码参考了一些别的帖子,自己在改时为图方便,进行了一些取巧处理,可能有的地方有很大优化空间。如一处相机移动的代码(应该是在正常相机操作的方法里),我获取了全部的位移后,将Y直接等于0,再赋给相机使相机不能上下移动,这个地方就可以改进得更优雅一点。