Unity开发2D类银河恶魔城游戏学习笔记
Unity教程(零)Unity和VS的使用相关内容
Unity教程(一)开始学习状态机
Unity教程(二)角色移动的实现
Unity教程(三)角色跳跃的实现
Unity教程(四)碰撞检测
Unity教程(五)角色冲刺的实现
Unity教程(六)角色滑墙的实现
Unity教程(七)角色蹬墙跳的实现
Unity教程(八)角色攻击的基本实现
Unity教程(九)角色攻击的改进
Unity教程(十)Tile Palette搭建平台关卡
Unity教程(十一)相机
Unity教程(十二)视差背景
Unity教程(十三)敌人状态机
Unity教程(十四)敌人空闲和移动的实现
如果你更习惯用知乎
Unity开发2D类银河恶魔城游戏学习笔记目录
文章目录
- Unity开发2D类银河恶魔城游戏学习笔记
- 前言
- 一、概述
- 二、创建动画(可不看,自行实现即可)
- (1)创建Enemy_Skeleton并添加动画师相关组件
- (2)创建空闲状态和移动状态动画
- 三、添加刚体、碰撞盒和碰撞检测(可不看,自行实现即可)
- (1)添加刚体
- (2)添加碰撞盒
- (3)添加碰撞检测
- 四、实现空闲状态和移动状态
- (1)修改构造函数
- (2)实现空闲状态
- (2)实现移动状态
- 总结 完整代码
- Enemy
- Enemy_Skeleton
- SkeletonIdleState
- SkeletonMoveState
前言
本文为Udemy课程The Ultimate Guide to Creating an RPG Game in Unity学习笔记,如有错误,欢迎指正。
本节实现敌人的空闲状态和移动状态。
对应b站视频:
【Unity教程】从0编程制作类银河恶魔城游戏P49
一、概述
本节中我们实现骷髅的空闲状态和移动状态。
骷髅小怪在地面上来回踱步,遇到地面边缘或者墙壁时翻转且由移动状态转换为空闲状态。
每隔一个时间间隔由空闲状态转换为移动状态。
看过前面Player相关步骤的,创建动画和添加碰撞检测的部分可跳过不看,自行实现。 记得将Animator中的Sorting Layer改为Enemy即可。
二、创建动画(可不看,自行实现即可)
(1)创建Enemy_Skeleton并添加动画师相关组件
骷髅的精灵表存放路径为
Assets->Graphics->Enemies->Skeleton
此处所用素材已经分割好了,如果想学习从头到尾分割精灵表可以参照
Unity教程(零)Unity和VS的使用相关内容
将Skeleton Idle的0帧拖入场景中,并重命名为Animator。
接着将它的Sorting Layer改为Enemy,骷髅显示在上层。
为Animator创建一个空的父对象,重命名为Enemy_Skeleton。
此时Animator并不在Enemy_Skeleton的中心位置。接下来将Animator图片调整到父对象的中心。
在层次面板选中Animator,移动它的位置,这里调整后Animator的x坐标大概为0.25
在层次面板中点击选中Animator,添加Animator组件
在右侧面板中点击下方Add Component添加组件,搜索Animator并点击添加
(2)创建空闲状态和移动状态动画
整理一下之前的动画和控制器。大致如下:
动画的存放
控制器的存放
在controllers文件夹中创建Enemy_Skeleton控制器。
右击创建Animator Controlller,命名为Skeleton_AC
右键->Create->Animator Controller
拖动Skeleton_AC到Animator的Animator组件上
创建空闲状态动画skeletonIdle
skeletonIdle在Skeleton Idle精灵表标号3-10,采样率改为12
具体讲解见Unity教程(零)Unity和VS的使用相关内容
同理,skeletonMove,将Skeleton Walk精灵表整个加入,采样率改为10
连接状态机,并添加过渡条件Idle和Move,并修改过渡设置
Entry->skeletonMove的过渡,加条件变量
skeletonMove->Exit的过渡,加条件变量并更改设置
三、添加刚体、碰撞盒和碰撞检测(可不看,自行实现即可)
(1)添加刚体
要让敌人按照物理规则正常反应我们还要添加刚体组件。
Add Component->Rigidbody 2D
并进行如下设置:
在刚体中设置Constraints->Freeze Rotation->Z,冻结Z轴,这样骷髅在移动时就不会倒下了。
Collision Detection碰撞检测选择Continuous,防止骷髅没有检测碰撞而穿过其他对象。
Interpolate插值方式选择Interpolate,因为有时物理与图形的不同步会导致视觉抖动,打开插值可以进行平滑。
Gravity Scale设置为4,防止被玩家撞下平台。
(2)添加碰撞盒
给骷髅添加碰撞盒并调整到合适大小
Add Component->Box Collider 2D
2D碰撞盒和2D刚体详细讲解请见 官方手册2D物理系统参考。
(3)添加碰撞检测
在Enemmy_Skeleton下创建空物体并重命名为GroundCheck
右键->Create Empty->重命名为GroundCheck
将GroundCheck拖入并增大GroundCheckDistance。将GroundCheck移到合适的位置,这次我们把它放在骷髅身前。
将Enemy_Skeleton直接拖入墙体检测,调整WallCheckDistance到合适的值。
WhatIsGround选择Ground
四、实现空闲状态和移动状态
(1)修改构造函数
上一节中我们创建了SkeletonIdleState和SkeletonMoveState两个脚本
我们希望在两个脚本中获取Enemy_Skeleton的实例,并调用它的某些变量。于是我们添加变量穿入骷髅小怪的实例,同时在构造函数里为它赋值。
两个脚本中构造函数分别改为
private Enemy_Skeleton enemy;
public SkeletonIdleState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy,string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName)
{
enemy = _enemy;
}
private Enemy_Skeleton enemy;
public SkeletonMoveState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy, string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName)
{
enemy = _enemy;
}
同时在Enemy_Skeleton中创建状态的函数也要做相应修改。
protected override void Awake()
{
base.Awake();
idleState = new SkeletonIdleState(stateMachine,this,this,"Idle");
moveState = new SkeletonMoveState(stateMachine, this,this, "Move");
}
这里的修改比较令人费解。考虑了一下最后我的理解是:
我们希望在两个脚本中获取Enemy_Skeleton的实例。但在定义EnemyState时,考虑到它适用于所有类型的敌人,所以使用了敌人基类Enemy。
首先我们要了解一些C#继承的相关知识,更具体的可以自行了解。在这里我们需要知道,子类实例化时会先执行父类的构造函数,再执行子类的构造函数。父类有了带参数的构造函数,子类的构造函数也可以不和父类的构造函数一样。但是如果父类定义了带参数的构造函数同时没有无参重载,那么在子类中必须对父类的带参数的构造进行赋值。
这就导致由它派生的两个脚本中,构造函数必须有敌人基类这个变量。如果想获取不同敌人独有的变量和函数,就只能再添加一个参数传具体的敌人类,即这里的Enemy_Skeleton。
(2)实现空闲状态
空闲状态的实现思路与Player基本一致,但略有不同的是骷髅处于来回移动的状态,并且在移动间隙会站一会儿再继续移动。
我们在Enemy中添加两个变量,一个是移动速度moveSpeed,一个是骷髅停顿的时间idleTime。
[Header("Move Info")]
public float moveSpeed = 1.5f;
public float idleTime = 2.0f;
SkeletonIdleState代码如下:
//SkeletonIdleState:骷髅空闲状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SkeletonIdleState : EnemyState
{
private Enemy_Skeleton enemy;
public SkeletonIdleState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy,string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName)
{
enemy = _enemy;
}
public override void Enter()
{
base.Enter();
stateTimer = enemy.idleTime;
}
public override void Exit()
{
base.Exit();
}
public override void Update()
{
base.Update();
if (stateTimer < 0)
stateMachine.ChangeState(enemy.moveState);
}
}
(2)实现移动状态
我们要在移动状态中设置骷髅的速度。
在骷髅碰到墙壁或者碰到平台边缘时翻转并转为空闲状态停住一会儿。
代码如下:
//SkeletonMoveState:骷髅移动状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SkeletonMoveState : EnemyState
{
private Enemy_Skeleton enemy;
public SkeletonMoveState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy, string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName)
{
enemy = _enemy;
}
public override void Enter()
{
base.Enter();
}
public override void Exit()
{
base.Exit();
}
public override void Update()
{
base.Update();
enemy.SetVelocity(enemy.moveSpeed*enemy.facingDir,enemy.rb.velocity.y);
if(!enemy.isGroundDetected() || enemy.isWallDetected())
{
enemy.Flip();
stateMachine.ChangeState(enemy.idleState);
}
}
}
效果如下:
总结 完整代码
Enemy
添加移动信息
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : Entity
{
[Header("Move Info")]
public float moveSpeed = 1.5f;
public float idleTime = 2.0f;
public EnemyStateMachine stateMachine;
protected override void Awake()
{
base.Awake();
stateMachine = new EnemyStateMachine();
}
protected override void Update()
{
base.Update();
stateMachine.currentState.Update();
}
}
Enemy_Skeleton
修改创建状态的语句。
//Enemy_Skeleton:骷髅敌人
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy_Skeleton : Enemy
{
#region 状态
public SkeletonIdleState idleState { get; private set; }
public SkeletonMoveState moveState { get; private set; }
#endregion
protected override void Awake()
{
base.Awake();
idleState = new SkeletonIdleState(stateMachine,this,this,"Idle");
moveState = new SkeletonMoveState(stateMachine, this,this, "Move");
}
protected override void Start()
{
base.Start();
stateMachine.Initialize(idleState);
}
protected override void Update()
{
base.Update();
}
}
SkeletonIdleState
设置计时器初值,实现到移动状态的切换。
//SkeletonIdleState:骷髅空闲状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SkeletonIdleState : EnemyState
{
private Enemy_Skeleton enemy;
public SkeletonIdleState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy,string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName)
{
enemy = _enemy;
}
public override void Enter()
{
base.Enter();
stateTimer = enemy.idleTime;
}
public override void Exit()
{
base.Exit();
}
public override void Update()
{
base.Update();
if (stateTimer < 0)
stateMachine.ChangeState(enemy.moveState);
}
}
SkeletonMoveState
设置骷髅的速度,实现到空闲状态的切换
/SkeletonMoveState:骷髅移动状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SkeletonMoveState : EnemyState
{
private Enemy_Skeleton enemy;
public SkeletonMoveState(EnemyStateMachine _stateMachine, Enemy _enemyBase, Enemy_Skeleton _enemy, string _animBoolName) : base(_stateMachine, _enemyBase, _animBoolName)
{
enemy = _enemy;
}
public override void Enter()
{
base.Enter();
}
public override void Exit()
{
base.Exit();
}
public override void Update()
{
base.Update();
enemy.SetVelocity(enemy.moveSpeed*enemy.facingDir,enemy.rb.velocity.y);
if(!enemy.isGroundDetected() || enemy.isWallDetected())
{
enemy.Flip();
stateMachine.ChangeState(enemy.idleState);
}
}
}