Gameplay.BossController - robblofield/TomeboundDocs GitHub Wiki

Gameplay.BossController

Author: Shahrzad Beevis
Last Updated: 14/05/2024

Overview

A script that manages the behaviour and state transitions of a boss character in game. It encapsulates various functionalities such as health management, state handling, input processing and interactions with the environment.

Dependencies

N/A

Contents

  • Use case
  • Breakdown of code
    • Using Statements
    • Enum Declaration
    • Class Declaration
    • Variables
    • Method/Start()
    • Method/Update()
    • Method/HandleStateLogic()
    • Method/HandleStateSwitchInput()
    • Method/DamageBoss(int damage)
    • Method/OnTriggerEnter(Collider other)
    • Method/TransitionToTransformation()
    • Method/TransitionToPhase1Bomberman()
    • Method/TransitionToPhase1Confused()
    • Method/TransitionToPhase2())
    • Method/TransitionToDestruction()
    • Method/TransitionToPhase3LArmSmash()
    • Method/TransitionToPhase3RArmSmash()
    • Method/TransitionToPhase3HeadSmash()
    • Method/TransitionToDefeated()
  • Future Expansion
  • Full Code Reference

Use case

The script manages the behaviour of a boss character in a Unity game, controlling its states (e.g. Patrolling, Transformation), health, damage, animations, and transitions between states based on conditions like health thresholds and user input. It also integrates with dialogue events and handles debugging and error reporting.

Breakdown of Code

Using Statements

using UnityEngine;

Enum Declaration

public enum BossState
{
    Patrolling,
    Transformation,
    Phase1_Bomberman,
    Phase1_Confused,
    Phase2,
    Destruction,
    Phase3_Arm1Smash,
    Phase3_Arm2Smash,
    Phase3_HeadSmash,
    Defeated
}

This is an enumeration named BossState, defining various states the boss character can be in during gameplay. Each state represents a distinct behaviour or phase of the boss, such as 'Patrolling", 'Transformation', 'Defeated', etc. These states are used elsewhere in the script to manage the boss's actions, animations, and transitions between different phases throughout the game.

Class Declaration

using TMPro;
using UnityEngine;

Variables

public BossState currentState = BossState.Patrolling;

[SerializeField] private int maxHealth = 120;
[SerializeField] private int currentHealth;

[SerializeField] private int transformThreshold = 100;
[SerializeField] private int phase2Threshold = 80;
[SerializeField] private int phase3Threshold = 60;
[SerializeField] private int targetHit1Threshold = 40;
[SerializeField] private int targetHit2Threshold = 20;
[SerializeField] private int targetHit3Threshold = 0;

[SerializeField] private int waterDamage = 20;

[SerializeField] private int destructionDuration = 5;
[SerializeField] private int transformDuration = 5;
private float destructionTimer;

public TextMeshProUGUI bossStateText;

public GameObject patrollingBossModel;
public GameObject transformationBossModel;


private TileManager tileManager;
private GridBasedMovement1 playerMovement;
private FloorDetector floorDetector;
private Animator bossAnimator;

public BossState currentState = BossState.Patrolling:

This variable of type BossState represents the current state of the boss, which can be one of the several predefined states like 'Patrolling,' 'Transformation,' 'Phase1_Bomberman,' etc. detailed at the top of the script within the enum declaration.

[SerializeField] private int maxHealth = 120 & [SerializeField] private int currentHealth:

These variables define the maximum and current health points of the boss, respectively - both of which are editable within the inspector.

[SerializeField] private int transformThreshold = 100, [SerializeField] private int phase2Threshold = 80, [SerializeField] private int phase3Threshold = 60, [SerializeField] private int targetHit1Threshold = 40, [SerializeField] private int targetHit2Threshold = 20 & [SerializeField] private int targetHit3Threshold = 0:

These thresholds determine at which health points the boss transitions between different phases or behaviours.

[SerializeField] private int waterDamage = 20:

This variable defines the amount of damage the boss receives when it comes into contact with water.

[SerializeField] private int destructionDuration = 5 & [SerializeField] private int transformDuration = 5:

These variables specify the duration for the destruction and transformation phases.

private float destructionTimer:

This variable is used to keep track of the time remaining for the destruction phase.

public TextMeshProUGUI bossStateText:

This TMPro game object is assigned within the inspector and displays the current state the boss is in for debugging purposes.

public GameObject patrollingBossModel & public GameObject transformationBossModel:

These variables hold references to GameObjects representing models of the boss before and after transformation when the boss fight is triggered.

private TileManager tileManager, private GridBasedMovement1 playerMovement, private FloorDetector floorDetector & private Animator bossAnimator:

These variables hold references to other components and scripts used by the boss controller.

Start()

void Start()
{
    currentHealth = maxHealth;
    bossAnimator = GetComponent<Animator>();
    tileManager = FindObjectOfType<TileManager>();
    if (tileManager == null)
    {
        Debug.LogError("TileManager not found in the scene. Boss behaviour may not work correctly.");
    }
    playerMovement = FindObjectOfType<GridBasedMovement1>();
    floorDetector = FindObjectOfType<FloorDetector>();
}

In the Start method, the boss's current health is initialised, and references to necessary components and managers are obtained. If the TileManager is not found in the scene, an error message is logged.

Update()

void Update()
{
    HandleStateSwitchInput();
    HandleStateLogic();

    if (bossStateText != null)
    {
        bossStateText.text = "Boss State: " + currentState.ToString();
    }
}

The Update method is responsible for handling state switch input and logic. It also updates the boss's state debugging text if the bossStateText variable is assigned.

HandleStateLogic()

void HandleStateLogic()
{ 
    switch (currentState)
    {
        case BossState.Patrolling:
            bossAnimator.SetBool("isIdle", true);
            bossAnimator.SetBool("isSearching", false);
            if (currentHealth <= transformThreshold)
            {
                TransitionToTransformation();
            }
            break;
        case BossState.Transformation:
            DialogueController startDialogueController = GameObject.Find("DialogueStart").GetComponent<DialogueController>();
            if (startDialogueController != null && startDialogueController.dialogueEnded)
            {
                TransitionToPhase1Bomberman();
            }
            break;
        case BossState.Phase1_Bomberman:
            bossAnimator.SetBool("isIdle", true); 
            bossAnimator.SetBool("isSearching", false);
            if (!floorDetector.isOnFloor3)
            {
                TransitionToPhase1Confused();
            }
            break;

        case BossState.Phase1_Confused:
            bossAnimator.SetBool("isIdle", false);
            bossAnimator.SetBool("isSearching", true);
            if (currentHealth <= phase2Threshold)
            {
                TransitionToPhase2();
            }
            else if (floorDetector.isOnFloor3 && currentHealth > phase2Threshold)
            {
                currentState = BossState.Phase1_Bomberman;
                Debug.Log("Returned to Floor 3 before Phase 2 threshold. Back to Phase1_Bomberman state");
            }

            break;
        case BossState.Phase2:
            bossAnimator.SetBool("isIdle", true); 
            bossAnimator.SetBool("isSearching", false);
            if (currentHealth <= phase3Threshold)
            {
                TransitionToDestruction();
            }
            break;
        case BossState.Destruction:
            destructionTimer -= Time.deltaTime;
            if (destructionTimer <= 0f)
            {
                TransitionToPhase3LArmSmash();
            }
            break;
        case BossState.Phase3_Arm1Smash:
            if (currentHealth <= targetHit1Threshold)
            {
                TransitionToPhase3RArmSmash();
            }
            break;
        case BossState.Phase3_Arm2Smash:
            if (currentHealth <= targetHit2Threshold)
            {
                TransitionToPhase3HeadSmash();
            }
            break;
        case BossState.Phase3_HeadSmash:
            if (currentHealth <= 0)
            {
                TransitionToDefeated();
            }
            break;
        case BossState.Defeated:

            break;
    }
}

This method is responsible for managing the behaviour of the boss based on its current state (currentState). It utilises a switch statement to handle different cases, each corresponding to a specific boss state. Here's a breakdown of what it does for each case:

  • Patrolling: Sets animation parameters to indicate boss behaviour. If the boss's health falls below a certain threshold, it triggers a transition to the transformation state.

  • Transformation: Checks if a specific opening dialogue controller has ended its dialogue. If so, transitions to the next phase.

  • Phase1_Bomberman: Sets animation parameters for the corresponding behaviour. If the player is not on floor 3, it transitions to the confused state.

  • Phase1_Confused: Adjusts animation parameters to reflect the confused state. If the boss's health falls below a threshold, it transitions to phase 2. Additionally, if the player returns to floor 3 before reaching the phase 2 threshold, it reverts to the Phase1_Bomberman state.

  • Phase2: Sets animation parameters. If the boss's health falls below a certain threshold, it transitions to the destruction state.

  • Destruction: Decrements a timer and transitions to the next phase when the timer reaches zero.

  • Phase3_Arm1Smash, Phase3_Arm2Smash & Phase3_HeadSmash: Each phase checks the boss's health against a specific threshold and transitions to the next phase accordingly.

  • Defeated: No action is taken in this case, the state is simply set to defeated.

HandleStateSwitchInput()

void HandleStateSwitchInput()
{
    playerMovement.xLocked = false;
    playerMovement.yLocked = false;
    
    if (Input.GetKeyDown(KeyCode.Alpha1) && currentState != BossState.Patrolling)
    {
        bossAnimator.SetBool("isIdle", true);
        bossAnimator.SetBool("isSearching", false);
        currentState = BossState.Patrolling;
        currentHealth = 120;
        Debug.Log("Switched to Patrolling state");
    }
    else if (Input.GetKeyDown(KeyCode.Alpha2) && currentState != BossState.Transformation)
    {
        currentState = BossState.Transformation;
        currentHealth = 100;
        Debug.Log("Switched to Transformation state");
        TransitionToTransformation();
    }
    else if (Input.GetKeyDown(KeyCode.Alpha3) && currentState != BossState.Phase1_Bomberman)
    {
        bossAnimator.SetBool("isIdle", true);
        bossAnimator.SetBool("isSearching", false);
        currentState = BossState.Phase1_Bomberman;
        currentHealth = 100;
        Debug.Log("Switched to Phase 1 Bomberman state");
    }
    else if (Input.GetKeyDown(KeyCode.Alpha4) && currentState != BossState.Phase1_Confused)
    {
        bossAnimator.SetBool("isIdle", false);
        bossAnimator.SetBool("isSearching", true);
        currentState = BossState.Phase1_Confused;
        currentHealth = 100;
        Debug.Log("Switched to Phase 1 Confused state");
    }
    else if (Input.GetKeyDown(KeyCode.Alpha5) && currentState != BossState.Phase2)
    {
        bossAnimator.SetBool("isIdle", true);
        bossAnimator.SetBool("isSearching", false);
        currentState = BossState.Phase2;
        currentHealth = 80;
        Debug.Log("Switched to Phase 2 state");
        if (tileManager != null)
        {
            tileManager.StartAnimating();
        }
        else
        {
            Debug.LogError("TileManager reference is null. Boss cannot start tile animation.");
        }
    }
    else if (Input.GetKeyDown(KeyCode.Alpha6) && currentState != BossState.Destruction)
    {
        currentState = BossState.Destruction;
        currentHealth = 60;
        Debug.Log("Switched to Destruction state");
        if (tileManager != null)
        {
            tileManager.StopAnimating();
        }
        else
        {
            Debug.LogError("TileManager reference is null. Boss cannot stop tile animation.");
        }
        destructionTimer = destructionDuration;
    }
    else if (Input.GetKeyDown(KeyCode.Alpha7) && currentState != BossState.Phase3_Arm1Smash)
    {
        currentState = BossState.Phase3_Arm1Smash;
        currentHealth = 60;
        Debug.Log("Switched to Phase 3 Arm 1 Smash state");
    }
    else if (Input.GetKeyDown(KeyCode.Alpha8) && currentState != BossState.Phase3_Arm2Smash)
    {
        currentState = BossState.Phase3_Arm2Smash;
        currentHealth = 40;
        Debug.Log("Switched to Phase 3 Arm 2 Smash state");
    }
    else if (Input.GetKeyDown(KeyCode.Alpha9) && currentState != BossState.Phase3_HeadSmash)
    {
        currentState = BossState.Phase3_HeadSmash;
        currentHealth = 20;
        Debug.Log("Switched to Phase 3 Head Smash state");
    }
    else if (Input.GetKeyDown(KeyCode.Alpha0) && currentState != BossState.Defeated)
    {
        currentState = BossState.Defeated;
        TransitionToDefeated();
        currentHealth = 0;
        Debug.Log("Switched to Defeated state");
    }
    
    if (currentState == BossState.Transformation)
    {
        playerMovement.yLocked = true;
        playerMovement.xLocked = true;
    }
    else if (currentState == BossState.Patrolling || currentState == BossState.Defeated)
    {
        playerMovement.yLocked = true;
        playerMovement.xLocked = false;
    }
}

The HandleStateSwitchInput() function controls the manual switching of states for debugging purposes. Initially unlocking both x and y movements, it listens for specific key presses (0-9) corresponding to various states. Upon detecting a key press, it ensures that the boss's current state is not already the desired state before initiating a transition. Each transition involves setting animation parameters, updating the boss's current health, and logging the state change. Additionally, movement locking is adjusted based on the current state: if the boss is transforming, both x and y movements are locked, while in the Patrolling or Defeated state, only y movement is restricted as the player has returned to their usual restricted movement in these states.

DamageBoss(int damage)

public void DamageBoss(int damage)
{
    if (currentState == BossState.Destruction || currentState == BossState.Transformation)
    {
        Debug.Log("Boss is in Destruction or Transformation phase. Cannot take damage.");
        return;
    }

    currentHealth -= damage;
    Debug.Log("Boss took " + damage + " damage. Current health: " + currentHealth);

    if (currentState == BossState.Phase2 && currentHealth <= 0)
    {
        TransitionToDestruction();
    }
    else if (currentHealth <= 0)
    {
        TransitionToDefeated();
    }
}

The DamageBoss() function handles damage inflicted on the boss. It first checks if the boss is in a state where it cannot take damage, such as during Destruction or Transformation phases. If so, it logs a message and exits the function. Otherwise, it deducts the specified damage from the boss's current health and logs the updated health status. If the boss's health falls below zero while in Phase 2, it triggers a transition to the Destruction phase. If the boss's health reaches zero or below in any other state, it initiates a transition to the Defeated state, marking the boss as defeated. This method encapsulates the boss's response to damage, managing its health and state transitions accordingly.

OnTriggerEnter(Collider other)

void OnTriggerEnter(Collider other)
{
    if (other.CompareTag("Water"))
    {
        DamageBoss(waterDamage);
        Destroy(other.gameObject); 
    }
    
}

The OnTriggerEnter function is called when another collider enters the collider of the boss. It first checks if the entering collider has a tag 'Water'. If it does, it inflicts damage on the boss using the DamageBoss() function, with the amount of damage specified by the waterDamage variable. Additionally, it destroys the game object associated with the entering collider (presumably representing water) to indicate its interaction with the boss. This function is likely used to simulate the boss taking damage from water-related hazards in the game environment.

TransitionToTransformation()

void TransitionToTransformation()
{
    currentState = BossState.Transformation;
    DialogueController dialogueController = GameObject.Find("DialogueStart").GetComponent<DialogueController>();

    if (dialogueController != null)
    {
        dialogueController.StartDialogueFromScript();
    }
    else
    {
        Debug.LogError("DialogueController not found. Cannot start dialogue for transformation.");
    }

    Debug.Log("Boss entered Transformation phase");

    if (patrollingBossModel != null)
    {
        patrollingBossModel.SetActive(false);
    }

    if (transformationBossModel != null)
    {
        transformationBossModel.SetActive(true);
    }

}

This function transitions the boss to the Transformation state. It sets the current state to "Transformation" and attempts to find a DialogueController component attached to a game object named 'DialogueStart'. If found, it starts a dialogue from the script. If not found, it logs an error message. Then, it logs a message indicating that the boss has entered the Transformation phase. Finally, it deactivates the patrolling boss model and activates the transformation boss model if they exist.

TransitionToPhase1Bomberman()

 void TransitionToPhase1Bomberman()
 {
     currentState = BossState.Phase1_Bomberman;
     playerMovement.xLocked = false;
     playerMovement.yLocked = false;

     bossAnimator.SetBool("isIdle", true);
     bossAnimator.SetBool("isSearching", false);

     Debug.Log("Boss entered Phase 1 Part 1");
 }

This function transitions the boss to Phase 1 Bomberman state. It sets the current state to 'Phase1_Bomberman' and adjusts the movement of the player accordingly. It also sets animation parameters to reflect the boss's state. Additionally, it logs a message indicating that the boss has entered Phase 1 Part 1.

TransitionToPhase1Confused()

void TransitionToPhase1Confused()
{
    currentState = BossState.Phase1_Confused;
    bossAnimator.SetBool("isIdle", false);
    bossAnimator.SetBool("isSearching", true);
    Debug.Log("Boss entered Phase 1 Part 2");
}

This function transitions the boss to Phase 1 Confused state. It sets the current state to 'Phase1_Confused' and adjusts the boss's animation parameters to reflect confusion. It also logs a message indicating that the boss has entered Phase 1 Part 2.

TransitionToPhase2()

void TransitionToPhase2()
{
    currentState = BossState.Phase2;

    bossAnimator.SetBool("isIdle", true);
    bossAnimator.SetBool("isSearching", false);

    Debug.Log("Boss entered Phase 2");
    if (tileManager != null)
    {
        tileManager.StartAnimating();
    }
    else
    {
        Debug.LogError("TileManager reference is null. Boss cannot start tile animation.");
    }
}

This function transitions the boss to Phase 2 state. It sets the current state to 'Phase2' and starts tile animation if the tileManager is not null. It logs a message indicating that the boss has entered Phase 2.

TransitionToDestruction()

void TransitionToDestruction()
{
    currentState = BossState.Destruction;
    if (tileManager != null)
    {
        tileManager.StopAnimating();
    }
    else
    {
        Debug.LogError("TileManager reference is null. Boss cannot stop tile animation.");
    }
    Debug.Log("Boss entered Destruction phase");
    destructionTimer = destructionDuration;
}

This function transitions the boss to the Destruction state. It sets the current state to 'Destruction' and stops tile animation if the tileManager is not null. It logs a message indicating that the boss has entered the Destruction phase and resets the destruction timer.

TransitionToPhase3LArmSmash()

void TransitionToPhase3LArmSmash()
{
    currentState = BossState.Phase3_Arm1Smash;
    Debug.Log("Boss entered Phase 3.1");
}

This function transitions the boss to Phase 3 Arm 1 Smash state. It sets the current state to 'Phase3_Arm1Smash' and logs a corresponding message.

TransitionToPhase3RArmSmash()

void TransitionToPhase3RArmSmash()
{
    currentState = BossState.Phase3_Arm2Smash;
    Debug.Log("Boss entered Phase 3.2");
}

This function transitions the boss to Phase 3 Arm 2 Smash state. It sets the current state to 'Phase3_Arm2Smash' and logs a corresponding message.

TransitionToPhase3HeadSmash()

void TransitionToPhase3HeadSmash()
{
    currentState = BossState.Phase3_HeadSmash;
    Debug.Log("Boss entered Phase 3.3");
}

This function transitions the boss to Phase 3 Head Smash state. It sets the current state to 'Phase3_HeadSmash' and logs a corresponding message.

TransitionToDefeated()

void TransitionToDefeated()
{
    currentState = BossState.Defeated;
    Debug.Log("Boss defeated");

    if (bossStateText != null)
    {
        bossStateText.text = "Boss state: Defeated";
    }

    DialogueController dialogueController = GameObject.Find("DialogueEnd").GetComponent<DialogueController>();

    if (dialogueController != null)
    {
        dialogueController.StartDialogueFromScript();
    }
    else
    {
        Debug.LogError("DialogueController not found. Cannot start dialogue for defeated state.");
    }

    if (currentHealth <= targetHit3Threshold)
    {
        Destroy(this.gameObject);
    }
    playerMovement.yLocked = true;
    playerMovement.xLocked = false;
}

This function transitions the boss to the Defeated state. It sets the current state to 'Defeated', updates the UI if bossStateText is not null, starts a dialogue from the script if a DialogueController component is found on a game object named 'DialogueEnd', and destroys the boss game object if its health is below or equal to the third target hit threshold. Finally, it locks the player's movement in the y-axis and unlocks it in the x-axis.

Future Expansion

Phase 1 & 3 logic to be added when completed, along with any other required boss behaviours/animations.

Full Code Reference

using TMPro;
using UnityEngine;

public enum BossState
{
    Patrolling,
    Transformation,
    Phase1_Bomberman,
    Phase1_Confused,
    Phase2,
    Destruction,
    Phase3_Arm1Smash,
    Phase3_Arm2Smash,
    Phase3_HeadSmash,
    Defeated
}

public class BossController : MonoBehaviour
{
    #region Variables

    public BossState currentState = BossState.Patrolling;

    [Header("Health Settings")]
    [SerializeField] private int maxHealth = 120;
    [SerializeField] private int currentHealth;

    [Header("Thresholds")]
    [SerializeField] private int transformThreshold = 100;
    [SerializeField] private int phase2Threshold = 80;
    [SerializeField] private int phase3Threshold = 60;
    [SerializeField] private int targetHit1Threshold = 40;
    [SerializeField] private int targetHit2Threshold = 20;
    [SerializeField] private int targetHit3Threshold = 0;

    [Header("Damage Settings")]
    [SerializeField] private int waterDamage = 20;

    [Header("Timers")]
    [SerializeField] private int destructionDuration = 5;
    [SerializeField] private int transformDuration = 5;
    private float destructionTimer;

    [Header("Debug")]
    public TextMeshProUGUI bossStateText;

    [Header("Boss Models")]
    public GameObject patrollingBossModel;
    public GameObject transformationBossModel;


    private TileManager tileManager;
    private GridBasedMovement1 playerMovement;
    private FloorDetector floorDetector;
    private Animator bossAnimator;

    #endregion

    #region Unity Callbacks

    void Start()
    {
        currentHealth = maxHealth;
        bossAnimator = GetComponent<Animator>();
        tileManager = FindObjectOfType<TileManager>();
        if (tileManager == null)
        {
            Debug.LogError("TileManager not found in the scene. Boss behaviour may not work correctly.");
        }
        playerMovement = FindObjectOfType<GridBasedMovement1>();
        floorDetector = FindObjectOfType<FloorDetector>();
    }

    void Update()
    {
        HandleStateSwitchInput();
        HandleStateLogic();

        if (bossStateText != null)
        {
            bossStateText.text = "Boss State: " + currentState.ToString();
        }
    }

    #endregion

    #region State Handling
    void HandleStateLogic()
    { 
        switch (currentState)
        {
            case BossState.Patrolling:
                bossAnimator.SetBool("isIdle", true);
                bossAnimator.SetBool("isSearching", false);
                if (currentHealth <= transformThreshold)
                {
                    TransitionToTransformation();
                }
                break;
            case BossState.Transformation:
                DialogueController startDialogueController = GameObject.Find("DialogueStart").GetComponent<DialogueController>();
                if (startDialogueController != null && startDialogueController.dialogueEnded)
                {
                    TransitionToPhase1Bomberman();
                }
                break;
            case BossState.Phase1_Bomberman:
                bossAnimator.SetBool("isIdle", true); 
                bossAnimator.SetBool("isSearching", false);
                if (!floorDetector.isOnFloor3)
                {
                    TransitionToPhase1Confused();
                }
                break;

            case BossState.Phase1_Confused:
                bossAnimator.SetBool("isIdle", false);
                bossAnimator.SetBool("isSearching", true);
                if (currentHealth <= phase2Threshold)
                {
                    TransitionToPhase2();
                }
                else if (floorDetector.isOnFloor3 && currentHealth > phase2Threshold)
                {
                    currentState = BossState.Phase1_Bomberman;
                    Debug.Log("Returned to Floor 3 before Phase 2 threshold. Back to Phase1_Bomberman state");
                }

                break;
            case BossState.Phase2:
                bossAnimator.SetBool("isIdle", true); 
                bossAnimator.SetBool("isSearching", false);
                if (currentHealth <= phase3Threshold)
                {
                    TransitionToDestruction();
                }
                break;
            case BossState.Destruction:
                destructionTimer -= Time.deltaTime;
                if (destructionTimer <= 0f)
                {
                    TransitionToPhase3LArmSmash();
                }
                break;
            case BossState.Phase3_Arm1Smash:
                if (currentHealth <= targetHit1Threshold)
                {
                    TransitionToPhase3RArmSmash();
                }
                break;
            case BossState.Phase3_Arm2Smash:
                if (currentHealth <= targetHit2Threshold)
                {
                    TransitionToPhase3HeadSmash();
                }
                break;
            case BossState.Phase3_HeadSmash:
                if (currentHealth <= 0)
                {
                    TransitionToDefeated();
                }
                break;
            case BossState.Defeated:

                break;
        }
    }

    void HandleStateSwitchInput()
    {
        playerMovement.xLocked = false;
        playerMovement.yLocked = false;
        
        if (Input.GetKeyDown(KeyCode.Alpha1) && currentState != BossState.Patrolling)
        {
            bossAnimator.SetBool("isIdle", true);
            bossAnimator.SetBool("isSearching", false);
            currentState = BossState.Patrolling;
            currentHealth = 120;
            Debug.Log("Switched to Patrolling state");
        }
        else if (Input.GetKeyDown(KeyCode.Alpha2) && currentState != BossState.Transformation)
        {
            currentState = BossState.Transformation;
            currentHealth = 100;
            Debug.Log("Switched to Transformation state");
            TransitionToTransformation();
        }
        else if (Input.GetKeyDown(KeyCode.Alpha3) && currentState != BossState.Phase1_Bomberman)
        {
            bossAnimator.SetBool("isIdle", true);
            bossAnimator.SetBool("isSearching", false);
            currentState = BossState.Phase1_Bomberman;
            currentHealth = 100;
            Debug.Log("Switched to Phase 1 Bomberman state");
        }
        else if (Input.GetKeyDown(KeyCode.Alpha4) && currentState != BossState.Phase1_Confused)
        {
            bossAnimator.SetBool("isIdle", false);
            bossAnimator.SetBool("isSearching", true);
            currentState = BossState.Phase1_Confused;
            currentHealth = 100;
            Debug.Log("Switched to Phase 1 Confused state");
        }
        else if (Input.GetKeyDown(KeyCode.Alpha5) && currentState != BossState.Phase2)
        {
            bossAnimator.SetBool("isIdle", true);
            bossAnimator.SetBool("isSearching", false);
            currentState = BossState.Phase2;
            currentHealth = 80;
            Debug.Log("Switched to Phase 2 state");
            if (tileManager != null)
            {
                tileManager.StartAnimating();
            }
            else
            {
                Debug.LogError("TileManager reference is null. Boss cannot start tile animation.");
            }
        }
        else if (Input.GetKeyDown(KeyCode.Alpha6) && currentState != BossState.Destruction)
        {
            currentState = BossState.Destruction;
            currentHealth = 60;
            Debug.Log("Switched to Destruction state");
            if (tileManager != null)
            {
                tileManager.StopAnimating();
            }
            else
            {
                Debug.LogError("TileManager reference is null. Boss cannot stop tile animation.");
            }
            destructionTimer = destructionDuration;
        }
        else if (Input.GetKeyDown(KeyCode.Alpha7) && currentState != BossState.Phase3_Arm1Smash)
        {
            currentState = BossState.Phase3_Arm1Smash;
            currentHealth = 60;
            Debug.Log("Switched to Phase 3 Arm 1 Smash state");
        }
        else if (Input.GetKeyDown(KeyCode.Alpha8) && currentState != BossState.Phase3_Arm2Smash)
        {
            currentState = BossState.Phase3_Arm2Smash;
            currentHealth = 40;
            Debug.Log("Switched to Phase 3 Arm 2 Smash state");
        }
        else if (Input.GetKeyDown(KeyCode.Alpha9) && currentState != BossState.Phase3_HeadSmash)
        {
            currentState = BossState.Phase3_HeadSmash;
            currentHealth = 20;
            Debug.Log("Switched to Phase 3 Head Smash state");
        }
        else if (Input.GetKeyDown(KeyCode.Alpha0) && currentState != BossState.Defeated)
        {
            currentState = BossState.Defeated;
            TransitionToDefeated();
            currentHealth = 0;
            Debug.Log("Switched to Defeated state");
        }
        
        if (currentState == BossState.Transformation)
        {
            playerMovement.yLocked = true;
            playerMovement.xLocked = true;
        }
        else if (currentState == BossState.Patrolling || currentState == BossState.Defeated)
        {
            playerMovement.yLocked = true;
            playerMovement.xLocked = false;
        }
    }

    #endregion

    #region Damage Handling

    public void DamageBoss(int damage)
    {
        if (currentState == BossState.Destruction || currentState == BossState.Transformation)
        {
            Debug.Log("Boss is in Destruction or Transformation phase. Cannot take damage.");
            return;
        }

        currentHealth -= damage;
        Debug.Log("Boss took " + damage + " damage. Current health: " + currentHealth);

        if (currentState == BossState.Phase2 && currentHealth <= 0)
        {
            TransitionToDestruction();
        }
        else if (currentHealth <= 0)
        {
            TransitionToDefeated();
        }
    }

    void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Water"))
        {
            DamageBoss(waterDamage);
            Destroy(other.gameObject); 
        }
        
    }
    #endregion

    #region State Transitions
    void TransitionToTransformation()
    {
        currentState = BossState.Transformation;
        DialogueController dialogueController = GameObject.Find("DialogueStart").GetComponent<DialogueController>();

        if (dialogueController != null)
        {
            dialogueController.StartDialogueFromScript();
        }
        else
        {
            Debug.LogError("DialogueController not found. Cannot start dialogue for transformation.");
        }

        Debug.Log("Boss entered Transformation phase");

        if (patrollingBossModel != null)
        {
            patrollingBossModel.SetActive(false);
        }

        if (transformationBossModel != null)
        {
            transformationBossModel.SetActive(true);
        }

    }

    void TransitionToPhase1Bomberman()
    {
        currentState = BossState.Phase1_Bomberman;
        playerMovement.xLocked = false;
        playerMovement.yLocked = false;

        bossAnimator.SetBool("isIdle", true);
        bossAnimator.SetBool("isSearching", false);

        Debug.Log("Boss entered Phase 1 Part 1");
    }

    void TransitionToPhase1Confused()
    {
        currentState = BossState.Phase1_Confused;
        bossAnimator.SetBool("isIdle", false);
        bossAnimator.SetBool("isSearching", true);
        Debug.Log("Boss entered Phase 1 Part 2");
    }

    void TransitionToPhase2()
    {
        currentState = BossState.Phase2;

        bossAnimator.SetBool("isIdle", true);
        bossAnimator.SetBool("isSearching", false);

        Debug.Log("Boss entered Phase 2");
        if (tileManager != null)
        {
            tileManager.StartAnimating();
        }
        else
        {
            Debug.LogError("TileManager reference is null. Boss cannot start tile animation.");
        }
    }

    void TransitionToDestruction()
    {
        currentState = BossState.Destruction;
        if (tileManager != null)
        {
            tileManager.StopAnimating();
        }
        else
        {
            Debug.LogError("TileManager reference is null. Boss cannot stop tile animation.");
        }
        Debug.Log("Boss entered Destruction phase");
        destructionTimer = destructionDuration;
    }

    void TransitionToPhase3LArmSmash()
    {
        currentState = BossState.Phase3_Arm1Smash;
        Debug.Log("Boss entered Phase 3.1");
    }

    void TransitionToPhase3RArmSmash()
    {
        currentState = BossState.Phase3_Arm2Smash;
        Debug.Log("Boss entered Phase 3.2");
    }

    void TransitionToPhase3HeadSmash()
    {
        currentState = BossState.Phase3_HeadSmash;
        Debug.Log("Boss entered Phase 3.3");
    }

    void TransitionToDefeated()
    {
        currentState = BossState.Defeated;
        Debug.Log("Boss defeated");

        if (bossStateText != null)
        {
            bossStateText.text = "Boss state: Defeated";
        }

        DialogueController dialogueController = GameObject.Find("DialogueEnd").GetComponent<DialogueController>();

        if (dialogueController != null)
        {
            dialogueController.StartDialogueFromScript();
        }
        else
        {
            Debug.LogError("DialogueController not found. Cannot start dialogue for defeated state.");
        }

        if (currentHealth <= targetHit3Threshold)
        {
            Destroy(this.gameObject);
        }
        playerMovement.yLocked = true;
        playerMovement.xLocked = false;
    }


    #endregion
}
⚠️ **GitHub.com Fallback** ⚠️