游戏开发小结——Boss AI

游戏开发小结——Boss AI

目标:为我们的游戏创建一个 Boss。

让我们的游戏有一个 Boss 系统,这样当我们的玩家到达第 5 波时,它将产生我们的新 Boss。

在这里插入图片描述Boss sprite.
https://free-game-assets.itch.io/free-enemy-spaceship-2d-sprites-pixel-art

因此,我们将首先创建一个新类来处理我们的 BossBehavior,而不是使用我们的敌人类。
与我们的敌人不同,我们的Boss会在屏幕外生成,并慢慢进入屏幕。在此期间,他将刀枪不入。
private bool _isInvulnerable = true;
private bool _readyForCombat = false;
private void Update()
{
    if(_readyForCombat)
        CalculateMovement();
    else
    {
        transform.Translate(_speed / 2f * Time.deltaTime * Vector3.down);
        if (transform.position.y <= _maxYPosition)
        {
            _readyForCombat = true;
            _isInvulnerable = false;
        }
    }
}
我们在这里所做的就是当我们的 Boss 创建时,它有一个 bool readyForCombat ,允许我们慢慢地将他带入屏幕。他在这段时间内是无敌的,这是由我们的 isInvulnerable 变量处理的。
现在让我们检查一下我们将如何让我们的Boss采取行动。与我们的敌人不同,我想要的是 Boss 向侧面移动,并试图找到玩家。当玩家位于 Boss 面前时,它会向下移动直到屏幕末端。一旦到达那里,它就会向随机方向发射激光,使玩家难以逃脱。
然后他就回到自己的位置并再次开始侧向移动。每次他这样做,他就会变得更快。
private enum BossState
{
    Sideways,
    Down
}
private BossState _state;
private float _speed = 5f;
private int _direction = 1;
private float _maxYPosition = 5f;
private float _minYPosition = -3f;
private float _maxXPosition = 9f;
private float _minXPosition = -9f;
private void CalculateMovement()
{
    switch (_state)
    {
        case BossState.Sideways:
            transform.Translate(_speed * _speedMultiplier * _direction * Time.deltaTime * Vector3.right);
            if(transform.position.x >= _maxXPosition || transform.position.x <= _minXPosition)
            {
                ChangeDirection();
            }
            CheckPlayerPosition();
            break;
        case BossState.Down:
            transform.Translate(_speed * 1.5f * _speedMultiplier * _direction * Time.deltaTime * Vector3.down);
            if (transform.position.y <= _minYPosition)
            {
                ShootLasers();
                ChangeDirection();
            }
            if(transform.position.y >= _maxYPosition)
            {
                IncreaseSpeed();
                ChangeState(BossState.Sideways);
            }
            break;
    }
}
那么让我们检查一下我们在做什么。我们有一个枚举,表示 Boss 当前所处的状态。它可以横向移动,也可以向下移动。
如果我们要横向移动,我们会根据一个整数方向移动,这将使我们横向移动。当我们达到基于 maxXPosition 或 minXPosition 的屏幕限制时,我们只需改变方向即可。移动后,我们检查玩家是否在我们前面,我们稍后将创建该方法。
如果我们要向下,我们只需向下移动并根据 minYPosition 检查 Boss 是否已达到屏幕的极限。
如果这是真的,我们会发射激光(我们稍后将创建激光),并改变方向,这样我们就可以向后移动到原来的位置。
如果这是错误的,我们只需检查我们是否回到了 maxYPosition,然后我们只需改变我们的状态并增加我们的移动速度。
private void IncreaseSpeed()
{
    _speedMultiplier += 0.05f;
}

private void ChangeDirection()
{
    _direction *= -1;
}

private void ChangeState(BossState newState)
{
    _state = newState;
}
我们只是创建了基本方法来帮助我们处理 Boss 速度的增加、改变方向和状态。
private void CheckPlayerPosition()
{
    if(_player != null)
    {
        Vector2 toPlayer = _player.transform.position - transform.position;
        float angle = Vector2.SignedAngle(-transform.up, toPlayer.normalized);
        float alignmentThreshold = 5f;
        float verticalAlignment = Mathf.Abs(toPlayer.x) / Mathf.Abs(toPlayer.y);

        if (Mathf.Abs(angle) < alignmentThreshold && verticalAlignment < 1f)
            AttackPlayer();
    }
}
这是我们用来检查玩家位置的方法。我们检查玩家是否不为空,如果玩家不为空,我们只需执行以下操作:
toPlayer:计算从boss到玩家的向量。
angle角度:计算 Boss 向上方向与玩家的归一化向量之间的带符号角度。负的transform.up用于检查敌人的前方(downwards下方)。
VerticalAlignment:计算垂直对齐方式,表示玩家相对于 Boss 的垂直对齐方式。
if 语句:检查玩家向下移动的条件,考虑角度对齐和垂直对齐。当条件满足时,我们只需调用 AttackPlayer 方法即可。
private void AttackPlayer()
{
    ChangeState(BossState.Down);
    _direction = 1;
}
我们只需将状态更改为 Down 并确保方向为 1,这样我们就向下走。
private float _laserYPosition = 7.5f;
private void ShootLasers()
{
    Vector2[] positions = new Vector2[] 
    { new(-9.0f, -5f), new(-4.5f, -0.5f), new(0f, 4.5f), new(5.0f, 9.0f) };

    foreach (Vector2 laserPosition in positions)
    {
        float randomX = Random.Range(laserPosition.x, laserPosition.y);
        Vector3 laserSpawnPosition = new(randomX, _laserYPosition, 0.0f);
        Instantiate(_laserPrefab, laserSpawnPosition, Quaternion.identity);
    }
}
这是我们发射激光的方法。因此,当我们到达屏幕底部时,我们将发射随机落在玩家身上的激光。
我们的向量数组包含一对向量2,其中包含每个激光的位置。您可以看到每个位置都有屏幕的特定部分,这意味着激光将始终落在屏幕的大部分区域,而不是随机落下。这样我们就可以确保覆盖很大一部分,让玩家很难逃脱。
在 foreach 循环中,我们简单地为激光器的 X 分配一个随机位置,该位置受到向量值的限制。然后我们在生成的位置上实例化激光。
最后,我们想要确保当我们的Boss 受到伤害时,我们不是能够持续受到攻击,而是简单地轻弹我们的Boss ,表明他是无敌的。
private IEnumerator BossInvulnerabilityRoutine()
{
    _isInvulnerable = true;
    for (int i = 3; i >= 0; i--)
    {
        _spriteTransform.gameObject.SetActive(false);
        yield return new WaitForSeconds(0.17f);
        _spriteTransform.gameObject.SetActive(true);
        yield return new WaitForSeconds(0.17f);
    }
    _isInvulnerable = false;
}
这样,当我们的 Boss 受到伤害时,我们只需调用此例程即可确保玩家可以看到 Boss 在短时间内不会再次受到伤害。
private int _health = 10;
public void TakeDamage()
{
    if (_isInvulnerable)
        return;

    _health -= 1;
    StartCoroutine(BossInvulnerabilityRoutine());

    if (_health <= 0)
    {
        Instantiate(_explosionPrefab, transform.position, Quaternion.identity);
        Destroy(this);
    }
}

private void OnTriggerEnter2D(Collider2D other)
{
    if (other.CompareTag("PlayerLaser"))
    {
        Destroy(other.gameObject);
        TakeDamage();
    }
    else if (other.CompareTag("Player"))
    {
        if (_player != null)
            _player.TakeDamage();
        TakeDamage();
    }
}
这是我们的最后两种方法。其中一种允许 Boss 受到伤害,降低他的生命值并启动协程以使其刀枪不入。
我们的 TakeDamage 方法检查 Boss 是否无敌,如果是,该方法将返回而不采取进一步操作。否则,它会将健康值降低 1 并启动我们的协程。当他没有生命值时,我们会引发爆炸并摧毁我们的老板。
我们的 OnTriggerEnter2D 只是检查我们是否与玩家激光或玩家发生碰撞,以便在需要时摧毁碰撞的激光并损坏玩家。

在这里插入图片描述我们boss的日常。
在这里插入图片描述

现在我们的游戏中有一个boss了!到达第 5 波时会产生这个敌人,这会让我们的玩家变得更加困难!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小云同志你好

谁能书阁下,白首太玄经

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值