学习游戏制作记录(敌人的状态机,敌人和玩家的共同继承以及实现敌人的移动和待机)7.20

1.敌人的状态机

敌人的状态与玩家类似,同样需要敌人,敌人状态和管理状态的状态机,让我们创建三个脚本:

Enemy,EnemyState,EnemyStateMachine。

EnemyState脚本:

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;//计时器更新
    }
}

EnemyStateMachine脚本:

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();
    }
}

简单的Enemy脚本,后面将使用继承制作:

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();//调用当前状态的更新
    }
}

2.为敌人和玩家创建继承

让我们先思考一下玩家的那些功能是敌人也有的

比如说刚体和2D组件,翻转的检测,地面墙的碰撞检测。

创建一个Entity脚本(父类):

从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

Player脚本:

在Awake(),Start()和Update()中添加:
base.Awake();

base.Start();

base.Update();

让Player和Enemy都继承自Entity。

3.实现敌人的闲置和移动状态

不同的敌人有不同的状态,我们可以把敌人再作为一个父类,而骷髅敌人继承自敌人。

创建EnemySkeleton脚本,继承并重写方法:

public class EnemySkeleton : Entity
{
    protected override void Awake()
    {
        base.Awake();
    }

    protected override void Start()
    {
        base.Start();
    }

    protected override void Update()
    {
        base.Update();
    }
}

创建一个敌人对象,挂载刚体组件,box碰撞器和EnemySkeleton脚本,如图所示:

学习游戏制作记录(敌人的状态机,敌人和玩家的共同继承以及实现敌人的移动和待机)7.20_第1张图片

再创建子对象确保可以看到地面检测线和墙检测线。

接下来我们创建闲置动画和移动动画,并添加参数:
学习游戏制作记录(敌人的状态机,敌人和玩家的共同继承以及实现敌人的移动和待机)7.20_第2张图片

敌人的移动与闲置和玩家不同,它会移动一段距离后闲置一段时间,并且碰墙和检测到前方无地面时会自动翻转。

创建SkeletonIdleState和SkeletonMoveState脚本,重构并重写,当然这里的构造函数我们需要修改。

让我们先修改一下Enemy脚本:

将enemy变量名修改为enemyBase,意思是敌人基类,它有敌人共同的功能

再以SkeletonIdleState脚本为例:

    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();
    }

接下实现闲置和移动的代码

Enemy脚本:

    [Header("Move info")]//添加移动速度和待机时间,这是大部分敌人都有的属性,所以放在基类
    public float moveSpeed;
    public float IdleTime;

EnemySkeleton脚本:

    #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);//初始化处于待机状态
    }

SkeletonIdleState脚本:

Enter()中:

stateTimer = enemy.IdleTime;//计时器设置为待机时间

Update()中:

        if(stateTimer<0)
        {
            enemy.enemyStateMachine.ChangeState(enemy.skeletonMoveState);
        }//待机结束进入移动状态

SkeletonMoveState脚本:


Update()中:
 

        enemy.SetVelocity(enemy.moveSpeed * enemy.facingDir, enemy.rb.velocity.y);//设置速度

        if(enemy.isWallDetected()||!enemy.isGroundDetected())//地面墙的检测
        {
            enemy.Flip();
            enemy.enemyStateMachine.ChangeState(enemy.skeletonIdleState);
        }

演示:学习游戏制作记录(敌人的状态机,敌人和玩家的共同继承以及实现敌人的移动和待机)7.20_第3张图片

你可能感兴趣的:(学习,游戏,unity,c#)