敌人的状态与玩家类似,同样需要敌人,敌人状态和管理状态的状态机,让我们创建三个脚本:
Enemy,EnemyState,EnemyStateMachine。
public class EnemyState //不需要继承,因为它将作为父类
{
protected EnemyStateMachine enemyStateMachine;//状态机
protected Enemy enemy;//敌人对象
private string animBoolName;//动画名称
protected bool triggerCalled;//动画触发器
protected float stateTimer;//计时器
public EnemyState(EnemyStateMachine _enemyStateMachine, Enemy _enemy, bool _animBoolName)//初始化
{
this.enemyStateMachine = _enemyStateMachine;
this.enemy = _enemy;
this.animBoolName = _animBoolName;
}
public virtual void Enter()//进入状态
{
triggerCalled = false;//触发器进入时为否
enemy.anim.SetBool("animBoolName", true);
//播放动画 }
public virtual void Exit()
{
enemy.anim.SetBool("animBoolName", false);//结束动画
}
public virtual void Update()
{
stateTimer -= Time.deltaTime;//计时器更新
}
}
public class EnemyStateMachine : MonoBehaviour
{
public EnemyState currentState { get; private set; }//当前状态
public void Initialize(EnemyState _enemyState)//初始化状态
{
currentState = _enemyState; ;
}
public void ChangeState(EnemyState new_enemyState)//改变状态
{
currentState.Exit();
currentState = new_enemyState;
currentState.Enter();
}
}
public class Enemy : MonoBehaviour
{
public Rigidbody2D rb;
public Animator anim;//组件
public EnemyStateMachine enemyStateMachine;
public void Awake()
{
enemyStateMachine = new EnemyStateMachine();
}
public void Update()
{
enemyStateMachine.currentState.Update();//调用当前状态的更新
}
}
让我们先思考一下玩家的那些功能是敌人也有的
比如说刚体和2D组件,翻转的检测,地面墙的碰撞检测。
从Player脚本中将那些功能的代码剪贴过来,注意修改属性,最好都为protected virtual或者public virtual,以便重载修改
[Header("Collision info")]//碰撞的信息
[SerializeField] protected Transform groundCheck;
[SerializeField] protected float groundCheckDistance;
[SerializeField] protected Transform wallCheck;
[SerializeField] protected float wallCheckDistance;
[SerializeField] protected LayerMask whatIsground;
public int facingDir { get; private set; } = 1;//翻转的信息
private bool facingRight = true;
#region compenent//组件
public Animator anim { get; private set; }
public Rigidbody2D rb { get; private set; }
#endregion
protected virtual void Awake()
{
}
protected virtual void Start()
{
anim = GetComponentInChildren
rb = GetComponent
}
protected virtual void Update()
{
}
#region Collision//碰撞检测
public virtual bool isGroundDetected() => Physics2D.Raycast(groundCheck.position, Vector2.down, groundCheckDistance, whatIsground);
public virtual bool isWallDetected() => Physics2D.Raycast(wallCheck.position, Vector2.right * facingDir, wallCheckDistance, whatIsground);
protected virtual void OnDrawGizmos()
{
Gizmos.DrawLine(groundCheck.position, new Vector3(groundCheck.position.x, groundCheck.position.y - groundCheckDistance));
Gizmos.DrawLine(wallCheck.position, new Vector3(wallCheck.position.x + wallCheckDistance * facingDir, wallCheck.position.y));
}
#endregion
#region Flip//翻转的函数
public virtual void Flip()
{
facingDir = -facingDir;
facingRight = !facingRight;
transform.Rotate(0, 180, 0);
}
public virtual void FlipContorller(float _x)
{
if (_x > 0 && !facingRight)
{
Flip();
}
else if (_x < 0 && facingRight)
{
Flip();
}
}
#endregion
#region Velocity//速度
public virtual void ZeroVelocity() => rb.velocity = new Vector2(0, 0);
public virtual void SetVelocity(float _xVelocity, float _yVelocity)
{
rb.velocity = new Vector2(_xVelocity, _yVelocity);
FlipContorller(_xVelocity);
}
#endregion
在Awake(),Start()和Update()中添加:
base.Awake();
base.Start();
base.Update();
让Player和Enemy都继承自Entity。
不同的敌人有不同的状态,我们可以把敌人再作为一个父类,而骷髅敌人继承自敌人。
public class EnemySkeleton : Entity
{
protected override void Awake()
{
base.Awake();
}
protected override void Start()
{
base.Start();
}
protected override void Update()
{
base.Update();
}
}
创建一个敌人对象,挂载刚体组件,box碰撞器和EnemySkeleton脚本,如图所示:
再创建子对象确保可以看到地面检测线和墙检测线。
敌人的移动与闲置和玩家不同,它会移动一段距离后闲置一段时间,并且碰墙和检测到前方无地面时会自动翻转。
创建SkeletonIdleState和SkeletonMoveState脚本,重构并重写,当然这里的构造函数我们需要修改。
将enemy变量名修改为enemyBase,意思是敌人基类,它有敌人共同的功能
private EnemySkeleton enemy;//定义了骷髅敌人
public SkeletonIdleState(EnemyStateMachine _enemyStateMachine, Enemy _enemyBase, string _animBoolName, EnemySkeleton enemy) : base(_enemyStateMachine, _enemyBase, _animBoolName)//修改将敌人基类和骷髅敌人全部初始化
{
this.enemy = enemy;
}
public override void Enter()
{
base.Enter();
}
public override void Exit()
{
base.Exit();
}
public override void Update()
{
base.Update();
}
接下实现闲置和移动的代码
[Header("Move info")]//添加移动速度和待机时间,这是大部分敌人都有的属性,所以放在基类
public float moveSpeed;
public float IdleTime;
#region States
public SkeletonIdleState skeletonIdleState;//定义状态
public SkeletonMoveState skeletonMoveState;
#endregion
protected override void Awake()
{
base.Awake();
skeletonIdleState = new SkeletonIdleState(enemyStateMachine, this, "Idle", this);
skeletonMoveState = new SkeletonMoveState(enemyStateMachine, this, "Move", this);
}
protected override void Start()
{
base.Start();
enemyStateMachine.Initialize(skeletonIdleState);//初始化处于待机状态
}
Enter()中:
stateTimer = enemy.IdleTime;//计时器设置为待机时间
Update()中:
if(stateTimer<0)
{
enemy.enemyStateMachine.ChangeState(enemy.skeletonMoveState);
}//待机结束进入移动状态
Update()中:
enemy.SetVelocity(enemy.moveSpeed * enemy.facingDir, enemy.rb.velocity.y);//设置速度
if(enemy.isWallDetected()||!enemy.isGroundDetected())//地面墙的检测
{
enemy.Flip();
enemy.enemyStateMachine.ChangeState(enemy.skeletonIdleState);
}