前言
这段时间刚好看悠游视频,学习MMORPG的游戏制作,开这个篇章的主要是为了记录下自己的学习历程,以及自己的一些理解和思考,主要会把学习到的一些比较重要的东西记录下。
使用的环境
- Unity版本 2020.2.3f1c1
- 使用到的插件 FingerGestures(手势识别)
- 课程下载地址 http://www.u3dol.com/index_CourseOne.html
摄像机控制方案
通常情况下,我们控制摄像机都是直接去操作MainCamera位置旋转来达到摄像机跟随目标,旋转,拉近视角,但是这样跟随,旋转和拉近视角可能会相互影响,导致操作会变得很复杂。
刚好在视频中看到这种3d角色的摄像机控制方案,顿时感觉豁然开朗。
总体思想:既然多种操作之间会相互影响,那么自然而然就想到通过将跟随目标,旋转视角,拉近视角这些控制分离开来,每个属性控制单独的操作,使得相互之间没有影响。从而达到更方便简单的去实现摄像机的控制。
摄像机结构
摄像机的大致结构如下图所示,主要分5层,每层职责清晰
- CameraFolowAndRotate 这层主要是负责
- 摄像机跟随目标
- 摄像机左右旋转
- CameraUpAndDown 这层主要负责
- 摄像机上下旋转
- CameraZoomContainer 这层主要负责
- 初始化摄像机位置
- 初始化摄像方向
- CameraContainer 这层主要负责
- 摄像机拉远拉近
- Main Camera 这个就是场景摄像机
注:temp 主要是用来方便在scene场景中查看
摄像机控制
1. 摄像机跟随目标
通过修改 CameraFolowAndRotate.transform.position 坐标值,来实现摄像机跟随目标。
因为会设置好摄像机的初始角度,初始位置,所以直接设置 CameraFolowAndRotate.transform.position = Target.transform.position即可实现跟随目标。
2. 摄像机左右旋转
通过修改 CameraFolowAndRotate.transform.rotation.y 值,来实现摄像机左右旋转。
3. 摄像机上下旋转
通过修改 CameraUpAndDown.transform.rotation.z 值,来实现摄像机上下旋转。
4. 摄像机拉远拉近
通过修改 CameraContainer.transform.position.z 值,来实现摄像机拉近拉远。
具体代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 摄像机控制
/// </summary>
public class CameraCtrl : MonoBehaviour
{
/// <summary>
/// 单例
/// </summary>
public static CameraCtrl Instance;
/// <summary>
/// 摄像机 上下
/// </summary>
[SerializeField]
private Transform m_CameraUpAndDown;
/// <summary>
/// 摄像机 放大缩小
/// </summary>
[SerializeField]
private Transform m_CameraZoomContainer;
/// <summary>
/// 摄像机 容器
/// </summary>
[SerializeField]
private Transform m_CameraContainer;
private void Awake()
{
Instance = this;
}
/// <summary>
/// 初始化摄像机
/// </summary>
public void Init()
{
m_CameraUpAndDown.transform.localEulerAngles = new Vector3(0, 0, Mathf.Clamp(m_CameraUpAndDown.transform.localEulerAngles.z, 30, 80));
}
/// <summary>
/// 设置摄像机左右旋转
/// </summary>
/// <param name="type">type = 0 表示向左 type =1 表示向右</param>
public void SetCameraRotate(int type)
{
transform.Rotate(0, 40f * Time.deltaTime * (type == 0 ? -1:1), 0);
}
/// <summary>
/// 设置摄像机上下移动
/// </summary>
/// <param name="type">type = 0 表示向上 type =1 表示向下</param>
public void SetCameraUp(int type)
{
m_CameraUpAndDown.Rotate(0, 0, 30f * Time.deltaTime * (type == 0 ? 1 : -1));
//-15,40
m_CameraUpAndDown.transform.localEulerAngles = new Vector3(0, 0, Mathf.Clamp(m_CameraUpAndDown.transform.localEulerAngles.z, 30, 80));
}
/// <summary>
/// 设置摄像机前后 移动
/// </summary>
/// <param name="type">type = 0 表示向前 type =1 表示向后</param>
public void SetCameraZoom(int type)
{
m_CameraContainer.Translate(Vector3.forward * 20 * (type == 0 ? 1 : -1) * Time.deltaTime);
m_CameraContainer.transform.localPosition = new Vector3(0, 0, Mathf.Clamp(m_CameraContainer.transform.localPosition.z, -5, 5));
}
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, 15);
Gizmos.color = Color.blue;
Gizmos.DrawWireSphere(transform.position, 14);
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(transform.position, 12);
}
}
摄像机方案使用
摄像机设置
1. CameraFolowAndRotate 设置
- 坐标归零
- 挂载 CameraCtrl 控制脚本,并设置好属性值
如下图所示
2. CameraUpAndDown 设置
- 设置初始角度(可根据需要设置)
如下图所示
3. CameraZoomContainer 设置
- 设置初始位置
- 设置初始摄像机方向(只配置Rotation.y)
如下图所示
4. CameraContainer 设置
- 坐标归零
如下图所示
5. Main Camera 设置
- 坐标归零 (方便后期做一些震屏等摄像机特效)
如下图所示
摄像机控制
跟随控制
前面也提到了,主要是跟随直接设置坐标就好了,这边直接在目标Update中设置摄像机与目标坐标相等即可
CameraCtrl.Instance.transform.position = transform.position;
旋转控制
这边根据收拾手势拖动方向来调用对应摄像机上下旋转,左右旋转控制方法
具体代码如下:
/// <summary>
/// 手指拖动处理
/// </summary>
/// <param name="dragDir"></param>
private void OnFigerDrag(FingerEvent.DragDir dragDir)
{
switch (dragDir)
{
case FingerEvent.DragDir.Up:
CameraCtrl.Instance.SetCameraUp(1);
break;
case FingerEvent.DragDir.Down:
CameraCtrl.Instance.SetCameraUp(0);
break;
case FingerEvent.DragDir.Left:
CameraCtrl.Instance.SetCameraRotate(0);
break;
case FingerEvent.DragDir.Right:
CameraCtrl.Instance.SetCameraRotate(1);
break;
default:
break;
}
}
近远控制
这边根据收拾手势来调用对应摄像机远近控制方法,具体电脑使用鼠标滑轮控制,移动设备使用两手指缩小放大来控制。
具体代码如下
/// <summary>
/// 缩放摄像机
/// 缩放摄像机
/// </summary>
/// <param name="zoomType"></param>
private void OnZoom(FingerEvent.ZoomType zoomType)
{
switch (zoomType)
{
case FingerEvent.ZoomType.In:
CameraCtrl.Instance.SetCameraZoom(0);
break;
case FingerEvent.ZoomType.Out:
CameraCtrl.Instance.SetCameraZoom(1);
break;
default:
break;
}
}
手势触发
这边使用了插件FingerGestures 手势插件,主要是判断鼠标或者触控来识别对应控制 这边就不多说了 有兴趣的可以自己去看看源代码。
下面是FingerEvent代码
using UnityEngine;
using System.Collections;
public class FingerEvent : MonoBehaviour {
public static FingerEvent Instance;
/// <summary>
/// 手指拖动方向
/// </summary>
public enum DragDir
{
Up,
Down,
Left,
Right
}
/// <summary>
/// 放大类型
/// </summary>
public enum ZoomType
{
/// <summary>
/// 拉近
/// </summary>
In,
/// <summary>
/// 拉远
/// </summary>
Out
}
/// <summary>
/// 前一拖动坐标点
/// </summary>
private Vector2 oldDragPos;
/// <summary>
/// 点击类型
/// </summary>
private int porClickType =-1;
/// <summary>
/// 前一双指距离
/// </summary>
private float proTouchDis;
/// <summary>
/// 手指拖动委托
/// </summary>
public System.Action<DragDir> OnFigerDrag;
/// <summary>
/// 点击委托
/// </summary>
public System.Action OnPlayerClick;
/// <summary>
/// 拉近拉远委托
/// </summary>
public System.Action<ZoomType> OnZoom;
private void Awake()
{
Instance = this;
}
void OnEnable()
{
//启动时调用,这里开始注册手势操作的事件。
//按下事件: OnFingerDown就是按下事件监听的方法,这个名子可以由你来自定义。方法只能在本类中监听。下面所有的事件都一样!!!
FingerGestures.OnFingerDown += OnFingerDown;
//抬起事件
FingerGestures.OnFingerUp += OnFingerUp;
//开始拖动事件
FingerGestures.OnFingerDragBegin += OnFingerDragBegin;
//拖动中事件...
FingerGestures.OnFingerDragMove += OnFingerDragMove;
//拖动结束事件
FingerGestures.OnFingerDragEnd += OnFingerDragEnd;
//长按事件
FingerGestures.OnFingerLongPress += OnFingerLongPress;
}
private void Update()
{
#if UNITY_EDITOR || UNITY_STANDALONE_WIN
if (Input.GetAxis("Mouse ScrollWheel") < 0)
{
if (OnZoom != null)
{
OnZoom(ZoomType.In);
}
}
else if (Input.GetAxis("Mouse ScrollWheel") > 0)
{
if (OnZoom != null)
{
OnZoom(ZoomType.Out);
}
}
#elif UNITY_ANDROID || UNITY_IPHONE
if (Input.touchCount > 1)
{
if(Input.GetTouch(0).phase == TouchPhase.Moved || Input.GetTouch(1).phase == TouchPhase.Moved)
{
float touchDis = Vector2.Distance(Input.GetTouch(0).position, Input.GetTouch(0).position);
if (proTouchDis > touchDis)
{
if (OnZoom != null)
{
OnZoom(ZoomType.In);
}
}
else
{
if (OnZoom != null)
{
OnZoom(ZoomType.Out);
}
}
}
}
#endif
}
void OnDisable()
{
//关闭时调用,这里销毁手势操作的事件
//和上面一样
FingerGestures.OnFingerDown -= OnFingerDown;
FingerGestures.OnFingerUp -= OnFingerUp;
FingerGestures.OnFingerDragBegin -= OnFingerDragBegin;
FingerGestures.OnFingerDragMove -= OnFingerDragMove;
FingerGestures.OnFingerDragEnd -= OnFingerDragEnd;
FingerGestures.OnFingerLongPress -= OnFingerLongPress;
}
//开始滑动
void OnFingerDragBegin( int fingerIndex, Vector2 fingerPos, Vector2 startPos )
{
porClickType = 2;
oldDragPos = fingerPos;
}
//滑动中
void OnFingerDragMove( int fingerIndex, Vector2 fingerPos, Vector2 delta )
{
porClickType = 3;
Vector2 dir = fingerPos - oldDragPos;
DragDir dragDir;
if (dir.x > dir.y) {
if(dir.x > -dir.y) {
//往右
dragDir = DragDir.Right;
}
else {
//往下
dragDir = DragDir.Down;
}
}
else{
if (dir.x > -dir.y) {
//往左
dragDir = DragDir.Up;
}
else {
//往上
dragDir = DragDir.Left;
}
}
if (OnFigerDrag != null)
{
OnFigerDrag(dragDir);
}
}
}
结语
单独一篇讲这个,主要是第一次看到这种控制方案觉得挺新奇的,2d游戏,大部分3d游戏也不会用到这种方案,只是这种方案,将多种控制相互关联的属性拆开来,每种单独控制,复杂问题简单化倒是以后处理问题的一种很好的思路。
代码工程下载
具体的脚本这边就不单独给出来了,主要就是CameraCtrl脚本,FingerGestures这个插件只是更方便的提供多平台同一套控制方法。
Github工程地址 https://github.com/Wsxiaojian/MMORPG