Unity类银河战士恶魔城学习总结(P181-182 Boss Bringer第一个boss)

发布于:2024-12-18 ⋅ 阅读:(119) ⋅ 点赞:(0)

教程源地址:https://www.udemy.com/course/2d-rpg-alexdev/

完结撒花!!!!!!

本章节制作了精英怪物死亡领主,拥有高额的伤害,和传送技能,召唤死亡触手技能

攻击有概率触发传送,之后召唤触手

Enemy_DeathBringer.cs

1. 传送功能

  • 目的:让 Boss 在指定范围内进行随机传送。
  • 逻辑:
    1. 在传送区域 (arena) 内随机生成一个位置。
    2. 检查该位置是否满足:
      • 在地面上(通过射线检测 GroundBelow())。
      • 周围没有玩家或障碍物(SomethingIsAround())。
    3. 如果位置不合适,递归重新查找。
  • 传送概率控制:
    • 使用 CanTeleport() 函数判断是否可以传送,基于随机概率决定。
    • 成功传送后,重置传送概率为默认值。

2. 施法功能

  • 目的:生成施法技能攻击玩家。
  • 逻辑
    1. 获取玩家当前位置,并计算偏移量。
    2. 在玩家位置附近生成施法技能(spellPrefab)。
    3. 通过冷却时间控制施法频率,使用 CanDoSpellCast() 函数判断是否可以施法。
using System.Collections;
using UnityEngine;

//2024.12.16
//完结撒花
public class Enemy_DeathBringer : Enemy
{

    public bool bossFightBegun;

    [Header("施法信息")]//Spell cast details
    [SerializeField] private GameObject spellPrefab;
    public int amountOfSpells;
    public float spellCooldown;

    public float lastTimeCast;
    [SerializeField] private float spellStateCooldown;
    [SerializeField] private Vector2 spellOffset;
    


    [Header("传送信息")]//Teleport details
    [SerializeField] private BoxCollider2D arena;
    [SerializeField] private Vector2 surroundingCheckSize;
    public float chanceToteleport;
    public float defaultChanceToTeleport =25 ;
    



    #region States
    public DeathBringerBattleState battleState { get; private set; }
    public DeathBringerAttackState attackState { get; private set; }
    public DeathBringerIdleState idleState { get; private set; }
    public DeathBringerDeadState deadState { get; private set; }
    public DeathBringerSpellCastState spellCastState { get; private set; }
    public DeathBringerTeleportState teleportState { get; private set; }
    #endregion

    protected override void Awake()
    {
        base.Awake();

        SetupDefaultFacingDir(-1);

        idleState = new DeathBringerIdleState(this, stateMachine, "Idle", this);

        battleState = new DeathBringerBattleState(this, stateMachine, "Move", this);
        attackState = new DeathBringerAttackState(this, stateMachine, "Attack", this);

        deadState = new DeathBringerDeadState(this, stateMachine, "Idle", this);
        spellCastState = new DeathBringerSpellCastState(this, stateMachine, "SpellCast", this);

        teleportState = new DeathBringerTeleportState(this, stateMachine, "Teleport", this);
       
    }

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

        stateMachine.Initialize(idleState);
    }

    public override void Die()
    {
        base.Die();

        stateMachine.ChangeState(deadState);
    }

    public void CastSpell()
    {
        Player player = PlayerManager.instance.player;

        float xOffset = 0;

        if(player.rb.velocity.x!= 0)
            xOffset = player.facingDir * spellOffset.x;

        Vector3 spellPostion = new Vector3(player.transform.position.x + player.facingDir * 2, player.transform.position.y + spellOffset.y);


        GameObject newSpell = Instantiate(spellPrefab ,spellPostion,Quaternion.identity);
        newSpell.GetComponent<DeathBringerSpell_Controller>().SetupSpell(stats);

    }


    public void FindPosition()
    {
        float x = Random.Range(arena.bounds.min.x + 3, arena.bounds.max.x - 3);
        float y = Random.Range(arena.bounds.min.y + 3, arena.bounds.max.y - 3);

        transform.position = new Vector3(x, y);
        transform.position = new Vector3(transform.position.x, transform.position.y - GroungdBelow().distance + (cd.size.y / 2));

        if(!GroungdBelow() || SomethingIsAround())
        {
            Debug.Log("寻找新的位置");
            FindPosition();
        }

    }


    private RaycastHit2D GroungdBelow() => Physics2D.Raycast(transform.position, Vector2.down, 100, whatIsGround);
    private bool SomethingIsAround() => Physics2D.BoxCast(transform.position, surroundingCheckSize, 0, Vector2.zero, 0, whatIsPlayer);



    protected override void OnDrawGizmos()
    {
        base.OnDrawGizmos();

        Gizmos.DrawWireCube(transform.position, surroundingCheckSize);
    }


    public bool CanTeleport()
    {
        if (Random.Range(0, 100) <= chanceToteleport)
        {
            chanceToteleport = defaultChanceToTeleport;
            return true;
        }

        return false;
    }

    public bool CanDoSpellCast()
    {
        if (Time.time >= lastTimeCast + spellStateCooldown)
        {
            return true;
        }
        else
            return false;
    }

}

DeathBringerSpell_Controller.cs

核心功能

1. 施法技能碰撞检测

  • 使用 Physics2D.OverlapBoxAll() 方法,基于一个 指定区域check.positionboxSize)检测是否有玩家碰到技能。
  • 检测的对象由 whatIsPlayer 层级掩码指定,确保只对玩家进行碰撞检测。

2. 施加伤害和击退效果

  • 遍历检测到的碰撞对象,如果对象包含 CharacterStats 组件:
    • 调用 Entity.SetupKnockbackDir(),设置击退方向。
    • 使用 myStats.DoDamage() 对玩家施加伤害。

3. 自我销毁

  • 当动画播放到特定帧时,通过 SelfDestory() 函数销毁技能对象,避免资源浪费。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DeathBringerSpell_Controller : MonoBehaviour
{

    [SerializeField] Transform check;
    [SerializeField] Vector2 boxSize;
    [SerializeField] private LayerMask whatIsPlayer;

    private CharacterStats myStats;


    public void SetupSpell(CharacterStats _stats)=> myStats = _stats;

    private void AnimationTrigger()
    {
        Collider2D[] colliders = Physics2D.OverlapBoxAll(check.position,boxSize,whatIsPlayer);

        foreach (var hit in colliders)
        {
            if (hit.GetComponent<CharacterStats>() != null)
            {
                hit.GetComponent<Entity>().SetupKnockbackDir(transform);
                myStats.DoDamage(hit.GetComponent<CharacterStats>());


            }
        }
    }


    private void OnDrawGizmos()=> Gizmos.DrawWireCube(check.position, boxSize);
    
    private void SelfDestory() => Destroy(gameObject);
}

DeathBringerAttackState.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DeathBringerAttackState : EnemyState
{
    private Enemy_DeathBringer enemy;
    public DeathBringerAttackState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName, Enemy_DeathBringer _enemy) : base(_enemyBase, _stateMachine, _animBoolName)
    {
        this.enemy = _enemy;
    }

    public override void Enter()
    {
        base.Enter();

        enemy.chanceToteleport += 5;//攻击增加传送概率
    }

    public override void Exit()
    {
        base.Exit();

        enemy.lastTimeAttack = Time.time;
    }

    public override void Update()
    {
        base.Update();

        enemy.SetZeroVelocity();

        if (triggerCalled)
        {
            if (enemy.CanTeleport())
                stateMachine.ChangeState(enemy.teleportState);
            else
                stateMachine.ChangeState(enemy.battleState);

        }
    }
}

DeathBringerBattleState.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DeathBringerBattleState : EnemyState
{
    private Enemy_DeathBringer enemy;
    private Transform player;
    private int moveDir;

    public DeathBringerBattleState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName, Enemy_DeathBringer _enemy) : base(_enemyBase, _stateMachine, _animBoolName)
    {
        this.enemy = _enemy;
    }

    public override void Enter()
    {
        base.Enter();

        
        player = PlayerManager.instance.player.transform;

        //if (player.GetComponent<PlayerStats>().isDead)
            //stateMachine.ChangeState(enemy.moveState);

    }


    public override void Update()
    {
        base.Update();



        if (enemy.IsPlayerDetected())
        {
            stateTimer = enemy.battleTime;

            if (enemy.IsPlayerDetected().distance < enemy.attackDistance)
            {
                if (CanAttack())
                    stateMachine.ChangeState(enemy.attackState);
                else
                    stateMachine.ChangeState(enemy.idleState);
            }
        }

        if (player.position.x > enemy.transform.position.x)
            moveDir = 1;
        else if (player.position.x < enemy.transform.position.x)
            moveDir = -1;


        if (enemy.IsPlayerDetected() && enemy.IsPlayerDetected().distance < enemy.attackDistance - .1f)
            return;

        if (Vector2.Distance(player.transform.position, enemy.transform.position) > 1)
            enemy.SetVelocity(enemy.moveSpeed * moveDir, rb.velocity.y);
        else
            enemy.SetZeroVelocity();

    }


    public override void Exit()
    {
        base.Exit();
    }

    private bool CanAttack()
    {
        if (Time.time >= enemy.lastTimeAttack + enemy.attackCoolDown)
        {
            enemy.attackCoolDown = Random.Range(enemy.minAttackCoolDown, enemy.maxAttackCoolDown);
            enemy.lastTimeAttack = Time.time;
            return true;
        }
        else
            return false;
    }
}

 DeathBringerSpellCastState.cs

using System.Collections;
using UnityEngine;

public class DeathBringerSpellCastState : EnemyState
{
    private Enemy_DeathBringer enemy;

    private int amountOfSpells;
    private float spellTimer;

    public DeathBringerSpellCastState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName, Enemy_DeathBringer _enemy) : base(_enemyBase, _stateMachine, _animBoolName)
    {
        this.enemy = _enemy;
    }


    public override void Enter()
    {
        base.Enter();

        amountOfSpells = enemy.amountOfSpells;
        spellTimer = .5f;
    }


    public override void Update()
    {
        base.Update();

        spellTimer -= Time.deltaTime;

        if(CanCast())   
            enemy.CastSpell();


        if (amountOfSpells <= 0)
            stateMachine.ChangeState(enemy.teleportState);
    }

    public override void Exit()
    {
        base.Exit();

        enemy.lastTimeCast = Time.time;
    }


    private bool CanCast()
    {
        if(amountOfSpells > 0 && spellTimer < 0)
        {
            amountOfSpells--;
            spellTimer = enemy.spellCooldown;
            return true;
        }
        return false;
    }
}

DeathBringerIdleState.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DeathBringerIdleState : EnemyState
{
    private Enemy_DeathBringer enemy;

    private Transform player;
    public DeathBringerIdleState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName, Enemy_DeathBringer _enemy) : base(_enemyBase, _stateMachine, _animBoolName)
    {
        this.enemy = _enemy;
    }

    public override void Enter()
    {
        base.Enter();

        stateTimer = enemy.idleTime;
        player = PlayerManager.instance.player.transform;

    }


    public override void Exit()
    {
        base.Exit();

        AudioManager.instance.PlaySFX(24, enemy.transform);
    }

    public override void Update()
    {
        base.Update();

        if( Vector2.Distance(player.transform.position, enemy.transform.position) < 7)
            enemy.bossFightBegun = true;


        if (Input.GetKeyDown(KeyCode.V))
        
            stateMachine.ChangeState(enemy.teleportState);

        if(stateTimer < 0 && enemy.bossFightBegun)
            stateMachine.ChangeState(enemy.battleState);


    }

}

Enemy_DeathBringerTrigger.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Enemy_DeathBringerTrigger : Enemy_AnimationTriggers
{
    private Enemy_DeathBringer enemydeathBringer => GetComponentInParent<Enemy_DeathBringer>();

    private void Relocate() => enemydeathBringer.FindPosition();

    private void MakeInvisivle() => enemydeathBringer.fx.MakeTransprent(true);
    private void MakeVisivle() => enemydeathBringer.fx.MakeTransprent(false);


}

 DeathBringerTeleportState.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DeathBringerTeleportState : EnemyState
{
    private Enemy_DeathBringer enemy;  
    public DeathBringerTeleportState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName, Enemy_DeathBringer _enemy) : base(_enemyBase, _stateMachine, _animBoolName)
    {
        this.enemy = _enemy;
    }

    public override void Enter()
    {
        base.Enter();

        enemy.stats.MakeInvincible(true);
    }

    public override void Update()
    {
        base.Update();

        if(triggerCalled)
        {
            if(enemy.CanDoSpellCast())
                stateMachine.ChangeState(enemy.spellCastState);
            else
                stateMachine.ChangeState(enemy.battleState);
        }
    }

    public override void Exit()
    {
        base.Exit();

        enemy.stats.MakeInvincible(false);
    }
}

DeathBringerDeadState.cs

using UnityEngine;

public class DeathBringerDeadState : EnemyState
{
    private Enemy_DeathBringer enemy;
    public DeathBringerDeadState(Enemy _enemyBase, EnemyStateMachine _stateMachine, string _animBoolName, Enemy_DeathBringer _enemy) : base(_enemyBase, _stateMachine, _animBoolName)
    {
        this.enemy = _enemy;
    }

    public override void Enter()
    {
        base.Enter();

        enemy.anim.SetBool(enemy.lastAnimBoolName, true);
        enemy.anim.speed = 0;
        enemy.cd.enabled = false;//禁止启动碰撞器,死后升天然后掉到地下

        stateTimer = .18f;//死的时候升天的时间
    }

    public override void Update()
    {
        base.Update();

        if (stateTimer > 0)
            rb.velocity = new Vector2(0, 10);//死后升天
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到