前言
最近对 HD2D 风格的游戏特别感兴趣,便开始着手制作一个简单的角色控制脚本。
实现步骤
1. 创建角色刚体和胶囊碰撞器
首先在角色的对象上创建一个刚体和胶囊碰撞器,并设置好参数。
2. 创建角色动画
在角色的对象上创建一个动画控制器,并添加相应的动画。
3. 创建角色控制器脚本
新建一个脚本,开始一步步实现吧!
3.1 定义变量
首先定义一些变量,包括速度、跳跃力度、地面检测忽略的图层、动画组件、Sprite Renderer 组件、人物移动、人物身上的胶囊碰撞体、点、半径、是否在地面。
public class PlayerMovement : MonoBehaviour
{
// 获取人物的刚体组件
private new Rigidbody rigidbody;
// 人物的速度
public float speed = 4F;
// 跳跃力度
private float jumpForce = 3F;
// 地面检测忽略的图层
public LayerMask ignoreLayer;
// 动画组件
private Animator anim;
// Sprite Renderer组件
private SpriteRenderer spriteRenderer;
// 人物移动
private Vector2 input;
private Vector3 moveInput;
// 人物身上的胶囊碰撞体
private CapsuleCollider capsuleCollider;
private Vector3 pointBottom, pointTop;
private float radius;
private bool isGround;
}
3.2 人物行走
我们要让人物正常行走,首先需要获取刚体组件和动画器组件。
void Start()
{
// 获取刚体组件
rigidbody = GetComponent<Rigidbody>();
// 获取动画器
anim = GetComponent<Animator>();
}
然后我们需要在 Update 中获取输入,并将其赋值给 input 变量。
void Update()
{
// 获取水平垂直输入
input.x = Input.GetAxisRaw("Horizontal");
input.y = Input.GetAxisRaw("Vertical");
moveInput = new Vector3(input.x * speed, rigidbody.velocity.y, input.y * speed);
}
接着我们需要将 input 变量乘以速度,将其赋值给刚体的速度。
void FixedUpdate()
{
// 人物的移动
rigidbody.velocity = moveInput;
}
接着在 void Update () 中播放动画。
// 设定isRunning的值,是true还是false。根据返回向量的长度来决定的。
anim.SetBool("isRunning", input.magnitude > 0 ? true : false);
3.3 人物精灵图翻转
刚刚实现的效果我们可以发现一个小问题,由于我们只制作了单个方向的行走动画,导致向左向右行走时都是播放相同方向的东西,显得异常突兀。
为了解决这个问题,我们需要让角色的精灵图翻转,具体的做法是,判断 input. X < 0 时,将角色的 Sprite Renderer 组件的 flipX 属性设置为 true,否则设置为 false。
首先我们需要在 void Start () 中获取 Sprite Renderer 组件。
// 获取Sprite Renderer组件
spriteRenderer = GetComponent<SpriteRenderer>();
然后在 void Update () 中判断 input. X < 0,并设置 spriteRenderer 的 flipX 属性。
// 根据水平输入来翻转Sprite
if (input.x != 0)
{
spriteRenderer.flipX = input.x < 0;
}
现在我们获得了一个看上去正常得多的行走动画。
3.4 人物跳跃
接下来我们要实现人物跳跃,首先我们需要判断角色是否在地面上,如果在地面上,我们就允许跳跃。如果不在地面上,则播放悬空动画,并禁止再次跳跃。
这一部分参考傅老師MrFu的黑魂DarkSouls複刻經典教程。
首先我们需要在 void Start () 中获取胶囊碰撞体组件。
capsuleCollider = GetComponent<CapsuleCollider>();
radius = capsuleCollider.radius;
然后创建一个 bool OnGround () 函数用于判断角色是否在地面上。
bool OnGround()
{
pointBottom = transform.position + transform.up * radius - transform.up * 0.1f;
pointTop = transform.position + transform.up * capsuleCollider.height - transform.up * radius;
Collider[] colliders = Physics.OverlapCapsule(pointBottom, pointTop, radius, ~ignoreLayer);
Debug.DrawLine(pointBottom, pointTop, Color.green);
if (colliders.Length != 0)
{
isGround = true;
return true;
}
else
{
isGround = false;
return false;
}
}
然后我们需要在 void Update () 中调用 OnGround () 函数,并在角色在地面上时播放跳跃动画。
anim.SetBool("isJumping", !OnGround());
接着我们在 void Update () 中获取按键输入,当角色在地面上并且按下键盘空格键或者手柄 A 键时,给予角色一个向上的跳跃力度。
// 检查跳跃输入
if (Input.GetKeyDown(KeyCode.Space) || Input.GetKeyDown(KeyCode.JoystickButton0))
{
if (isGround)
{
// 应用向上的力
rigidbody.AddForce(new Vector3(0, jumpForce, 0), ForceMode.VelocityChange);
}
}
现在我们就能获得一个比较完美的跳跃效果了。
完整代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
// 获取人物的刚体组件
private new Rigidbody rigidbody;
// 人物的速度
public float speed = 4F;
// 跳跃力度
private float jumpForce = 3F;
// 地面检测忽略的图层
public LayerMask ignoreLayer;
// 动画组件
private Animator anim;
// Sprite Renderer组件
private SpriteRenderer spriteRenderer;
// 人物移动
private Vector2 input;
// 人物身上的胶囊碰撞体
private CapsuleCollider capsuleCollider;
private Vector3 pointBottom, pointTop;
private float radius;
private bool isGround;
// Start is called before the first frame update
void Start()
{
// 获取刚体组件
rigidbody = GetComponent<Rigidbody>();
// 获取动画器
anim = GetComponent<Animator>();
// 获取Sprite Renderer组件
spriteRenderer = GetComponent<SpriteRenderer>();
capsuleCollider = GetComponent<CapsuleCollider>();
radius = capsuleCollider.radius;
}
void FixedUpdate()
{
// 人物的移动
rigidbody.velocity = new Vector3(input.x * speed, rigidbody.velocity.y, input.y * speed);
}
// Update is called once per frame
void Update()
{
// 获取水平垂直输入
input.x = Input.GetAxisRaw("Horizontal");
input.y = Input.GetAxisRaw("Vertical");
// 根据水平输入来翻转Sprite
if (input.x != 0)
{
spriteRenderer.flipX = input.x < 0;
}
// 设定isRunning的值,是true还是false。根据返回向量的长度来决定的。
anim.SetBool("isRunning", input.magnitude > 0 ? true : false);
anim.SetBool("isJumping", !OnGround());
// 检查跳跃输入
if (Input.GetKeyDown(KeyCode.Space) || Input.GetKeyDown(KeyCode.JoystickButton0))
{
if (isGround)
{
// 应用向上的力
rigidbody.AddForce(new Vector3(0, jumpForce, 0), ForceMode.VelocityChange);
}
}
}
// https://www.bilibili.com/video/av21513489/?p=19
bool OnGround()
{
pointBottom = transform.position + transform.up * radius - transform.up * 0.1f;
pointTop = transform.position + transform.up * capsuleCollider.height - transform.up * radius;
Collider[] colliders = Physics.OverlapCapsule(pointBottom, pointTop, radius, ~ignoreLayer);
Debug.DrawLine(pointBottom, pointTop, Color.green);
if (colliders.Length != 0)
{
isGround = true;
return true;
}
else
{
isGround = false;
return false;
}
}
}