自己作为一个游戏美术从业人员,想自己练习做个角色控制器,主要是学习代码编写,用很简单的过程式代码实现基本效果。主要是学习为目的。
代码分摄像机和角色控制两部分组成。下面的是摄像机:(自学者,格式啥的可能不规范,多多包涵)
总结基本思路:
1. transform.RotateAround实现全向旋转,分别有两个旋转轴,第二个轴是用了向量叉乘来确定轴方向,但是有万向节问题,我采用限制视角来解决。
2.为了实现摄像机要接触地面时能朝角色拉近拉远,不会穿地的需求,采用unity射线检测的方法Physics.Raycast()(注意 1 << 8表示把层里面8的索引转换成2进制的意思);返回的out RaycastHit hitinfo 里面有很多有用的东西,为了得到丝滑的运动速度变化效果用了幂函数来实现离地面越近速度越快: toTarSpeed = Mathf.Pow(2 - dis, 2)。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Syj_Camera : MonoBehaviour
{
private Vector3 m_TargetPosition;
private Vector3 target;
private Vector3 m_TargetPosition2;
private float smooth = 1f;
public float speed = 5f;
Vector3 oldPos;
Quaternion oldquaternion;
private float toTarSpeed = 0;
Charactor_Controller script;
// Start is called before the first frame update
void Start()
{
script = FindObjectOfType<Charactor_Controller>(); //访问角色脚本
target = GameObject.FindWithTag("Player").transform.position + new Vector3(0, 1, 0);
//初始摄像机的推进两个位置点
m_TargetPosition = GameObject.FindWithTag("Player").transform.position + new Vector3(0, 2, -5.2f);
m_TargetPosition2 = GameObject.FindWithTag("Player").transform.position + new Vector3(0, 2, -7f);
transform.position = m_TargetPosition2;
transform.LookAt(target);
}
// Update is called once per frame
void Update()
{
//角色转向的摄像机的坐标系转换成世界坐标系并传入角色脚本
float _h = Input.GetAxis("Horizontal");
float _v = Input.GetAxis("Vertical");
Vector3 dirInCam = new Vector3(_h, 0, _v);
dirInCam = transform.TransformDirection(dirInCam);
script.turnAxis(dirInCam); //调用角色脚本函数,传入数据
target = GameObject.FindWithTag("Player").transform.position + new Vector3(0, 1, 0);
//初始摄像机的推进
if (Time.time < 2)
{
transform.position = Vector3.Lerp(transform.position, m_TargetPosition, Time.deltaTime * smooth);
}
float dx = 0;
float dy = 0;
//获得鼠标移动数据
if (Time.time > 2)
{
dx = Input.GetAxis("Mouse X");
dy = Input.GetAxis("Mouse Y");
}
//竖直旋转轴的选定(向量叉乘)
Vector3 tarToCam = transform.position - target;
Vector3 axisdy = Vector3.Cross(tarToCam, -Vector3.up);
//全向旋转摄像机
transform.RotateAround(target, Vector3.up, dx * speed);
transform.RotateAround(target, axisdy, dy * speed);
transform.LookAt(target);
float h = Vector3.Dot(Vector3.Normalize(transform.position - target), Vector3.up);
//解决万向节
if (Mathf.Abs(h) > 0.9)
{
transform.position = oldPos;
transform.rotation = oldquaternion;
}
oldPos = transform.position;
oldquaternion = transform.rotation;
//用射线方法防止镜头穿帮
Ray ray = new Ray(transform.position, Vector3.down);
bool Key = Physics.Raycast(ray, out RaycastHit hitinfo, 1000, 1 << 8);
float dis = hitinfo.distance;
if (Key)
{
if (dis < 0.6)
{
toTarSpeed = Mathf.Pow(2 - dis, 2);
}
else if (dis > 0.62 && Vector3.Distance(transform.position, target) < 5)
{
toTarSpeed = -Mathf.Pow(dis + 2.5f, 2);
}
else
{
toTarSpeed = 0;
}
}
else
{
toTarSpeed = 100;
}
transform.Translate(0, 0, toTarSpeed * Time.deltaTime, Space.Self);
}
}
角色控制代码:
总结思路:
1.先实现世界坐标系下角色转向正确,然后把摄像机里的方向转换到世界空间下。Input.GetAxis
控制转向想的是利用四元数来先记录角色上一帧的旋转状态,然后用Quaternion.LookRotation()
看向一个世界空间下的方向,为了得“丝滑”的旋转效果,用了 Quaternion.Lerp();
2.转向一旦实现了,运动就好说了,需求就是按照WASD控制在摄像机空间方向运动。
3.跳跃实现 : Input.GetButton("Jump") (注意J大小写)
4.解决角色从地面掉下去的问题:
a.利用胶囊体射线检测地面 Physics.OverlapCapsule(参考的方法:unity复杂地形检测角色是否在地面上(与地面碰撞)(着地)的方法_云上空的博客-CSDN博客_unity检测角色是否在地面上)
b.在场景里给角色添加球型碰撞框(取消is Trigger,实现刚体功能)
注意:| 和 || 还是有区别。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Charactor_Controller : MonoBehaviour
{
public Transform cam;
public float speedScale = 5f;
private Vector3 move;
public float jumpSpeed = 10;
public float gravity = 20;
public float margin = 1.001f;
public float overLapCapsuleOffset = 1.001f;
private Vector3 moveDirection = Vector3.zero;
Vector3 get;
private CapsuleCollider capsuleCollider;
private Vector3 pointBottom, pointTop;
private float radius;
public LayerMask ignoreLayer;
bool isOnGround;
bool OnGround()
{
pointBottom = transform.position + transform.up * radius - transform.up * overLapCapsuleOffset;
pointTop = transform.position + transform.up * capsuleCollider.height - transform.up * radius;
//LayerMask ignoreMask = ~ignoreLayer;
Collider[] colliders = Physics.OverlapCapsule(pointBottom, pointTop, radius, 1 << 8);
Debug.DrawLine(pointBottom, pointTop, Color.green);
if (colliders.Length != 0)
{
isOnGround = true;
return true;
}
else
{
isOnGround = false;
return false;
}
}
void Awake()
{
capsuleCollider = GetComponent<CapsuleCollider>();
radius = capsuleCollider.radius * 0.9f;
}
// Start is called before the first frame update
void Start()
{
}
public void turnAxis(Vector3 v3)
{
get = v3;
}
// 通过射线检测主角是否落在地面或者物体上
//bool IsGrounded()
//{
// return Physics.Raycast(transform.position, -Vector3.up, margin);
//}
void Update()
{
Vector3 moveCam = cam.transform.position - transform.position;
// 控制移动
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
float speed;
//求WASD一起控制的速度大小
moveDirection = new Vector3(h, 0, v);
speed = Vector3.SqrMagnitude(Vector3.Normalize(moveDirection));
//bool ground_Dis = Physics.Raycast(transform.position, Vector3.down, 2f);
OnGround();
if (isOnGround)
{
move.y = 0;
//先以世界坐标系实现转向,后面再转换坐标空间
if (Input.GetKey(KeyCode.A) | Input.GetKey(KeyCode.W) | Input.GetKey(KeyCode.S) | Input.GetKey(KeyCode.D))
{
Quaternion quaternion1 = transform.rotation;
Quaternion quaternion2 = Quaternion.LookRotation(new Vector3(get.x, 0, get.z), Vector3.up);
transform.rotation = Quaternion.Lerp(quaternion1, quaternion2, 0.2f);
}
// 空格键控制跳跃
if (Input.GetButton("Jump"))
{
move.y = jumpSpeed;
}
}
else
{
move.y = move.y - gravity * Time.deltaTime;
}
if (Input.GetKey(KeyCode.A) | Input.GetKey(KeyCode.W) | Input.GetKey(KeyCode.S) | Input.GetKey(KeyCode.D))
{
transform.position += transform.forward * speed * Time.deltaTime * speedScale;
}
transform.position += move * Time.deltaTime;
cam.transform.position = transform.position + moveCam;
}
}