环绕相机控制脚本
该脚本需要挂载在环绕相机上,而环绕相机不绑定在角色身上,而是作为一个单独的存在
此外,由于一般来说,角色的原点都在脚底,所以需要特别在角色的中心位置放置一个空对象作为视野中心,并在面板指定
相机被遮挡的判断与处理
具体分析见 Unity 相机被遮挡的判断与处理
相机的惯性旋转
具体分析见 Unity 自由视角的惯性旋转
效果
操作方式
按下鼠标左键并拖拽,可以让相机在上下和左右方向以角色为轴心旋转
鼠标滚轮可以调节视角的大小
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SurroundCamera : MonoBehaviour
{
//视野中心
public Transform focus;
//相机相对角色的位置
Vector3 RelativePosition;
void Start()
{
RelativePosition = transform.position - focus.position; //以人为原点A,相机为B,向量AB=B-A,B的坐标等于向量AB。
unit = RelativePosition.magnitude;
}
void Update()
{
Follow(); //相机跟随
if (cameraRotateBy == CameraRotateBy.MouseVelocity) //调节视角
DragToRotateView_Velocity();
else
DragToRotateView_Distance();
OcclusionJudge(); //视野遮挡判断
if (scaleViewBy == ScaleViewBy.Distance) //调节视野
ScrollToScaleDistance();
else if (scaleViewBy == ScaleViewBy.FieldOfView)
ScrollToScaleView();
else if (scaleViewBy == ScaleViewBy.Level)
ScrollToAdjustView();
}
void Follow()
{
transform.position = focus.position + RelativePosition; //每一帧都跟随移动
}
//相机旋转方案
public CameraRotateBy cameraRotateBy = CameraRotateBy.MouseVelocity;
public enum CameraRotateBy
{
MouseVelocity,
Distance,
}
//最小水平夹角
public float MinimumDegree = 0;
//最大水平夹角
public float MaximumDegree = 60;
//两点连线与水平方向的夹角
float currentAngleY;
float mouseVelocityX;
float mouseVelocityY;
Vector3? point1;
//旋转每度,在一帧中需要的速度
int DragVelocityPerAngle = 170;
//脱手瞬间鼠标速度
float lastMouseVelocityX;
float lastMouseVelocityY;
void DragToRotateView_Velocity()
{
if (Input.GetMouseButton(0)) //按下鼠标左键的每一帧都执行
{
var point2 = Input.mousePosition;
if (point1 != null)
{
mouseVelocityX = -(point1.Value.x - point2.x) / Time.deltaTime;
mouseVelocityY = -(point1.Value.y - point2.y) / Time.deltaTime;
}
point1 = point2;
float anglex = mouseVelocityX / DragVelocityPerAngle; //将鼠标在屏幕上拖拽的速度转化为角度
float angley = mouseVelocityY / DragVelocityPerAngle;
currentAngleY = 90 - Vector3.Angle(-RelativePosition, Vector3.down); //计算两点连线与水平方向的夹角
if (currentAngleY - angley > MaximumDegree || currentAngleY - angley < MinimumDegree)
angley = 0;
transform.RotateAround(focus.position, Vector3.up, anglex);
transform.RotateAround(focus.position, -transform.right, angley);
transform.LookAt(focus); //如果没有这一句,摄像头转着转着就会歪
RelativePosition = transform.position - focus.position; //更新相对位置
}
if(Input.GetMouseButtonUp(0)) //脱手瞬间
{
point1 = null;
inertialRotation = true;
lastMouseVelocityX = mouseVelocityX;
lastMouseVelocityY = mouseVelocityY;
if (lastMouseVelocityX > maxlastMouseVelocityX) lastMouseVelocityX = maxlastMouseVelocityX;
else if (lastMouseVelocityX < -maxlastMouseVelocityX) lastMouseVelocityX = -maxlastMouseVelocityX;
if (lastMouseVelocityX > 0) isCounterClockwise = true;
else if (lastMouseVelocityX < 0) isCounterClockwise = false;
//print(lastMouseVelocityX);
}
if(inertialRotation==true)
StartCoroutine("InertialRotation"); //通过协程来实现视角的惯性旋转,调用协程只有写在Update里并且在每一帧都被调用时才会继续执行
}
bool inertialRotation = false; //是否需要视角的惯性旋转
float maxlastMouseVelocityX = 3000;
bool isCounterClockwise; //旋转方向
IEnumerator InertialRotation() //在旋转末尾补上一个逐渐减缓的惯性旋转
{
yield return null;
float anglex = lastMouseVelocityX / DragVelocityPerAngle; //将鼠标在屏幕上拖拽的速度转化为角度
float angley = lastMouseVelocityY / DragVelocityPerAngle;
currentAngleY = 90 - Vector3.Angle(-RelativePosition, Vector3.down); //计算两点连线与水平方向的夹角
if (currentAngleY - angley > MaximumDegree || currentAngleY - angley < MinimumDegree + 10)
angley = 0;
lastMouseVelocityX -= lastMouseVelocityX * 0.08f;
lastMouseVelocityY -= lastMouseVelocityY * 0.08f;
//print(lastMouseVelocityX);
if ((isCounterClockwise && (anglex < 1))||!isCounterClockwise && (anglex > -1))
{
StopCoroutine("InertialRotation");
inertialRotation = false;
}
transform.RotateAround(focus.position, Vector3.up, anglex/3);
transform.RotateAround(focus.position, -transform.right, Mathf.Abs(angley/25));
transform.LookAt(focus);
RelativePosition = transform.position - focus.position;
}
Vector3 Point1;
Vector3 Point2;
//旋转每度,在一帧中需要拖拽的距离
int DragDistancePerAngle = 20;
void DragToRotateView_Distance()
{
float v = Input.GetAxis("Vertical");
float h = Input.GetAxis("Horizontal");
if (!(h==0&&v==0)) //不运动时的旋转灵敏度
{
DragDistancePerAngle = 17; //松手前拖拽灵敏度
sactor = 10; //松手后拖拽灵敏度
}
else //运动时的旋转灵敏度
{
DragDistancePerAngle = 8;
sactor = 4;
}
if (Input.GetMouseButtonDown(0)) //按下鼠标左键的瞬间,记录起始位置
{
Point1 = Input.mousePosition;
StartPoint = Point1;
}
if (Input.GetMouseButton(0)) //按下鼠标左键的每一帧都执行
{
Point2 = Input.mousePosition;
float dx = Point2.x - Point1.x;
float dy = Point2.y - Point1.y;
float anglex = dx / DragDistancePerAngle; //将鼠标在屏幕上拖拽的距离转化为角度
float angley = dy / DragDistancePerAngle;
currentAngleY = 90 - Vector3.Angle(-RelativePosition, Vector3.down); //计算两点连线与水平方向的夹角
if (currentAngleY - angley > MaximumDegree || currentAngleY - angley < MinimumDegree)
angley = 0;
transform.RotateAround(focus.position, Vector3.up, anglex);
transform.RotateAround(focus.position, -transform.right, angley);
transform.LookAt(focus); //如果没有这一句,摄像头转着转着就会歪
RelativePosition = transform.position - focus.position; //更新相对位置
Point1 = Point2;
Point2 = Vector3.zero;
}
if(Input.GetMouseButtonUp(0))
{
EndPoint = Input.mousePosition;
if (Point1!=EndPoint) //鼠标无速度则不进行惯性旋转
inertialRotation = true;
dragX = EndPoint.x - StartPoint.x;
dragY = EndPoint.y - StartPoint.y;
if (dragX > maxdragX) dragX = maxdragX;
else if (dragX < -maxdragX) dragX = -maxdragX;
if (dragX > 0) isCounterClockwise = true;
else if (dragX < 0) isCounterClockwise = false;
print(dragX);
}
if (inertialRotation == true)
StartCoroutine("InertialRotation2");
}
Vector3 StartPoint; //拖拽起点
Vector3 EndPoint; //拖拽终点
float dragX; //水平拖拽距离
float dragY; //垂直拖拽距离
float maxdragX = 3000;
float sactor = 10; //惯性系数
IEnumerator InertialRotation2() //在旋转末尾补上一个逐渐减缓的惯性旋转
{
yield return null;
float anglex = dragX / DragDistancePerAngle / sactor; //将鼠标在屏幕上拖拽的距离转化为角度
float angley = dragY / DragDistancePerAngle / sactor;
currentAngleY = 90 - Vector3.Angle(-RelativePosition, Vector3.down); //计算两点连线与水平方向的夹角
if (currentAngleY - angley > MaximumDegree || currentAngleY - angley < MinimumDegree + 10)
angley = 0;
dragX -= dragX * 0.05f;
dragY -= dragY * 0.05f;
print(dragX);
if ((isCounterClockwise && (anglex < 1)) || !isCounterClockwise && (anglex > -1))
{
StopCoroutine("InertialRotation2");
inertialRotation = false;
}
transform.RotateAround(focus.position, Vector3.up, anglex / 4);
transform.RotateAround(focus.position, -transform.right, Mathf.Abs(angley/4));
transform.LookAt(focus);
RelativePosition = transform.position - focus.position;
}
//鼠标滚轮灵敏度
float mouseWheelSensitivity = 30;
//视野调整方案
public enum ScaleViewBy
{
Distance,
FieldOfView,
Level,
}
//视野选择列表
public ScaleViewBy scaleViewBy = ScaleViewBy.Level;
float MinFieldOfView = 20f;
float MaxFieldOfView = 100f;
void ScrollToScaleView()
{
if (Input.GetAxis("Mouse ScrollWheel") == 0) return;
GetComponent().fieldOfView = GetComponent().fieldOfView - Input.GetAxis("Mouse ScrollWheel") * mouseWheelSensitivity;
GetComponent().fieldOfView = Mathf.Clamp(GetComponent().fieldOfView, MinFieldOfView, MaxFieldOfView);
}
float MinViewDistance = 1;
float MaxViewDistance = 4;
void ScrollToScaleDistance()
{
if (Input.GetAxis("Mouse ScrollWheel") > 0)
{
if (RelativePosition.magnitude <= MinViewDistance) return;
transform.Translate(-RelativePosition / mouseWheelSensitivity *10, Space.World);
RelativePosition = transform.position - focus.position;
}
else if (Input.GetAxis("Mouse ScrollWheel") < 0)
{
if (RelativePosition.magnitude >= MaxViewDistance) return;
transform.Translate(RelativePosition / mouseWheelSensitivity *10, Space.World);
RelativePosition = transform.position - focus.position;
}
}
//单位长度
float unit;
void ScrollToAdjustView()
{
if (Input.GetAxis("Mouse ScrollWheel") > 0)
{
ViewPlus();
preferdLevel = currentLevel;
}
else if (Input.GetAxis("Mouse ScrollWheel") < 0)
{
ViewMinus();
preferdLevel = currentLevel;
}
}
void ViewPlus()
{
if (RelativePosition.magnitude <= MinViewDistance) return;
transform.Translate(-RelativePosition.normalized * unit, Space.World);
RelativePosition = transform.position - focus.position;
currentLevel--;
}
void ViewMinus()
{
if (RelativePosition.magnitude >= MaxViewDistance) return;
transform.Translate(RelativePosition.normalized * unit, Space.World);
RelativePosition = transform.position - focus.position;
currentLevel++;
}
//当前视角级别
int currentLevel = 1;
//偏好视野级别
int preferdLevel = 1;
//是否需要恢复原机位
bool resumable = false;
void OcclusionJudge()
{
if (Physics.Raycast(transform.position, -RelativePosition.normalized, RelativePosition.magnitude - unit)) //如果机位被遮挡
{
resumable = true;
while (Physics.Raycast(transform.position, -RelativePosition.normalized, RelativePosition.magnitude - unit))
{
ViewPlus();
}
}
if (!resumable) return; //如果不需要恢复
Vector3 PositionToResume = focus.position + RelativePosition.normalized * unit * preferdLevel; //计算偏好距离所在位置
if (resumable && !Physics.Raycast(PositionToResume, -RelativePosition.normalized, (preferdLevel - 1) * unit)) //原机位没被遮挡,恢复原位
{
while (currentLevel != preferdLevel)
{
ViewMinus();
}
resumable = false;
}
}
//todo
}
自由视角的角色控制脚本
不同于一般的固定视角的角色控制脚本,该脚本适用于自由视角的角色控制。
该脚本需要挂载在角色上,并指定环绕相机的Transform属性。
操作方式
W 朝着屏幕前方走 S 面向屏幕走 A 朝着屏幕左边走 D 朝着屏幕右边走
空格 跳跃 LeftShift 行走
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//Move by Screen Reference System
public class PlayerControl : MonoBehaviour
{
public Transform camera;
private Rigidbody rigidbody;
public float moveSpeed = 4;
public float jumpForce = 200f;
Animator anim;
private int jumpLimit = 2; //二段跳
void Start()
{
rigidbody = GetComponent();
anim = GetComponent();
}
void FixedUpdate()
{
Move();
Jump();
}
void Move()
{
float v = Input.GetAxis("Vertical");
float h = Input.GetAxis("Horizontal");
if (Input.GetKey(KeyCode.LeftShift))
{
h *= 0.5f;
v *= 0.5f;
}
anim.SetFloat("SpeedX", h);
anim.SetFloat("SpeedY", v);
Vector3 screenRight = camera.right; //以屏幕为参考系移动
Vector3 screenForward = camera.forward;
screenForward.y = 0; //不能有竖直分量
Vector3 sumVector = screenForward * v + screenRight * h; //矢量之和
if (!(h==0&&v==0))
{
transform.rotation = Quaternion.LookRotation(sumVector);
}
transform.Translate(sumVector * moveSpeed * Time.deltaTime, Space.World); //Space.World绝对不能少
}
bool IsGrounded() // 通过射线检测角色是在地面或者物体(角色的零点需要设置在脚底处)
{
return Physics.Raycast(transform.position, -Vector3.up, 0.1f);
}
void Jump()
{
if (IsGrounded()) //如果接触地面,则恢复可跳跃次数
{
jumpLimit = 2;
anim.SetBool("Jump", false);
}
if (Input.GetKeyDown(KeyCode.Space))
{
if (jumpLimit > 0)
{
rigidbody.AddForce(Vector3.up * jumpForce);
anim.SetBool("Jump", true);
jumpLimit--;
}
}
}
}



