Unity-平台跳跃任务控制器
前言:
Unity制作2D游戏中,跳跃机制是个问题,今天我们就来学习一下一个很好手感的平台跳跃人物控制器。
跟着的教程是b站的此教程
成品展示
关于陷阱机关这些图片请在Unity商店中随便找个2D冒险题材的素材,关于人物请下载这张图片:
好了,接下来我们来动手制作
优秀的移动
新建一个2D场景,然后可以去找一些2D素材,我这里在Unity Assert里找了Pixel Adventure,准备用它来搭建基本地面。
建立两个Tilemap,一个是背景,一个是素材地面,我的最后效果是这样的:
当然,这只是个背景,不弄也行,毕竟这篇文章主要是学人物的控制。
然后把我们的主角弄进来,加个碰撞盒和刚体,给我们的Tilemap也加上tilemap的碰撞检测。
把人物放好之后我们开始写脚本:
[Range (0,5)]
public float walkSpeed=3.5f;
private Rigidbody2D _rigidbody2D;
private float _velocityX;
public float AccelerateTime=0.09f;
private void Awake()
{
_rigidbody2D = GetComponent<Rigidbody2D>();
}
void FixedUpdate()
{
//velocity:刚体的线速度
//三目运算符来返回一个速度
float speed =
(Input.GetAxis("Horizontal") == 0 ?
0 : (Input.GetAxis("Horizontal") > 0 ?
walkSpeed : -walkSpeed));
if (speed < 0)
{
_rigidbody2D.transform.eulerAngles =
new Vector3(0, 180, 0);
}
if (speed > 0) {
_rigidbody2D.transform.eulerAngles =
new Vector3(0, 0, 0);
}
//平滑的移动
_rigidbody2D.velocity =
new Vector2(
Mathf.SmoothDamp(
_rigidbody2D.velocity.x,
speed * Time.fixedDeltaTime*60,
ref _velocityX,
AccelerateTime),
_rigidbody2D.velocity.y);
}
可能会出现的问题及解决方法:
- 人物不能立即停下:在Project Setting的Input中找到Horizontal的设置,然后将Gravity调高
- 人物行走中会突然卡住:使用椭圆的碰撞盒或者box碰撞盒中调整一点点Edge Radius(有一个圆角)
我们加上动画后看一下效果,加上动画我就不演示了,相信你可以的:
有一说一这个控制手感是真的好!
优秀的跳跃
接下来我们来实现跳跃功能
代码:
private Rigidbody2D _rigidbody2D;
private Animator _animator;
private float _velocityX;
private int _speedID = Animator.StringToHash("speed");
private int _jumpID = Animator.StringToHash("jump");
private bool _isOnGround = false;
[Header("移动速度")]
public float walkSpeed=3.5f;
public float AccelerateTime = 0.09f;
[Header("跳跃")]
public float JumpingSpeed;
public float FallMultipier;
public float LowJumpMultiplier;
private bool _isJumping=false;
[Header("触地检测盒")]
public Vector2 PointOffset; //地面盒形接触框的位置偏差
public Vector2 Size; //地面盒形接触框的大小
public LayerMask GroundLayerMask; //地面盒形接触框的检测层
private void Awake()
{
_rigidbody2D = GetComponent<Rigidbody2D>();
_animator = GetComponent<Animator>();
}
void FixedUpdate()
{
//判断是否碰到地面
_isOnGround = OnGround();
//velocity:刚体的线速度
float speed =
(Input.GetAxis("Horizontal") == 0 ?
0 : (Input.GetAxis("Horizontal") > 0 ?
walkSpeed : -walkSpeed));
if (speed < 0)
{
_rigidbody2D.transform.eulerAngles = new Vector3(0, 180, 0);
_animator.SetFloat(_speedID, 1);
}
else if (speed > 0)
{
_rigidbody2D.transform.eulerAngles = new Vector3(0, 0, 0);
_animator.SetFloat(_speedID, 1);
}
else {
_animator.SetFloat(_speedID, 0);
}
//平滑的移动
_rigidbody2D.velocity =
new Vector2(
Mathf.SmoothDamp(
_rigidbody2D.velocity.x,
speed * Time.fixedDeltaTime*60,
ref _velocityX,
AccelerateTime),
_rigidbody2D.velocity.y);
/* 注意:GetButtonDown等类似方法不可以
* 在fixedUpdate中使用,因为这FixedUpdate
* 是按照时间执行的,你必须保证你按键的那一帧
* 正好fixedUpdate也该执行了,才会检验到你的按键
* 所以我们使用GetAxis("Jump"),GetAxis与帧率无关
* ,只要按键就在0到1变化
*/
if (Input.GetAxis("Jump") == 1&&!_isJumping) {
//给一个向上的线速度
_rigidbody2D.velocity = new Vector2(
_rigidbody2D.velocity.x, JumpingSpeed);
_animator.SetTrigger(_jumpID);
_isJumping = true;
}
if (_isOnGround&&Input.GetAxis("Jump")==0) {
_isJumping = false;
}
//线速度是负值(即下坠中)(加速下坠)
if (_rigidbody2D.velocity.y < 0)
{
_rigidbody2D.velocity += Vector2.up *
Physics2D.gravity.y * //重力的y是一个负值
FallMultipier *
Time.fixedDeltaTime;
print(Physics2D.gravity.y);
}
//当玩家上升时,玩家不再按跳跃键了(减缓上升)
else if (_rigidbody2D.velocity.y > 0 && Input.GetAxis("Jump") != 1) {
_rigidbody2D.velocity += Vector2.up *
Physics2D.gravity.y *
LowJumpMultiplier *
Time.fixedDeltaTime;
}
}
private bool OnGround() {
//物理判定框
//参数:盒子中心、大小、角度、层筛选器
Collider2D Coll=Physics2D.OverlapBox((Vector2)transform.position+PointOffset,
Size,
0,
GroundLayerMask);
if (Coll != null) {
return true;
}
return false;
}
//可视效果
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireCube((Vector2)transform.position + PointOffset,
Size);
}
在外边记得给变量们赋值,还要设置好Layer。
效果图:
中间休息
我们为下半场来做一些准备:
优化
这里有个可以优化的地方,就是上面你可以使用getAxisRaw来替代getAxis方法。
getAxis是返回一个渐变,0->0.3->0.6->1,而getAxisRaw会直接到1
这样就避免了人物滑行问题,你可以将Project Setting的Input中找到Horizontal的Gravity调回原来的值了。
DOTween
下面我们会用到Unity的插件——DoTween,在Unity Assert里面就可以找到免费的doTween
冲刺准备
修改Project Setting的Input中的一个fire1,修改成dash:
代码缩写
利用C#内部的预处理器指令#region来整合代码块,从而让思路清晰:
void FixedUpdate()
{
//判断是否碰到地面
_isOnGround = OnGround();
#region 左右移动
//velocity:刚体的线速度
float speed =
(Input.GetAxisRaw("Horizontal") == 0 ?
0 : (Input.GetAxisRaw("Horizontal") > 0 ?
walkSpeed : -walkSpeed));
if (speed < 0)
{
_rigidbody2D.transform.eulerAngles = new Vector3(0, 180, 0);
_animator.SetFloat(_speedID, 1);
}
else if (speed > 0)
{
_rigidbody2D.transform.eulerAngles = new Vector3(0, 0, 0);
_animator.SetFloat(_speedID, 1);
}
else {
_animator.SetFloat(_speedID, 0);
}
//平滑的移动
_rigidbody2D.velocity =
new Vector2(
Mathf.SmoothDamp(
_rigidbody2D.velocity.x,
speed * Time.fixedDeltaTime*60,
ref _velocityX,
AccelerateTime),
_rigidbody2D.velocity.y);
#endregion
#region 跳跃控制
/* 注意:GetButtonDown等类似方法不可以
* 在fixedUpdate中使用,因为这FixedUpdate
* 是按照时间执行的,你必须保证你按键的那一帧
* 正好fixedUpdate也该执行了,才会检验到你的按键
* 所以我们使用GetAxis("Jump"),GetAxis与帧率无关
* ,只要按键就在0到1变化
*/
if (Input.GetAxis("Jump") == 1&&!_isJumping) {
//给一个向上的线速度
_rigidbody2D.velocity = new Vector2(
_rigidbody2D.velocity.x, JumpingSpeed);
_animator.SetTrigger(_jumpID);
_isJumping = true;
}
if (_isOnGround&&Input.GetAxis("Jump")==0) {
_isJumping = false;
}
#endregion
#region 重力调整器
//线速度是负值(即下坠中)(加速下坠)
if (_rigidbody2D.velocity.y < 0)
{
_rigidbody2D.velocity += Vector2.up *
Physics2D.gravity.y * //重力的y是一个负值
FallMultipier *
Time.fixedDeltaTime;
}
//当玩家上升时,玩家不再按跳跃键了(减缓上升)
else if (_rigidbody2D.velocity.y > 0 && Input.GetAxis("Jump") != 1) {
_rigidbody2D.velocity += Vector2.up *
Physics2D.gravity.y *
LowJumpMultiplier *
Time.fixedDeltaTime;
}
#endregion
}
关闭后是这样:
实现冲刺
实现冲刺的逻辑其实很简单,就是先取消掉物理限制之类的,然后,给主角一个方向一个力,然后再恢复那些限制即可。
public class PlayerControl : MonoBehaviour
{
private Rigidbody2D _rigidbody2D;
private Animator _animator;
private float _velocityX;
private int _speedID = Animator.StringToHash("speed");
private int _jumpID = Animator.StringToHash("jump");
private bool _isOnGround = false;
[Header("移动速度")]
public float walkSpeed=3.5f;
public float AccelerateTime = 0.09f;
bool _canMove=true;
[Header("跳跃")]
public float JumpingSpeed;
public float FallMultipier;
public float LowJumpMultiplier;
private bool _isJumping=false;
bool _canJump = true;
[Header("触地检测盒")]
public Vector2 PointOffset; //地面盒形接触框的位置偏差
public Vector2 Size; //地面盒形接触框的大小
public LayerMask GroundLayerMask; //地面盒形接触框的检测层
bool _gravityModifier = true; //允许重力调整
[Header("冲刺")]
private bool _wasDashed = false;
private Vector2 _dir;
public float DashForce;
public float DragMaxForce;
public float DragDuration;
public float DashWaitTime;
private void Awake()
{
_rigidbody2D = GetComponent<Rigidbody2D>();
_animator = GetComponent<Animator>();
}
void FixedUpdate()
{
//判断是否碰到地面
_isOnGround = OnGround();
#region 左右移动
if (_canMove)
{
//velocity:刚体的线速度
float speed =
(Input.GetAxisRaw("Horizontal") == 0 ?
0 : (Input.GetAxisRaw("Horizontal") > 0 ?
walkSpeed : -walkSpeed));
if (speed < 0)
{
_rigidbody2D.transform.eulerAngles = new Vector3(0, 180, 0);
_animator.SetFloat(_speedID, 1);
}
else if (speed > 0)
{
_rigidbody2D.transform.eulerAngles = new Vector3(0, 0, 0);
_animator.SetFloat(_speedID, 1);
}
else
{
_animator.SetFloat(_speedID, 0);
}
//平滑的移动
_rigidbody2D.velocity =
new Vector2(
Mathf.SmoothDamp(
_rigidbody2D.velocity.x,
speed * Time.fixedDeltaTime * 60,
ref _velocityX,
AccelerateTime),
_rigidbody2D.velocity.y);
}
#endregion
#region 跳跃控制
/* 注意:GetButtonDown等类似方法不可以
* 在fixedUpdate中使用,因为这FixedUpdate
* 是按照时间执行的,你必须保证你按键的那一帧
* 正好fixedUpdate也该执行了,才会检验到你的按键
* 所以我们使用GetAxis("Jump"),GetAxis与帧率无关
* ,只要按键就在0到1变化
*/
if (_canJump)
{
if (Input.GetAxis("Jump") == 1 && !_isJumping)
{
//给一个向上的线速度
_rigidbody2D.velocity = new Vector2(
_rigidbody2D.velocity.x, JumpingSpeed);
_animator.SetTrigger(_jumpID);
_isJumping = true;
}
if (_isOnGround && Input.GetAxis("Jump") == 0)
{
_isJumping = false;
}
}
#endregion
#region 重力调整器
if (_gravityModifier)
{
//线速度是负值(即下坠中)(加速下坠)
if (_rigidbody2D.velocity.y < 0)
{
_rigidbody2D.velocity += Vector2.up *
Physics2D.gravity.y * //重力的y是一个负值
FallMultipier *
Time.fixedDeltaTime;
}
//当玩家上升时,玩家不再按跳跃键了(减缓上升)
else if (_rigidbody2D.velocity.y > 0 && Input.GetAxis("Jump") != 1)
{
_rigidbody2D.velocity += Vector2.up *
Physics2D.gravity.y *
LowJumpMultiplier *
Time.fixedDeltaTime;
}
}
#endregion
#region 冲刺
if (Input.GetAxisRaw("Dash") == 1&& !_wasDashed) {
_wasDashed = true;
_dir = GetDir();
//将玩家当前所有的动量清零
_rigidbody2D.velocity = Vector2.zero;
//施加一个力,让玩家飞出去
_rigidbody2D.velocity += _dir * DashForce;
StartCoroutine(Dash());
}
if (_isOnGround&&Input.GetAxisRaw("Dash")==0) {
_wasDashed = false;
}
#endregion
}
IEnumerator Dash() {
//关闭玩家的移动和跳跃的功能
_canJump = false;
_canMove = false;
//关闭重力调整器
_gravityModifier = false;
//关闭重力影响
_rigidbody2D.gravityScale = 0;
//施加一个空气阻力(Rigidbody.Drag)
//创建一个虚拟tween来不断调用方法更改值
DOVirtual.Float(DragMaxForce,0, DragDuration, RigidbodyDrag);
//等待一段时间
yield return new WaitForSeconds(DashWaitTime);
//恢复所有关闭的东西
_canJump = true;
_canMove = true;
_gravityModifier = true;
_rigidbody2D.gravityScale = 1;
}
private Vector2 GetDir() {
Vector2 temp= new Vector2(Input.GetAxisRaw("Horizontal"),
Input.GetAxisRaw("Vertical"));
if (temp.x == 0 && temp.y == 0) {
//默认向主角朝向进行冲刺
if (_rigidbody2D.transform.eulerAngles.y == 0)
{
temp.x = 1;
}
else {
temp.x = -1;
}
}
return temp;
}
private void RigidbodyDrag(float x) {
_rigidbody2D.drag = x;
print("阻力:" + x);
}
private bool OnGround() {
//物理判定框
//参数:盒子中心、大小、角度、层筛选器
Collider2D Coll=Physics2D.OverlapBox((Vector2)transform.position+PointOffset,
Size,
0,
GroundLayerMask);
if (Coll != null) {
return true;
}
return false;
}
//可视效果
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawWireCube((Vector2)transform.position + PointOffset,
Size);
}
}