Unity2D基础之人物动画、移动、跳跃
Unity2D基础之人物动画、移动、跳跃
一、人物动画
从Window->Assets Store可以打开资源商店页面,可以选购一个免费的2D资源。本文就以这个骑士资源为角色.
Hero Knight
购买完成之后就导入Unity项目了。
通常购买的资源会有一个Demo项目运行,可以看看大概效果。动画、脚本等都有现成写好的。本文以学习为目的,所以会从头走一遍。
1.制作动画
找到Sprites文件夹,里面的HeroKnight文件,点击箭头可以看到这是已经帮我们做好了切割的。
我们先将第一张拖入Hierarchy,取名为Player
我们浏览一下下面的图片,可以发现:
0-6是站立,7-17是奔跑,18-36是攻击…
接着我们先选中0-6的图片,拖入到Player中
这时候就会出现一个保存的弹窗
我们选择好保存的路径和名称即可。
然后分别将其他的动作也以相同的方式创建动画,
可以看到左上角里动画已经都创建好了。
2.搭建场景
动画的切换会在角色控制的时候进行,这里我们先简单搭一个场景。
在Hierarchy里右键->2D Object-> Tilemap->Rectangular,就可以创建一个Tilemap
创建好后点击,左边的视图里就会出现很多小格子。
接着我们找到Environment文件夹下面的图片,简单的在格子里搭一个地板。将文件拖入即可。
这时候点击运行,会发现人物会直接穿过地板掉下去。这是因为还没有碰撞检测。
3.设置碰撞检测
我们点击Player,在Inspector视图里选择Add Compoent,然后依次添加下面三个组件:
-
RigidBody 2D 然后将Freeze Rotation z打勾
-
Capsule Collider 2D 点击Edit Collider 将胶囊体缩小到和人物一样
-
BoxCollider 2D 点击Edit Collider 将检测范围缩小到和角色的脚一样,勾选is Trigger
接下来点击Tilemap,添加Tilemap Collider 2D组件。
现在再运行游戏,角色就会站在地板上了。
二、人物控制与动画切换
角色的移动通过脚本来实现。在这部分我们也会涉及到动画的切换。
同样点击Player -> Add Component -> new script 即可创建脚本。
脚本里有Start()和Update()方法,可以理解为Start()是在初始化的时候执行,Update()是在每一帧刷新的时候执行。
1.移动
1.1 控制移动
要控制角色移动,首先我们要获取角色的RigidBody.添加一个成员变量
同时为了移动速度可控,我们将速度也抽成一个变量。
public float runSpeed ;
private Rigidbody2D myRigidBody2D;
然后在Start()里进行初始化
void Start(){
myRigidBody2D = GetComponent<Rigidbody2D>();
}
控制移动的方法应该在Update()里,为了代码的可读性和可维护性,我们定义一个函数Run()
然后在这个方法里获取按键事件,然后给角色赋值一个速度.具体代码如下:
private void Run(){
var moveDir = Input.GetAxis("Horizontal");
var playerVel = new Vector2(moveDir * runSpeed, myRigidBody2D.velocity.y);
myRigidBody2D.velocity = playerVel;
}
最后记得将Run()函数添加到Update()里
void Update() {
Run();
}
接着回到Unity,点击Player,我们可以在右边看到刚才定义的Run Speed变量,可以在这里进行赋值。
此时运行游戏,按下左右方向键就可以移动了。但此时有两个问题:
- 角色一直朝着一个方向
- 角色的动画一直是站立
接下来就解决这两个问题
1.2 改变角色面朝方向
要改变角色的朝向,首先我们要知道角色是往哪边移动的。
这一点可以通过角色x轴速度的正负来判断,如果为正则是往右,为负则是往左。
这样我们就可以编写一个Flip()函数了
private void Flip(){
if (myRigidBody2D.velocity.x > 0.1f)
{
transform.localRotation = Quaternion.Euler(0, 0, 0);
}
if (myRigidBody2D.velocity.x < -0.1f)
{
transform.localRotation = Quaternion.Euler(0, 180, 0);
}
}
可以将Flip()放到Run()里调用。
1.3 切换动画
到重点了,如何切换动画。
我们回到Unity,来到动画这里。切换动画是由参数来控制的,所以我们先创建一个控制移动的参数。
接着右键点击stand动画,选择Make Transition拉出一个箭头到run动画,接着选中这跟箭头,在Inspector页面取消过渡时间,设置条件isRuning为True的
这样就表示isRuning这个变量为True的时候,就会从stand切换到run。
同样我们也要从run拉一条线回到stand,条件为isRuning为False的时候。
这样我们完成了动画切换的设置。
而给isRuning变量赋值,就是脚本里做的事了。我们需要判断角色当前是否有x轴方向的速度,如果有就将isRuning设置成True, 没有就设置为False。修改一下Run()函数
private void Run(){
var moveDir = Input.GetAxis("Horizontal");
var playerVel = new Vector2(moveDir * runSpeed, myRigidBody2D.velocity.y);
myRigidBody2D.velocity = playerVel;
var playerHasXSpeed = Math.Abs(myRigidBody2D.velocity.x) > Mathf.Epsilon;
if (playerHasXSpeed) {
myAnimator.SetBool("isRunning", true);
Flip();
}
else
{
myAnimator.SetBool("isRunning", false);
}
}
至此,我们的角色就拥有了移动的动画效果。
2.跳跃和降落
完成了移动之后,跳跃和降落就是一样的原理了。大家可以先按移动的逻辑尝试着自己实现跳跃和降落,能实现才说明掌握了。文末我会放出代码。
2.1 解决无限跳跃问题
按照上面的思路实现跳跃,可能会发现角色能够无限跳跃,这通常是不合理的。
要解决这个问题思路就是判断角色当前是否在地面上,如果在就可以跳跃,如果不在就禁止跳跃。
那最终问题就落在了如何判断角色是否在地面上了。还记得我们最开始加的Box Collider 2D组件吗,这时候就派上用场了。
首先做一个准备工作,选中Ground,在右边Layer的地方点击Add Layer,找一个User Layer的地方写上Ground
然后到脚本里,先在Start方法里获取BoxCollider2D组件,然后通过IsTouchingLayers方法来判断是否触碰了Layer。我们可以添加一个全局变量来保存这个值,并在Update()里去更新调用。
private void CheckIsOnGround(){
mIsOnGround = playerFeet.IsTouchingLayers(LayerMask.GetMask("Ground"));
}
在Jump里呢就可以通过读取mIsOnGround这个值,来控制是否可以跳跃。
我们还能在这基础上加上二段跳。思路很简单,这里就不赘述了,最后放上本文涉及的代码。
public class PlayerControler : MonoBehaviour
{
public float runSpeed ;
public float jumpSpeed;
public bool canDoubleJump;
private Rigidbody2D myRigidBody2D;
private Animator myAnimator;
private BoxCollider2D playerFeet;
private bool mIsOnGround = true;
private int jumpTimes = 0;
// Start is called before the first frame update
void Start()
{
myRigidBody2D = GetComponent<Rigidbody2D>();
myAnimator = GetComponent<Animator>();
playerFeet = GetComponent<BoxCollider2D>();
}
// Update is called once per frame
void Update()
{
CheckIsOnGround();
Run();
Jump();
Fall();
}
private void CheckIsOnGround()
{
mIsOnGround = playerFeet.IsTouchingLayers(LayerMask.GetMask("Ground"));
}
private void Run()
{
var moveDir = Input.GetAxis("Horizontal");
var playerVel = new Vector2(moveDir * runSpeed, myRigidBody2D.velocity.y);
myRigidBody2D.velocity = playerVel;
var playerHasXSpeed = Math.Abs(myRigidBody2D.velocity.x) > Mathf.Epsilon;
if (playerHasXSpeed)
{
myAnimator.SetBool("isRunning", true);
Flip();
}
else
{
myAnimator.SetBool("isRunning", false);
}
}
private void Flip()
{
if (myRigidBody2D.velocity.x > 0.1f)
{
transform.localRotation = Quaternion.Euler(0, 0, 0);
}
if (myRigidBody2D.velocity.x < -0.1f)
{
transform.localRotation = Quaternion.Euler(0, 180, 0);
}
}
private void Jump()
{
if (Input.GetButtonDown("Jump"))
{
if (mIsOnGround || (canDoubleJump && jumpTimes < 1))
{
var playerVel = new Vector2(myRigidBody2D.velocity.x, jumpSpeed);
myRigidBody2D.velocity = playerVel;
myAnimator.SetBool("isJumping", true);
jumpTimes++;
}
}
}
private void Fall()
{
if (myRigidBody2D.velocity.y < -0.1f)
{
myAnimator.SetBool("isJumping", false);
myAnimator.SetBool("isFalling", true);
}
if (mIsOnGround)
{
myAnimator.SetBool("isFalling", false);
jumpTimes = 0;
}
}
}