流浪尸潮 封面

CH-01 信号

CH-02 实机

CH-03 制作

Core/Character/CharacterStates.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CharacterStates
{
    public enum MovementStates
    {
        Null,
        Idle,
        Walk,
        Run,
        Fall,
        Jump,
        DoubleJump,
        Dash,
        Climbing,
        Swimming
    }

    public enum CharacterCondition
    {
        Normal,
        Frozen,
        Paused,
        Dead,
        ControlledMovement,
        Stunned,
        Attacking,
    }
}
Core/Character/Core.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Core : MonoBehaviour
{
    public ControllerState controllerState;
    public ControllerParams defaultControllerParams;
    protected ControllerParams overrideControllerParams;


    public ControllerParams CurrentControllerParams
    {
        get
        {
            if (overrideControllerParams != null)
                return overrideControllerParams;
            if (defaultControllerParams != null)
                return defaultControllerParams;

            defaultControllerParams = new ControllerParams();
            return defaultControllerParams;
        }
    }

    public Vector3 Velocity;
    Vector3 positionOffset;
    Transform transform_;
    float currentGravity;
    bool gravityActive = true;

    public LayerMask GroundMask;
    public LayerMask OneWayPlatformMask;

    private CharacterController characterController;

    public GameObject StandingOn;

    void Awake()
    {
        Init();
    }

    private void Init()
    {
        transform_ = this.transform;
        controllerState = new ControllerState();
        characterController = GetComponent<CharacterController>();

        if (characterController == null)
        {
            characterController = gameObject.AddComponent<CharacterController>();
            characterController.height = 2.0f;
            characterController.radius = 0.5f;
        }

        if (defaultControllerParams == null)
        {
            defaultControllerParams = new ControllerParams();
        }
    }

    void Update()
    {
        EveryFrame();
    }

    protected virtual void EveryFrame()
    {
        ApplyGravity();
        PreLogic();
        CastRayBelow();
        MoveCharacter();
        SetStates();
        ResetParams();
    }

    void CastRayBelow()
    {
        float rayLenth = 1.2f;
        Vector3 rayOrigin = transform_.position;

        RaycastHit hit;
        if (Physics.Raycast(rayOrigin, Vector3.down, out hit, rayLenth, GroundMask))
        {
            controllerState.IsGrounded = true;
            StandingOn = hit.collider.gameObject;

            if (hit.distance < characterController.height / 2)
            {
                positionOffset.y = -hit.distance + characterController.height / 2;
            }
        }
        else
        {
            controllerState.IsGrounded = false;
            StandingOn = null;
        }
    }

    void PreLogic()
    {
        positionOffset = Velocity * Time.deltaTime;
        controllerState.WasGroundedLastFrame = controllerState.IsGrounded;
        controllerState.Reset();
    }

    void MoveCharacter()
    {
        if (characterController != null)
        {
            characterController.Move(positionOffset);
        }
        else
        {
            transform_.position += positionOffset;
        }
    }

    void ApplyGravity()
    {
        if (CurrentControllerParams == null)
        {
            return;
        }

        float gravity = CurrentControllerParams.Gravity;
        currentGravity = gravity;

        if (!controllerState.IsGrounded)
        {
            Velocity.y += currentGravity * Time.deltaTime;
            if (Velocity.y < 0) 
            {
                Velocity.y *= 0.98f;
            }
            Velocity.y = Mathf.Clamp(Velocity.y, CurrentControllerParams.MaxFallSpeed, CurrentControllerParams.MaxRiseSpeed);
        }
        else if (Velocity.y < 0)
        {
            Velocity.y = -2f;
        }
    }

    void SetStates()
    {
        if (!controllerState.WasGroundedLastFrame && controllerState.IsGrounded)
        {
            controllerState.JustGotGrounded = true;
        }
    }

    void ResetParams()
    {
        // �����ⲿ�����ȣ���ʱ��д
    }

    public void SetVelocityXZ(float x, float z)
    {
        Velocity.x = x;
        Velocity.z = z;
    }

    public void SetEnableGravity(bool enable)
    {
        gravityActive = enable;
    }

    public void AddVelocity(Vector3 velocity)
    {
        Velocity += velocity;
    }

    public void SetVelocity(Vector3 velocity)
    {
        Velocity = velocity;
    }
    public void SetYforce(float y)
    {
        Velocity.y = y;
    }
    public void Jump(float jumpHeight)
    {
        if (controllerState.IsGrounded)
        {
            if (CurrentControllerParams == null)
            {
                return;
            }

            Velocity.y = Mathf.Sqrt(jumpHeight * -2f * CurrentControllerParams.Gravity);
            controllerState.IsJumping = true;
        }
    }
}

[System.Serializable]
public class ControllerParams
{
    public float Gravity = -30f;
    public float MoveSpeed = 8f;
    public float JumpForce = 12f;
    public float AirControl = 0.8f;
    public float MaxFallSpeed = -50f;
    public float MaxRiseSpeed = 100f;
}

public class ControllerState
{
    public bool IsGrounded { get; set; }
    public bool WasGroundedLastFrame { get; set; }
    public bool JustGotGrounded { get; set; }
    public bool IsJumping { get; set; }

    public bool IsCollidingAbove { get; set; }
    public bool IsCollidingLeft { get; set; }
    public bool IsCollidingRight { get; set; }
    public bool TouchingLevelBounds { get; set; }

    public float LateralSlopeAngel { get; set; }
    public bool SlopeAngelOK { get; set; }
    public float DistanceToLeftColl { get; set; }
    public float DistanceToRightColl { get; set; }
    public float BelowSlopAngel { get; set; }
    public bool OnMovingPlatform { get; set; }

    public void Reset()
    {
        JustGotGrounded = false;
        IsJumping = false;
        IsCollidingAbove = false;
        IsCollidingLeft = false;
        IsCollidingRight = false;
        TouchingLevelBounds = false;
        LateralSlopeAngel = 0;
        SlopeAngelOK = false;
        DistanceToLeftColl = -1;
        DistanceToRightColl = -1;
        BelowSlopAngel = 0;
    }

}
Events/GameEvents.cs
using System;
using UnityEngine;

public static class GameEvents
{
    // ����¼�
    public static event Action<Character> OnPlayerSpawn;
    public static event Action<Character> OnPlayerDeath;
    public static event Action<float> OnPlayerCurrentHealthChanged;
    //���£���������/����
    public static event Action<float> OnPlayerMaxHealthPreChanged;
    public static event Action<float> OnPlayerMaxHealthTemChanged;
    public static event Action<int> OnPlayerMoveSpeedPreChanged;
    public static event Action<int> OnPlayerMoveSpeedTemChanged;
    public static event Action<int> OnPlayerAttackSpeedPreChanged;
    public static event Action<int> OnPlayerAttackSpeedTemChanged;
    public static event Action<int> OnPlayerAttackDamagePreChanged;
    public static event Action<int> OnPlayerAttackDamageTemChanged;
    public static event Action<int, int> OnPlayerExpChanged;
    public static event Action<int> OnPlayerLevelUp;

    // �����¼�
    public static event Action<Enemy> OnEnemySpawn;
    public static event Action<Enemy, bool> OnEnemyKilled; // bool isSpecial

    // Ч���¼�
    public static event Action<int> OnCurrencyChanged;
    public static event Action<int> OnFansChanged;
    public static event Action<int> OnBulletChanged;
    public static event Action<float> OnFlowChanged;
    public static event Action OnFlowWaveStarted;
    public static event Action OnFlowWaveEnded;
    public static event Action<int> OnGoldChanged;// ���

    // �˺��¼�
    public static event Action<float, GameObject> OnDamageDealt; // ����˺�ֵ��Ŀ��


    // ���Ա仯�¼�
    public static event Action<string, float, float> OnPlayerStatChanged; // ����������ֵ����ֵ

    // �������¼�
    public static event Action<int> OnViewerCountChanged;

    // �����¼�
    public static event Action<string> OnSystemUnlocked;
    public static event Action<string> OnContentUnlocked;
    public static event Action<string> OnAchievementUnlocked;
    public static event Action<float> OnTrafficChanged;// ����  
    // �̳��¼�
    public static event Action<string> OnTutorialStepStarted;
    public static event Action<string> OnTutorialStepCompleted;
    public static event Action OnTutorialCompleted;

    // �����¼�
    public static event Action<string> OnPlayerAction; // "move", "jump", "attack", "dash"

    // ����¼�����
    public static void TriggerPlayerSpawn(Character player) => OnPlayerSpawn?.Invoke(player);
    public static void TriggerPlayerDeath(Character player) => OnPlayerDeath?.Invoke(player);
    //����ֵ������/���֣�������ֵ���ޣ�����/���֣�
    public static void TriggerPlayerCurrentHealthChanged(float health) => OnPlayerCurrentHealthChanged?.Invoke(health);
    public static void TriggerPlayerMaxHealthPreChanged(float health) => OnPlayerMaxHealthPreChanged?.Invoke(health);
    public static void TriggerPlayerMaxHealthTemChanged(float health) => OnPlayerMaxHealthTemChanged?.Invoke(health);
    //���٣�����/���֣������٣�����/���֣���������������/���֣�
    public static void TriggerMoveSpeedPreChanged(int speed) => OnPlayerMoveSpeedPreChanged?.Invoke(speed);
    public static void TriggerMoveSpeedTemChanged(int speed) => OnPlayerMoveSpeedTemChanged?.Invoke(speed);
    public static void TriggerAttackSpeedPreChanged(int speed) => OnPlayerAttackSpeedPreChanged?.Invoke(speed);
    public static void TriggerAttackSpeedTemChanged(int speed) => OnPlayerAttackSpeedTemChanged?.Invoke(speed);
    public static void TriggerAttackDamagePreChanged(int damage) => OnPlayerAttackDamagePreChanged?.Invoke(damage);
    public static void TriggerAttackDamageTemChanged(int damage) => OnPlayerAttackDamageTemChanged?.Invoke(damage);
    //������/���֣������ֽ���
    public static void TriggerPlayerExpChanged(int currentExp, int nextLevelExp) => OnPlayerExpChanged?.Invoke(currentExp, nextLevelExp);
    public static void TriggerPlayerLevelUp(int level) => OnPlayerLevelUp?.Invoke(level);
    public static void TriggerDamageDealt(float damage, GameObject target) => OnDamageDealt?.Invoke(damage, target);
    public static void TriggerPlayerStatChanged(string statType, float oldValue, float newValue) => OnPlayerStatChanged?.Invoke(statType, oldValue, newValue);

    // �����¼�����
    public static void TriggerEnemySpawn(Enemy enemy) => OnEnemySpawn?.Invoke(enemy);
    public static void TriggerEnemyKilled(Enemy enemy, bool isSpecial = false) => OnEnemyKilled?.Invoke(enemy, isSpecial);

    // Ч���¼�����
    public static void TriggerCurrencyChanged(int amount) => OnCurrencyChanged?.Invoke(amount);
    public static void TriggerFansChanged(int count) => OnFansChanged?.Invoke(count);
    public static void TriggerFlowChanged(float flow) => OnFlowChanged?.Invoke(flow);
    public static void TriggerFlowWaveStarted() => OnFlowWaveStarted?.Invoke();
    public static void TriggerFlowWaveEnded() => OnFlowWaveEnded?.Invoke();
    public static void TriggerGoldChanged(int amount) => OnGoldChanged?.Invoke(amount);
    public static void TriggerBulletChanged(int count) => OnBulletChanged?.Invoke(count);
    // �����¼�����
    public static void TriggerSystemUnlocked(string systemId) => OnSystemUnlocked?.Invoke(systemId);
    public static void TriggerContentUnlocked(string contentId) => OnContentUnlocked?.Invoke(contentId);
    public static void TriggerAchievementUnlocked(string achievementId) => OnAchievementUnlocked?.Invoke(achievementId);
    public static void TriggerViewerCountChanged(int count) => OnViewerCountChanged?.Invoke(count);
    public static void TriggerTrafficChanged(float amount) => OnTrafficChanged?.Invoke(amount);

    // �̳��¼�����
    public static void TriggerTutorialStepStarted(string stepName) => OnTutorialStepStarted?.Invoke(stepName);
    public static void TriggerTutorialStepCompleted(string stepName) => OnTutorialStepCompleted?.Invoke(stepName);
    public static void TriggerTutorialCompleted() => OnTutorialCompleted?.Invoke();

    // �����¼�����
    public static void TriggerPlayerAction(string action) => OnPlayerAction?.Invoke(action);
}
概念 01
概念 01
Others/ObjectPool.cs
using System.Collections.Generic;
using UnityEngine;

public class ObjectPool : MonoBehaviour
{
    [System.Serializable]
    public class Pool
    {
        public string tag;
        public GameObject prefab;
        public int size;
    }

    public List<Pool> pools;
    public Dictionary<string, Queue<GameObject>> poolDictionary;

    private void Start()
    {
        poolDictionary = new Dictionary<string, Queue<GameObject>>();

        foreach (Pool pool in pools)
        {
            Queue<GameObject> objectPool = new Queue<GameObject>();

            for (int i = 0; i < pool.size; i++)
            {
                GameObject obj = Instantiate(pool.prefab);
                obj.SetActive(false);
                objectPool.Enqueue(obj);
            }

            poolDictionary.Add(pool.tag, objectPool);
        }
    }

    public GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation)
    {
        if (!poolDictionary.ContainsKey(tag)) return null;

        GameObject objectToSpawn = poolDictionary[tag].Dequeue();
        objectToSpawn.SetActive(true);
        objectToSpawn.transform.position = position;
        objectToSpawn.transform.rotation = rotation;

        poolDictionary[tag].Enqueue(objectToSpawn);
        return objectToSpawn;
    }
}
Abilities/Ability_Base.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Ability_Base : MonoBehaviour
{
    public bool Initialised;
    protected float Xinput, Zinput;
    public Core core;
    public Character character;
    protected bool jumpInput;

    void Start()
    {
        Init();
    }

    protected virtual void Init()
    {
        core = GetComponent<Core>();
        character = GetComponent<Character>();
        Initialised = true;
    }

    public virtual void PreExecute()
    {
        GetInput();
    }

    public virtual void Execute()
    {

    }

    public virtual void AfterExecute()
    {

    }

    protected virtual void GetInput()
    {
        Xinput = Input.GetAxis("Horizontal");
        Zinput = Input.GetAxis("Vertical");
        jumpInput = Input.GetKeyDown(KeyCode.Space);

        UseInput();
    }

    protected virtual void UseInput()
    {

    }
}
Abilities/Ability_Dash.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Ability_Dash : Ability_Base
{
    [Header("��̻�������")]
    public float dashDistance = 3f;          
    public float dashSpeed = 40f;           
    public float dashCooldown = 1f;          
    public bool keepMomentum = true;        
    public bool autoFaceDirection = true;    

    [Header("��������")]
    public KeyCode dashKey = KeyCode.LeftShift;

    [Header("�Ӿ�Ч��")]
    public AudioClip dashSound;              

    private float nextDashTime = 0f;
    private Vector3 dashDirection;
    private float currentDashDistance;
    private bool isDashing = false;
    private Coroutine dashCoroutine;
    private const float directionThreshold = 0.05f;

    public bool IsDashing => isDashing;
    public float CooldownProgress => Mathf.Clamp01(1f - (nextDashTime - Time.time) / dashCooldown);
    public bool CanDash => Time.time >= nextDashTime &&
                          character.characterCondition.CurrentState == CharacterStates.CharacterCondition.Normal;

    protected override void UseInput()
    {
        if (Input.GetKeyDown(dashKey) && CanDash)
        {
            StartDash();
        }
    }

    public void StartDash()
    {
        if (!CanDash) return;

        character.movementState.ChangeState(CharacterStates.MovementStates.Dash);
        isDashing = true;
        CalculateDashDirection();
        nextDashTime = Time.time + dashCooldown;
        currentDashDistance = 0f;
        GameEvents.TriggerPlayerAction("dash");
        if (dashCoroutine != null)
            StopCoroutine(dashCoroutine);

        dashCoroutine = StartCoroutine(DashRoutine());
        PlayDashEffects();
    }

    private IEnumerator DashRoutine()
    {
        Vector3 startPosition = transform.position;
        core.SetEnableGravity(false);
        while (currentDashDistance < dashDistance &&
               isDashing &&
               character.movementState.CurrentState == CharacterStates.MovementStates.Dash)
        {
            Vector3 movement = dashDirection * dashSpeed * Time.deltaTime;
            core.SetVelocity(new Vector3(movement.x, 0, movement.z));
            currentDashDistance = Vector3.Distance(startPosition, transform.position);

            yield return null;
        }
        EndDash();
    }

    private void CalculateDashDirection()
    {
        if (character.currentFaceDir == Character.FaceDir.Left)
        {
            dashDirection = Vector3.left;
        }
        else
        {
            dashDirection = Vector3.right;
        }

        if (autoFaceDirection)
        {
            AdjustCharacterFacing();
        }
    }

    private void AdjustCharacterFacing()
    {
        if (Mathf.Abs(dashDirection.x) > directionThreshold)
        {
            bool shouldFaceLeft = dashDirection.x < 0;
            bool currentlyFacingLeft = character.currentFaceDir == Character.FaceDir.Left;

            if (shouldFaceLeft != currentlyFacingLeft)
            {
                character.Flip();
            }
        }
    }
    private void EndDash()
    {
        isDashing = false;
        core.SetEnableGravity(true);

        if (!keepMomentum)
        {
            core.SetVelocity(new Vector3(0, core.Velocity.y, 0));
        }

        if (character.movementState.CurrentState == CharacterStates.MovementStates.Dash)
        {
            if (core.controllerState.IsGrounded)
            {
                character.movementState.ChangeState(CharacterStates.MovementStates.Idle);
            }
            else
            {
                character.movementState.ChangeState(CharacterStates.MovementStates.Fall);
            }
        }

        dashCoroutine = null;
    }
    public void InterruptDash()
    {
        if (isDashing)
        {
            if (dashCoroutine != null)
                StopCoroutine(dashCoroutine);

            EndDash();
        }
    }
    private void PlayDashEffects()
    {
        if (dashSound != null)
        {
            AudioSource.PlayClipAtPoint(dashSound, transform.position);
        }
    }

    public void ResetCooldown()
    {
        nextDashTime = 0f;
    }
    public void Dash(Vector3 direction, float customDistance = -1, float customSpeed = -1)
    {
        if (!CanDash) return;

        dashDirection = direction.normalized;
        if (customDistance > 0) dashDistance = customDistance;
        if (customSpeed > 0) dashSpeed = customSpeed;

        StartDash();

        if (customDistance > 0) dashDistance = 3f;
        if (customSpeed > 0) dashSpeed = 40f;
    }

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

        if (isDashing && character.characterCondition.CurrentState != CharacterStates.CharacterCondition.Normal)
        {
            InterruptDash();
        }
    }
}
概念 02
概念 02
Abilities/Ability_Jump.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.TextCore.Text;

public class Ability_Jump : Ability_Base
{
    public enum JumpType
    {
        CanJump,
        CanJumpAnyWhere,
        CanJumpOnGround,
        CanJumpOnGroundAndFromLadders,
        CanJumpAnyWhereAnyNum,
    }

    [Header("��Ծ����")]
    public int JumpNum = 1;
    public float JumpHeight = 3f;
    public JumpType jumpType = JumpType.CanJumpAnyWhere;
    public bool IsExactJump = true;

    [Header("��Ծ�ж�")]
    public float jumpBufferTime = 0.15f;
    public float coyoteTime = 0.1f;

    [Header("״̬")]
    public int CurJumpNum;
    public bool JumpHappenedThisFrame;

    [Header("��ȷ��Ծ����")]
    public bool IsPressTimeForJumpHeight = true;
    public float ShortestTimeInAir = 0.1f;
    public float FloatFactor_ButtonRelease = 2f;

    private float jumpButtonPressTime;
    private bool jumpButtonPressed = false;
    private bool jumpButtonReleased = false;
    private float lastGroundTime;
    private float lastJumpInputTime = -10f;
    private bool wasGroundedLastFrame = false;
    private bool hasJumpedThisAirTime = false;
    private Animator animator;

    protected override void Init()
    {
        base.Init();
        CurJumpNum = JumpNum;
        hasJumpedThisAirTime = false;
        animator = character.GetComponent<Animator>();
    }

    protected override void UseInput()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            lastJumpInputTime = Time.time;
            TryJump();
        }

        if (Input.GetKeyUp(KeyCode.Space))
        {
            JumpStop();
        }
    }

    void TryJump()
    {
        if (!character.CanPerformAction() || (PlayerHPController.Instance != null && PlayerHPController.Instance.IsDead))
        {
            return;
        }

        if (CanJump() || HasBufferedJumpInput())
        {
            ExecuteJump();
        }
    }

    bool CanJump()
    {
        if (!IsJumpAuthorized())
            return false;

        if (character.characterCondition.CurrentState != CharacterStates.CharacterCondition.Normal &&
            character.characterCondition.CurrentState != CharacterStates.CharacterCondition.ControlledMovement)
            return false;

        if (character.movementState.CurrentState == CharacterStates.MovementStates.Dash)
            return false;

        if (jumpType != JumpType.CanJumpAnyWhereAnyNum && CurJumpNum <= 0)
            return false;

        return true;
    }

    bool HasBufferedJumpInput()
    {
        bool isBufferValid = (Time.time - lastJumpInputTime <= jumpBufferTime);
        bool isGrounded = core.controllerState.IsGrounded;
        bool isInCoyoteTime = (Time.time - lastGroundTime <= coyoteTime) && !isGrounded && wasGroundedLastFrame;
        return isBufferValid && (isGrounded || isInCoyoteTime);
    }

    bool IsJumpAuthorized()
    {
        switch (jumpType)
        {
            case JumpType.CanJumpAnyWhere:
            case JumpType.CanJumpAnyWhereAnyNum:
                return true;

            case JumpType.CanJumpOnGround:
                if (core.controllerState.IsGrounded) return true;
                if (IsExactJump && CurJumpNum > 0) return true;
                if (!IsExactJump && !hasJumpedThisAirTime) return true;
                return false;

            case JumpType.CanJumpOnGroundAndFromLadders:
                return core.controllerState.IsGrounded ||
                       character.movementState.CurrentState == CharacterStates.MovementStates.Climbing;

            case JumpType.CanJump:
            default:
                return core.controllerState.IsGrounded;
        }
    }

    void ExecuteJump()
    {
        bool isFirstJump = core.controllerState.IsGrounded || (Time.time - lastGroundTime <= coyoteTime && wasGroundedLastFrame);
        if (isFirstJump)
        {
            character.movementState.ChangeState(CharacterStates.MovementStates.Jump);
        }
        else
        {
            character.movementState.ChangeState(CharacterStates.MovementStates.DoubleJump);
        }
        animator.SetTrigger("Takeof");
        float jumpVelocity = CalculateJumpVelocity();
        core.SetYforce(jumpVelocity);
        core.SetEnableGravity(true);

        if (jumpType != JumpType.CanJumpAnyWhereAnyNum)
        {
            CurJumpNum--;
            hasJumpedThisAirTime = true;
        }

        JumpHappenedThisFrame = true;
        jumpButtonPressTime = Time.time;
        jumpButtonPressed = true;
        jumpButtonReleased = false;
        core.controllerState.IsJumping = true;

        lastJumpInputTime = -10f;
        string jumpAct = isFirstJump ? "jumped" : "double_jumped";
        GameEvents.TriggerPlayerAction(jumpAct);
    }


    float CalculateJumpVelocity()
    {
        float gravity = Mathf.Abs(core.CurrentControllerParams.Gravity);
        return Mathf.Sqrt(2f * gravity * JumpHeight);
    }

    void JumpStop()
    {
        if (!IsExactJump) return;

        jumpButtonPressed = false;
        jumpButtonReleased = true;
    }

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

        JumpHappenedThisFrame = false;

        if (core.controllerState.JustGotGrounded)
        {
            CurJumpNum = JumpNum;
            hasJumpedThisAirTime = false;
            wasGroundedLastFrame = true;
        }
        else
        {
            wasGroundedLastFrame = core.controllerState.IsGrounded;
        }

        if (core.controllerState.IsGrounded)
        {
            lastGroundTime = Time.time;
        }

        if (IsExactJump)
        {
            HandleExactJump();
        }
        UpdateJumpState();
    }

    void HandleExactJump()
    {
        if (jumpButtonPressTime != 0 &&
            Time.time - jumpButtonPressTime >= ShortestTimeInAir &&
            core.Velocity.y > 0 &&
            jumpButtonReleased &&
            !jumpButtonPressed)
        {
            jumpButtonReleased = false;

            if (IsPressTimeForJumpHeight)
            {
                jumpButtonPressTime = 0;

                if (FloatFactor_ButtonRelease == 0)
                {
                    core.SetYforce(0);
                }
                else
                {
                    core.SetYforce(core.Velocity.y / FloatFactor_ButtonRelease);
                }
            }
        }
    }

    void UpdateJumpState()
    {
        bool isJumping = character.movementState.CurrentState == CharacterStates.MovementStates.Jump ||
                        character.movementState.CurrentState == CharacterStates.MovementStates.DoubleJump;

        core.controllerState.IsJumping = isJumping;
    }

    public void ResetJump()
    {
        CurJumpNum = JumpNum;
        hasJumpedThisAirTime = false;
        jumpButtonPressed = false;
        jumpButtonReleased = false;
        jumpButtonPressTime = 0;
        lastJumpInputTime = -10f;
    }

    public bool CanJumpImmediate()
    {
        bool hasBufferedJump = (Time.time - lastJumpInputTime <= jumpBufferTime);

        bool inCoyoteTime = (Time.time - lastGroundTime <= coyoteTime) &&
                           !core.controllerState.IsGrounded &&
                           wasGroundedLastFrame;

        bool canJumpImmediate = IsJumpAuthorized();
        if (jumpType != JumpType.CanJumpAnyWhereAnyNum)
        {
            if (IsExactJump)
            {
                canJumpImmediate = canJumpImmediate && CurJumpNum > 0;
            }
            else
            {
                canJumpImmediate = canJumpImmediate && !hasJumpedThisAirTime;
            }
        }

        bool canJumpWithBuffer = hasBufferedJump && (core.controllerState.IsGrounded || inCoyoteTime);

        return canJumpImmediate || canJumpWithBuffer;
    }
}
Abilities/Ability_Move.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Ability_Move : Ability_Base
{
    private CharacterAnimatorController animatorController;

    private Vector3 moveDirection;
    private bool isJumping;
    private float targetAngle;
    private float currentAngle;
    private const float LerpValue = 0.1f;

    protected override void Init()
    {
        base.Init();
        animatorController = character.GetComponent<CharacterAnimatorController>();
    }
    protected override void UseInput()
    {
        float XVelocity = Mathf.Abs(Xinput);
        float ZVelocity = Mathf.Abs(Zinput);

        animatorController.SetXVelocity(XVelocity);
        animatorController.SetZVelocity(ZVelocity);

        HandleMovement();
        HandleJump();
        HandleRotation();
        if (core.controllerState.IsGrounded)
        {
            animatorController.SetIsOnGround(true);
        }
        else
        {
            animatorController.SetIsOnGround(false);
        }
    }

    void HandleMovement()
    {
        moveDirection = new Vector3(Xinput, 0, Zinput).normalized;

        if (moveDirection.magnitude > 0.1f)
        {
            GameEvents.TriggerPlayerAction("moved");
        }

        if (character.movementState.CurrentState == CharacterStates.MovementStates.Dash)
        {
            return;
        }

        float currentSpeed = core.CurrentControllerParams.MoveSpeed;
        if (!core.controllerState.IsGrounded)
        {
            currentSpeed *= core.CurrentControllerParams.AirControl;
        }

        core.SetVelocityXZ(moveDirection.x * currentSpeed, moveDirection.z * currentSpeed);
        Vector3 targetVelocity = moveDirection * currentSpeed;

        if (character.characterCondition.CurrentState == CharacterStates.CharacterCondition.Dead)
        {
            core.SetVelocityXZ(0, 0);
            return;
        }

        if (character.characterCondition.CurrentState == CharacterStates.CharacterCondition.Stunned)
        {
            core.SetVelocityXZ(0, 0);
            return;
        }
    }
    void HandleJump()
    {
        if (jumpInput && core.controllerState.IsGrounded)
        {
            core.Jump(core.CurrentControllerParams.JumpForce);
            core.controllerState.IsJumping = true;
            isJumping = true;
        }

        if (core.controllerState.IsGrounded && isJumping)
        {
            isJumping = false;
            core.controllerState.IsJumping = false;
        }
    }

    void HandleRotation()
    {
        if (Mathf.Abs(Xinput) > 0.1f)
        {
            UpdateFaceDirection(Xinput);
        }

        SmoothRotateToTarget();
    }

    void UpdateFaceDirection(float xInput)
    {
        if (xInput > 0.1f)
        {
            targetAngle = 0f;
            character.currentFaceDir = Character.FaceDir.Right;
        }
        else if (xInput < -0.1f)
        {
            targetAngle = 180f;
            character.currentFaceDir = Character.FaceDir.Left;
        }
    }

    void SmoothRotateToTarget()
    {
        if (character.Model != null)
        {
            currentAngle = Mathf.LerpAngle(character.Model.eulerAngles.y, targetAngle, LerpValue);

            Vector3 newRotation = character.Model.eulerAngles;
            newRotation.y = currentAngle;
            character.Model.eulerAngles = newRotation;
        }
    }

}
Abilities/ShootATK.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class ShootATK : Ability_Base
{
    [Header("�������")]
    public float shootRange = 10f;
    public LayerMask enemyLayerMask = 1 << 9;
    public Transform shootPoint;
    public AudioClip shootSound;
    public AudioSource audioSource;
    public float shootCooldown = 0.3f;
    private float nextShootTime = 0f;
    public int damage = 25;
    public int maxAmmo = 8;
    public int currentAmmo = 8;
    private bool isReloading = false;
    public float reloadTime = 2f;
    private bool isShooting = false;
    private CharacterAnimatorController animatorController;

    private Coroutine attackCoroutine;

    private Queue<GameObject> MuzzleFlashPool = new Queue<GameObject>();
    private Queue<GameObject> HitEffectPool = new Queue<GameObject>();

    protected override void Init()
    {
        base.Init();
        animatorController = character.GetComponent<CharacterAnimatorController>();
        if (audioSource == null)
            audioSource = GetComponent<AudioSource>();
        if (audioSource == null)
            audioSource = gameObject.AddComponent<AudioSource>();

        if (shootPoint == null)
        {
            shootPoint = transform.Find("ShootPoint");
            if (shootPoint == null)
            {
                GameObject shootPointObj = new GameObject("ShootPoint");
                shootPoint = shootPointObj.transform;
                shootPoint.SetParent(transform);
                shootPoint.localPosition = new Vector3(0.5f, 0.5f, 0);
            }
        }
        enemyLayerMask = LayerMask.GetMask("Enemy");
    }

    protected override void UseInput()
    {
        if (isReloading) return;

        if (Input.GetMouseButtonDown(0) && Time.time >= nextShootTime &&
            character.CanPerformAction() && currentAmmo > 0&&UIManager.Instance.GetCurrentPanel().PanelName=="InGame_Main_Combat")
        {
            StartShoot();
        }

        if (Input.GetKeyDown(KeyCode.R) && currentAmmo < maxAmmo)
        {
            StartReload();
        }
    }
    private void OnReloadInsertAmmo()
    {
        currentAmmo = maxAmmo;
        GameEvents.TriggerBulletChanged(currentAmmo);
    }
    private void StartShoot()
    {
        if (!character.CanPerformAction()) return;

        if (character.characterCondition.CurrentState != CharacterStates.CharacterCondition.Normal)
            return;

        isShooting = true;
        nextShootTime = Time.time + shootCooldown;
        currentAmmo--;
        GameEvents.TriggerBulletChanged(currentAmmo);
        GameEvents.TriggerPlayerAction("attacked");
        StartShootAnimation();
        PlayShootEffects();
        StartCoroutine(YieldUntilFinish(animatorController.GetComponent<Animator>(), "Player_Attack", EndShootAnimation));

        if (currentAmmo <= 0)
        {
            StartReload();
        }

        character.StartAttack();

        if (shootPoint == null)
        {
            return;
        }

        if (shootSound != null && audioSource != null)
        {
            audioSource.PlayOneShot(shootSound);
        }

        Vector3 shootDirection = shootPoint.forward;
        PerformRaycast(shootDirection);

        if (attackCoroutine != null)
            StopCoroutine(attackCoroutine);
    }

    private void PlayShootEffects()
    {
        if (shootSound != null)
            audioSource.PlayOneShot(shootSound);
    }

    private void StartReload()
    {
        GameEvents.TriggerPlayerAction("reloaded");
        if (isReloading || currentAmmo == maxAmmo) return;

        isReloading = true;
        StartReloadAnimation();
        StartCoroutine(YieldUntilFinish(animatorController.GetComponent<Animator>(),"Player_Change",EndReloadAnimation));

    }
    public void EndReloadAnimation()
    {
        currentAmmo = maxAmmo;
        isReloading = false;
        animatorController.EndReloadAnimation();
        GameEvents.TriggerBulletChanged(currentAmmo);
    }
    public void EndShootAnimation()
    {
        animatorController.EndShootAnimation();
    }

    public void StartShootAnimation()
    {
        animatorController.StartShootAnimation();
    }

    public void StartReloadAnimation()
    {
        animatorController.StartReloadAnimation();
    }
    public IEnumerator YieldUntilFinish(Animator anim, string aniName, UnityAction action)
    {
        yield return new WaitUntil(() =>
        {
            AnimatorStateInfo stateInfo = anim.GetCurrentAnimatorStateInfo(0);
            return stateInfo.IsName(aniName) && stateInfo.normalizedTime >= 1.0f;
        });
        action();
    }

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

        if (!isShooting && currentAmmo <= 0 && !isReloading)
        {
            StartReload();
        }
    }

    void PerformRaycast(Vector3 direction)
    {
        RaycastHit hit;
        bool hitSomething = Physics.Raycast(
            shootPoint.position,
            direction,
            out hit,
            shootRange,
            enemyLayerMask
        );

        if (hitSomething)
        {
            OnHitEnemy(hit);
        }
    }

    void OnHitEnemy(RaycastHit hit)
    {
        Enemy enemy = FindEnemyInHierarchy(hit.collider);
        if (enemy != null)
        {
            GameEvents.TriggerDamageDealt(damage, enemy.gameObject);
            enemy.OnBulletHit(character, hit.point);
        }
    }

    Enemy FindEnemyInHierarchy(Collider collider)
    {
        Transform current = collider.transform;
        while (current != null)
        {
            Enemy enemy = current.GetComponent<Enemy>();
            if (enemy != null)
            {
                return enemy;
            }
            current = current.parent;
        }

        Enemy rootEnemy = collider.transform.root.GetComponent<Enemy>();
        if (rootEnemy != null)
        {
            return rootEnemy;
        }

        GameObject enemyRoot = GameObject.Find("Enemy");
        if (enemyRoot != null)
        {
            Enemy foundEnemy = enemyRoot.GetComponent<Enemy>();
            if (foundEnemy != null) return foundEnemy;
        }

        return null;
    }

    void OnGUI()
    {
        // ��ʾ��ҩUI
    }

    void OnDrawGizmosSelected()
    {
        if (shootPoint != null)
        {
            Gizmos.color = Color.red;
            Vector3 direction = shootPoint.forward;
            Gizmos.DrawRay(shootPoint.position, direction * shootRange);

            Gizmos.color = Color.yellow;
            Gizmos.DrawSphere(shootPoint.position, 0.1f);
        }
    }
    

}
概念 01
概念 01
Animation/Player/CharacterAnimatorController.cs
using UnityEngine;
using UnityEngine.EventSystems;

public class CharacterAnimatorController : MonoBehaviour
{
    private Animator animator;
    private static readonly int IsOnGround = Animator.StringToHash("isGrounded");
    private static readonly int XVelocity = Animator.StringToHash("xVelocity");
    private static readonly int ZVelocity = Animator.StringToHash("zVelocity");
    private static readonly int YVelocity = Animator.StringToHash("yVelocity");
    private static readonly int IsJumping = Animator.StringToHash("IsJumping");
    private static readonly int IsShooting = Animator.StringToHash("IsShooting");
    private static readonly int EndShooting = Animator.StringToHash("EndShooting");
    private static readonly int IsReload = Animator.StringToHash("IsReload");
    private static readonly int EndReload = Animator.StringToHash("EndReload");
    private static readonly int IsInjured = Animator.StringToHash("IsInjured");

    void Awake()
    {
        animator = GetComponent<Animator>();
    }

    public void SetIsOnGround(bool value)
    {
        animator.SetBool(IsOnGround, value);
    }

    public void SetXVelocity(float value)
    {
        animator.SetFloat(XVelocity, value);
    }
    public void SetIsInjured(bool value)
    {
        animator.SetBool(IsInjured, value);
    }

    public void SetZVelocity(float value)
    {
        animator.SetFloat(ZVelocity, value);
    }

    public void SetYVelocity(float value)
    {
        animator.SetFloat(YVelocity, value);
    }

    public void SetIsJumping(bool value)
    {
        animator.SetBool(IsJumping, value);
    }
    public void StartShootAnimation()
    {
        animator.SetTrigger("IsShooting"); 
    }

    public void EndShootAnimation()
    {
        animator.SetTrigger("EndShooting"); 
    }

    public void StartReloadAnimation()
    {
        animator.SetTrigger("IsReload"); 
    }

    public void EndReloadAnimation()
    {
        animator.SetTrigger("EndReload"); 
    }
    public AnimatorStateInfo GetCurrentAnimatorStateInfo(int layerIndex)
    {
        return animator.GetCurrentAnimatorStateInfo(layerIndex);
    }
}
Audio/AudioCue.cs
using UnityEngine;

/// <summary>
/// 音效所属的音频分组
/// </summary>
public enum AudioBus
{
    BGM,        // 背景音乐
    SFX,        // 普通音效
    UI,         // UI音效
    Ambience    // 环境音
}

/// <summary>
/// 单个音效/音乐的配置数据
/// </summary>
[System.Serializable]
public class AudioCue
{
    [Header("唯一标识符")]
    public string key;

    [Header("音频分组")]
    public AudioBus bus = AudioBus.SFX;

    [Header("音频文件(可多个随机)")]
    public AudioClip[] clips;

    [Header("音量")]
    [Range(0f, 1f)]
    public float volume = 1f;

    [Header("随机音高(让同音效更自然)")]
    [Range(0.1f, 3f)] public float pitchMin = 1f;
    [Range(0.1f, 3f)] public float pitchMax = 1f;

    [Header("是否循环")]
    public bool loop = false;

    [Header("同时播放上限(<=0 表示不限制)")]
    public int maxSimultaneous = 8;
}
Audio/AudioDatabase.cs
using UnityEngine;
using System.Collections.Generic;

/// <summary>
/// 音效数据库
/// 用于存储所有音效,并通过 key 快速查找
/// </summary>
[CreateAssetMenu(menuName = "Audio/Audio Database")]
public class AudioDatabase : ScriptableObject
{
    [Header("音效列表")]
    [Tooltip("在这里添加所有音效")]
    public List<AudioCue> cues = new();

    /// <summary>
    /// 内部字典,用于快速查找
    /// key = 音效名字
    /// value = 音效数据
    /// </summary>
    private Dictionary<string, AudioCue> cueDictionary;

    /// <summary>
    /// 构建字典(启动时调用)
    /// </summary>
    public void Build()
    {
        cueDictionary = new Dictionary<string, AudioCue>();

        foreach (var cue in cues)
        {
            // 跳过空数据
            if (cue == null)
                continue;

            // 跳过空key
            if (string.IsNullOrWhiteSpace(cue.key))
                continue;

            // 防止重复key
            if (cueDictionary.ContainsKey(cue.key))
            {
                Debug.LogWarning("AudioDatabase: 发现重复key: " + cue.key);
                continue;
            }

            // 添加到字典
            cueDictionary.Add(cue.key, cue);
        }
    }

    /// <summary>
    /// 根据key查找音效
    /// </summary>
    public bool TryGetCue(string key, out AudioCue cue)
    {
        // 如果字典还没构建,先构建
        if (cueDictionary == null)
        {
            Build();
        }

        // 尝试获取
        return cueDictionary.TryGetValue(key, out cue);
    }
}
概念 02
概念 02
Audio/AudioManager.cs
using UnityEngine;
using UnityEngine.Audio;
using System.Collections;
using System.Collections.Generic;

/// <summary>
/// 音频管理器(全局单例)
/// 功能:
/// 1) 播放音效(2D/3D/指定位置)
/// 2) 播放背景音乐(双AudioSource交叉淡入淡出)
/// 3) 对象池复用AudioSource(避免频繁创建销毁造成卡顿)
/// 4) 通过 AudioMixer 的 Exposed Parameters 控制 Master/BGM/SFX/UI/Ambience 音量
/// 5) 音量与静音保存(PlayerPrefs)
/// </summary>
public class AudioManager : MonoBehaviour
{
    public static AudioManager I { get; private set; }

    [Header("音效数据库(AudioDatabase.asset)")]
    public AudioDatabase database;

    [Header("AudioMixer(MasterMixer.mixer)")]
    public AudioMixer mixer;

    [Header("Mixer分组输出(把 Mixer 的 Group 拖进来)")]
    public AudioMixerGroup bgmGroup;
    public AudioMixerGroup sfxGroup;
    public AudioMixerGroup uiGroup;
    public AudioMixerGroup ambienceGroup;

    [Header("对象池预热数量(可按项目规模调整)")]
    public int sfxPrewarm = 16;
    public int uiPrewarm = 8;
    public int ambiencePrewarm = 8;

    [Header("默认冷却(防止同音效按钮被疯狂触发)")]
    [Tooltip("如果你不想限制,可以填 0")]
    public float defaultSfxCooldown = 0.03f;

    // -----------------------------
    // 对象池(SFX / UI / 环境音)
    // -----------------------------
    private readonly Queue<AudioSource> sfxPool = new();
    private readonly Queue<AudioSource> uiPool = new();
    private readonly Queue<AudioSource> ambiencePool = new();

    // -----------------------------
    // BGM 双源交叉淡入淡出
    // -----------------------------
    private AudioSource bgmA;
    private AudioSource bgmB;
    private bool usingA = true;
    private Coroutine bgmFadeCo;

    // -----------------------------
    // 限制与记录(冷却 & 同时播放计数)
    // -----------------------------
    private readonly Dictionary<string, float> lastPlayTime = new(); // key -> 上次播放时间
    private readonly Dictionary<string, int> playingCount = new();   // key -> 当前同时播放数量

    // -----------------------------
    // Unity 生命周期
    // -----------------------------
    private void Awake()
    {
        // 单例
        if (I != null)
        {
            Destroy(gameObject);
            return;
        }
        I = this;
        DontDestroyOnLoad(gameObject);

        // 构建数据库字典
        if (database != null)
            database.Build();

        // 创建根节点,保持层级清爽
        var root = new GameObject("AudioRoot").transform;
        root.SetParent(transform);

        // 创建 BGM 双源(2D)
        bgmA = CreateSource(root, "BGM_A", bgmGroup, is3D: false);
        bgmB = CreateSource(root, "BGM_B", bgmGroup, is3D: false);
        bgmA.loop = true;
        bgmB.loop = true;

        // 预热对象池(SFX/UI/环境)
        PrewarmPool(root, sfxPool, sfxPrewarm, "SFX_Pooled", sfxGroup, is3D: false);
        PrewarmPool(root, uiPool, uiPrewarm, "UI_Pooled", uiGroup, is3D: false);
        PrewarmPool(root, ambiencePool, ambiencePrewarm, "AMB_Pooled", ambienceGroup, is3D: true);

        // 启动时应用保存的音量设置
        ApplySavedMixerVolumes();
    }

    // -----------------------------
    // 公共 API:播放
    // -----------------------------

    /// <summary>
    /// 播放 2D 音效(默认走 SFX 分组,实际分组由数据库 cue.bus 决定)
    /// </summary>
    public void PlaySfx(string key, float volumeMul = 1f, float? cooldownOverride = null)
    {
        PlayInternal(key, Vector3.zero, is3D: false, volumeMul: volumeMul, cooldownOverride: cooldownOverride);
    }

    /// <summary>
    /// 播放 3D 音效(指定世界坐标位置)
    /// </summary>
    public void PlaySfxAt(string key, Vector3 worldPos, float volumeMul = 1f, float? cooldownOverride = null)
    {
        PlayInternal(key, worldPos, is3D: true, volumeMul: volumeMul, cooldownOverride: cooldownOverride);
    }

    /// <summary>
    /// 播放背景音乐(BGM),带淡入淡出
    /// 约定:BGM cue 一般 clips 只放 1 个(也可以多个随机)
    /// </summary>
    public void PlayMusic(string key, float fadeTime = 1.0f, bool loop = true)
    {
        if (!TryGetCue(key, out var cue)) return;

        // 选 clip(BGM允许多个随机)
        var clip = PickClip(cue);
        if (clip == null) return;

        // 选择当前与下一个 BGM Source
        var next = usingA ? bgmB : bgmA;
        var cur = usingA ? bgmA : bgmB;

        next.outputAudioMixerGroup = bgmGroup;
        next.clip = clip;
        next.loop = loop;
        next.pitch = 1f;
        next.volume = 0f;
        next.Play();

        // 交叉淡入淡出
        if (bgmFadeCo != null) StopCoroutine(bgmFadeCo);
        bgmFadeCo = StartCoroutine(CrossFade(cur, next, fadeTime, cue.volume));

        usingA = !usingA;
    }

    /// <summary>
    /// 停止背景音乐(淡出停止)
    /// </summary>
    public void StopMusic(float fadeTime = 0.6f)
    {
        var cur = usingA ? bgmB : bgmA; // usingA 已经在上次 PlayMusic 里切换过
        if (bgmFadeCo != null) StopCoroutine(bgmFadeCo);
        bgmFadeCo = StartCoroutine(FadeOutAndStop(cur, fadeTime));
    }

    // -----------------------------
    // 公共 API:音量/静音(保存+应用Mixer)
    // -----------------------------

    public void SetMasterVolume(float v)
    {
        AudioSettingsSave.SetMaster(v);
        ApplySavedMixerVolumes();
        AudioSettingsSave.Save();
    }

    public void SetBgmVolume(float v)
    {
        AudioSettingsSave.SetBgm(v);
        ApplySavedMixerVolumes();
        AudioSettingsSave.Save();
    }

    public void SetSfxVolume(float v)
    {
        AudioSettingsSave.SetSfx(v);
        ApplySavedMixerVolumes();
        AudioSettingsSave.Save();
    }

    public void SetUiVolume(float v)
    {
        AudioSettingsSave.SetUi(v);
        ApplySavedMixerVolumes();
        AudioSettingsSave.Save();
    }

    public void SetAmbienceVolume(float v)
    {
        AudioSettingsSave.SetAmb(v);
        ApplySavedMixerVolumes();
        AudioSettingsSave.Save();
    }

    public void SetMute(bool mute)
    {
        AudioSettingsSave.SetMute(mute);
        ApplySavedMixerVolumes();
        AudioSettingsSave.Save();
    }

    // -----------------------------
    // 核心:播放内部逻辑
    // -----------------------------
    private void PlayInternal(string key, Vector3 pos, bool is3D, float volumeMul, float? cooldownOverride)
    {
        if (!TryGetCue(key, out var cue)) return;

        // 冷却:优先使用 override,否则用默认冷却
        float cooldown = cooldownOverride.HasValue ? cooldownOverride.Value : defaultSfxCooldown;
        if (!PassCooldownAndLimit(cue, cooldown)) return;

        // 选 clip
        var clip = PickClip(cue);
        if (clip == null) return;

        // 根据 bus 决定用哪个池、哪个 mixer group
        var pool = GetPoolByBus(cue.bus, out var group, out bool default3D);

        // 取 AudioSource(对象池)
        var src = GetFromPool(pool, group, is3D || default3D);

        // 设置位置(2D无所谓,3D需要)
        src.transform.position = pos;

        // 设置2D/3D(若 cue 属于 Ambience 默认3D;否则按调用者 is3D)
        src.spatialBlend = (is3D || default3D) ? 1f : 0f;

        // 配置基本属性
        src.clip = clip;
        src.loop = cue.loop;
        src.pitch = Random.Range(cue.pitchMin, cue.pitchMax);
        src.volume = Mathf.Clamp01(cue.volume * volumeMul);

        // 播放
        src.gameObject.SetActive(true);
        src.Play();

        // 记录同时播放数
        IncPlaying(key);

        // 非循环:播放完归还
        if (!cue.loop)
            StartCoroutine(ReleaseWhenDone(key, src, pool));
        // 循环音效:你可以后续扩展 StopSfx(key) 去停
    }

    // -----------------------------
    // 对象池相关
    // -----------------------------

    /// <summary>
    /// 预热池子:提前创建 AudioSource
    /// </summary>
    private void PrewarmPool(Transform root, Queue<AudioSource> pool, int count, string name, AudioMixerGroup group, bool is3D)
    {
        for (int i = 0; i < count; i++)
        {
            var src = CreateSource(root, name, group, is3D);
            src.gameObject.SetActive(false);
            pool.Enqueue(src);
        }
    }

    /// <summary>
    /// 创建一个 AudioSource
    /// </summary>
    private AudioSource CreateSource(Transform parent, string name, AudioMixerGroup group, bool is3D)
    {
        var go = new GameObject(name);
        go.transform.SetParent(parent);

        var src = go.AddComponent<AudioSource>();
        src.playOnAwake = false;
        src.outputAudioMixerGroup = group;

        // 0=2D,1=3D
        src.spatialBlend = is3D ? 1f : 0f;
        src.rolloffMode = AudioRolloffMode.Logarithmic;

        return src;
    }

    /// <summary>
    /// 按 bus 返回对应池子,同时返回应该输出到哪个 mixer group
    /// default3D:这个 bus 默认是否 3D(环境音一般默认 3D)
    /// </summary>
    private Queue<AudioSource> GetPoolByBus(AudioBus bus, out AudioMixerGroup group, out bool default3D)
    {
        default3D = false;

        switch (bus)
        {
            case AudioBus.UI:
                group = uiGroup != null ? uiGroup : sfxGroup;
                return uiPool;

            case AudioBus.Ambience:
                group = ambienceGroup != null ? ambienceGroup : sfxGroup;
                default3D = true;
                return ambiencePool;

            case AudioBus.BGM:
                // BGM 不走对象池,BGM 用专用 bgmA/bgmB
                group = bgmGroup;
                return sfxPool;

            default:
                group = sfxGroup;
                return sfxPool;
        }
    }

    /// <summary>
    /// 从池子拿 AudioSource(池子空了就创建一个)
    /// </summary>
    private AudioSource GetFromPool(Queue<AudioSource> pool, AudioMixerGroup group, bool is3D)
    {
        AudioSource src;
        if (pool.Count > 0)
        {
            src = pool.Dequeue();
        }
        else
        {
            // 池子不够就动态创建(尽量别太频繁)
            src = CreateSource(transform, "PooledAudio_Extra", group, is3D);
        }

        // 每次拿出来都更新输出组与2D/3D设置,避免上次残留
        src.outputAudioMixerGroup = group;
        src.spatialBlend = is3D ? 1f : 0f;

        return src;
    }

    /// <summary>
    /// 播放完毕后归还池子
    /// </summary>
    private IEnumerator ReleaseWhenDone(string key, AudioSource src, Queue<AudioSource> pool)
    {
        if (src == null) yield break;

        yield return new WaitWhile(() => src != null && src.isPlaying);

        // 播放计数减少
        DecPlaying(key);

        // 清理并归还
        src.Stop();
        src.clip = null;
        src.loop = false;
        src.transform.localPosition = Vector3.zero;

        src.gameObject.SetActive(false);
        pool.Enqueue(src);
    }

    // -----------------------------
    // 数据库与限制
    // -----------------------------

    private bool TryGetCue(string key, out AudioCue cue)
    {
        cue = null;

        if (database == null)
        {
            Debug.LogWarning("[AudioManager] 没有绑定 AudioDatabase.asset");
            return false;
        }

        if (!database.TryGetCue(key, out cue))
        {
            Debug.LogWarning("[AudioManager] 找不到音效 key: " + key);
            return false;
        }

        return true;
    }

    /// <summary>
    /// 随机选一个音频
    /// </summary>
    private AudioClip PickClip(AudioCue cue)
    {
        if (cue.clips == null || cue.clips.Length == 0) return null;
        return cue.clips[Random.Range(0, cue.clips.Length)];
    }

    /// <summary>
    /// 冷却 + 同时播放上限
    /// 注意:AudioCue 里如果你还没加 cooldown/maxSimultaneous,这里也能用默认值
    /// </summary>
    private bool PassCooldownAndLimit(AudioCue cue, float cooldown)
    {
        float now = Time.unscaledTime;

        // 冷却:key 在 cooldown 时间内重复触发则忽略
        if (cooldown > 0f)
        {
            if (lastPlayTime.TryGetValue(cue.key, out var last) && now - last < cooldown)
                return false;

            lastPlayTime[cue.key] = now;
        }

        // 同时播放上限:如果 cue.maxSimultaneous <=0 视为不限
        if (cue.maxSimultaneous > 0)
        {
            playingCount.TryGetValue(cue.key, out int cnt);
            if (cnt >= cue.maxSimultaneous)
                return false;
        }

        return true;
    }

    private void IncPlaying(string key)
    {
        playingCount.TryGetValue(key, out int cnt);
        playingCount[key] = cnt + 1;
    }

    private void DecPlaying(string key)
    {
        if (!playingCount.TryGetValue(key, out int cnt)) return;
        cnt--;
        if (cnt <= 0) playingCount.Remove(key);
        else playingCount[key] = cnt;
    }

    // -----------------------------
    // BGM 淡入淡出
    // -----------------------------
    private IEnumerator CrossFade(AudioSource from, AudioSource to, float time, float targetToVol)
    {
        float t = 0f;
        float fromStart = from != null ? from.volume : 0f;

        while (t < time)
        {
            t += Time.unscaledDeltaTime;
            float k = Mathf.Clamp01(t / time);

            if (from != null) from.volume = Mathf.Lerp(fromStart, 0f, k);
            if (to != null) to.volume = Mathf.Lerp(0f, targetToVol, k);

            yield return null;
        }

        if (from != null)
        {
            from.Stop();
            from.volume = 0f;
        }
        if (to != null)
        {
            to.volume = targetToVol;
        }
    }

    private IEnumerator FadeOutAndStop(AudioSource src, float time)
    {
        if (src == null) yield break;

        float t = 0f;
        float start = src.volume;

        while (t < time)
        {
            t += Time.unscaledDeltaTime;
            float k = Mathf.Clamp01(t / time);
            src.volume = Mathf.Lerp(start, 0f, k);
            yield return null;
        }

        src.Stop();
        src.volume = 0f;
    }

    // -----------------------------
    // Mixer 音量应用(Exposed Parameters)
    // -----------------------------

    /// <summary>
    /// 线性音量(0~1) 转 dB(用于 AudioMixer)
    /// 1 -> 0dB,接近0 -> 很小的负dB(相当于静音)
    /// </summary>
    private float LinearToDb(float v)
    {
        v = Mathf.Clamp(v, 0.0001f, 1f);
        return Mathf.Log10(v) * 20f;
    }

    /// <summary>
    /// 把保存的音量应用到 Mixer
    /// 参数名必须与 Mixer 的 Exposed Parameters 一致
    /// </summary>
    private void ApplySavedMixerVolumes()
    {
        if (mixer == null) return;

        // 静音:把 Master 视作 0
        float masterLinear = AudioSettingsSave.Mute ? 0f : AudioSettingsSave.Master;

        mixer.SetFloat("MasterVol", LinearToDb(masterLinear));
        mixer.SetFloat("BgmVol", LinearToDb(AudioSettingsSave.Bgm));
        mixer.SetFloat("SfxVol", LinearToDb(AudioSettingsSave.Sfx));
        mixer.SetFloat("UiVol", LinearToDb(AudioSettingsSave.Ui));
        mixer.SetFloat("AmbienceVol", LinearToDb(AudioSettingsSave.Amb));
    }
}
Audio/AudioSettingSave.cs
using UnityEngine;

/// <summary>
/// 音量设置保存(PlayerPrefs)
/// 保存的是 0~1 的线性音量
/// </summary>
public static class AudioSettingsSave
{
    private const string MasterKey = "audio_master";
    private const string BgmKey    = "audio_bgm";
    private const string SfxKey    = "audio_sfx";
    private const string UiKey     = "audio_ui";
    private const string AmbKey    = "audio_amb";   // 环境音
    private const string MuteKey   = "audio_mute";

    // 读取(默认值都是 1)
    public static float Master => PlayerPrefs.GetFloat(MasterKey, 1f);
    public static float Bgm    => PlayerPrefs.GetFloat(BgmKey, 1f);
    public static float Sfx    => PlayerPrefs.GetFloat(SfxKey, 1f);
    public static float Ui     => PlayerPrefs.GetFloat(UiKey, 1f);
    public static float Amb    => PlayerPrefs.GetFloat(AmbKey, 1f); // 环境音
    public static bool Mute    => PlayerPrefs.GetInt(MuteKey, 0) == 1;

    // 写入(会自动 clamp 到 0~1)
    public static void SetMaster(float v) => PlayerPrefs.SetFloat(MasterKey, Mathf.Clamp01(v));
    public static void SetBgm(float v)    => PlayerPrefs.SetFloat(BgmKey, Mathf.Clamp01(v));
    public static void SetSfx(float v)    => PlayerPrefs.SetFloat(SfxKey, Mathf.Clamp01(v));
    public static void SetUi(float v)     => PlayerPrefs.SetFloat(UiKey, Mathf.Clamp01(v));
    public static void SetAmb(float v)    => PlayerPrefs.SetFloat(AmbKey, Mathf.Clamp01(v)); // 环境音
    public static void SetMute(bool m)    => PlayerPrefs.SetInt(MuteKey, m ? 1 : 0);

    public static void Save() => PlayerPrefs.Save();
}
Audio/BgmByScene.cs
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections.Generic;

/// <summary>
/// 根据场景名自动播放 BGM
/// 用法:挂到任意物体上(推荐挂到 AudioManager 同一个物体上)
/// </summary>
public class BgmByScene : MonoBehaviour
{
    [System.Serializable]
    public class SceneBgm
    {
        [Tooltip("场景名字(Build Settings 里那个 Scene 名)")]
        public string sceneName;

        [Tooltip("AudioDatabase 里的 BGM key,例如 bgm_menu")]
        public string bgmKey;
    }

    [Header("场景 -> BGM 对应表")]
    public List<SceneBgm> mappings = new List<SceneBgm>();

    [Header("淡入淡出时间")]
    public float fadeTime = 1.0f;

    [Header("如果同一首歌就不重复播放")]
    public bool dontRestartSameBgm = true;

    private string currentBgmKey = null;
    private Dictionary<string, string> mapDict;

    private void Awake()
    {
        // 把 list 转成字典,查找更快
        mapDict = new Dictionary<string, string>();
        foreach (var m in mappings)
        {
            if (m == null) continue;
            if (string.IsNullOrWhiteSpace(m.sceneName)) continue;
            if (string.IsNullOrWhiteSpace(m.bgmKey)) continue;

            if (!mapDict.ContainsKey(m.sceneName))
                mapDict.Add(m.sceneName, m.bgmKey);
        }
    }

    private void OnEnable()
    {
        SceneManager.sceneLoaded += OnSceneLoaded;
    }

    private void OnDisable()
    {
        SceneManager.sceneLoaded -= OnSceneLoaded;
    }

    private void Start()
    {
        // 游戏启动时,手动触发一次当前场景
        OnSceneLoaded(SceneManager.GetActiveScene(), LoadSceneMode.Single);
    }

    private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        if (AudioManager.I == null) return;

        if (mapDict != null && mapDict.TryGetValue(scene.name, out var bgmKey))
        {
            if (dontRestartSameBgm && currentBgmKey == bgmKey)
                return;

            currentBgmKey = bgmKey;
            AudioManager.I.PlayMusic(bgmKey, fadeTime);
        }
        else
        {
            // 如果这个场景没配置BGM,你可以选择:不做事 / 停止音乐
            // AudioManager.I.StopMusic(fadeTime);
        }
    }
}
概念 01
概念 01
Audio/BgmTest.cs
using UnityEngine;

public class BgmTest : MonoBehaviour
{
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Alpha1))
            AudioManager.I.PlaySfx("53");     // 你的音效key

        if (Input.GetKeyDown(KeyCode.Alpha5))
            AudioManager.I.PlayMusic("BgmTest"); // 你的bgm key
    }
}
Core/Camera/CamC1.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CamC : MonoBehaviour
{
    public bool isFollow;
    float offsetZ;
    public Vector3 CamOffset;
    public float HlookFarforwardTriggerDistance;// ����ǰ�ӵIJ�����������ǰ������Զ�ĵط�
    public float TriggerThreashold;
    public float resetSpeed = 2;
    public float followSpeed = 5;
    public Transform target;
    public bool lookForward = false;
    Vector3 lastTargetPosition;
    Vector3 curVelocity;
    Vector3 lookAheadpos;

    [Header("��������")]
    public float zoomSpeed = 2f;
    public float minZoomDistance = 3f;
    public float maxZoomDistance = 15f;
    private float currentZoomDistance = 5f;

    [Header("�߶�����")]
    public float minHeight = 1f;    
    public float maxHeight = 10f;  

    [Header("X��Ƕ�����")]
    public float minXRotation = 0f;  
    public float maxXRotation = 60f;  

    [Header("FOV����")]
    public bool useFovZoom = true; 
    public float minFov = 30f;     
    public float maxFov = 60f;    
    private Camera cam;

    void Start()
    {
        cam = GetComponent<Camera>();
        if (cam == null)
        {
            cam = Camera.main;
        }

        lastTargetPosition = target.position;
        offsetZ = (transform.position - target.position).z;
        currentZoomDistance = Mathf.Abs(offsetZ);
        UpdateCameraRotation();
    }
    private void Update()
    {
        if (!isFollow)
        {
            return;
        }
        HandleZoomInput();

        if (lookForward)
        {
            float xMoveDelta = (target.position - lastTargetPosition).x;
            bool updatLookAheadTarget = Mathf.Abs(xMoveDelta) > TriggerThreashold;
            if (updatLookAheadTarget)
            {
                lookAheadpos = HlookFarforwardTriggerDistance * Vector3.right * Mathf.Sign(xMoveDelta);
                lookAheadpos.z = -3;
            }
            else
            {
                lookAheadpos = Vector3.MoveTowards(lookAheadpos, Vector3.zero, Time.deltaTime * resetSpeed);
            }
        }
        Vector3 aheadTargetpos = target.position + lookAheadpos + Vector3.forward * -currentZoomDistance +
                                new Vector3(CamOffset.x, GetCurrentHeight(), CamOffset.z);
        Vector3 posTemp = Vector3.Lerp(transform.position, aheadTargetpos, Time.deltaTime * followSpeed);
        transform.position = posTemp;
        UpdateCameraRotation();
        lastTargetPosition = target.position;
    }

    private void HandleZoomInput()
    {
        float scroll = Input.GetAxis("Mouse ScrollWheel");
        if (scroll != 0)
        {
            currentZoomDistance = Mathf.Clamp(currentZoomDistance - scroll * zoomSpeed, minZoomDistance, maxZoomDistance);
            if (useFovZoom && cam != null)
            {
                float zoomRatio = Mathf.InverseLerp(minZoomDistance, maxZoomDistance, currentZoomDistance);
                float targetFov = Mathf.Lerp(minFov, maxFov, zoomRatio);
                cam.fieldOfView = targetFov;
            }
        }
    }
    private float GetCurrentHeight()
    {
        float zoomRatio = Mathf.InverseLerp(minZoomDistance, maxZoomDistance, currentZoomDistance);
        return Mathf.Lerp(minHeight, maxHeight, zoomRatio);
    }
    private float GetCurrentXRotation()
    {
        float zoomRatio = Mathf.InverseLerp(minZoomDistance, maxZoomDistance, currentZoomDistance);
        return Mathf.Lerp(minXRotation, maxXRotation, zoomRatio);
    }
    private void UpdateCameraRotation()
    {
        float currentXRotation = GetCurrentXRotation();
        transform.eulerAngles = new Vector3(currentXRotation, 0, 0);
    }
}
Core/Character/Character.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Character : MonoBehaviour
{
    [Header("��������")]
    public float hurtForce = 10f;
    public float hurtDuration = 0.5f; 
    public Color hurtColor = Color.red; 

    private bool isInjured = false; 
    private CharacterController characterController;
    private SpriteRenderer spriteRenderer;
    private Color originalColor;
    private Vector3 knockbackVelocity;
    private CharacterAnimatorController aniController;
    protected Ability_Base[] abilities;
    public LayerMask enemyLayerMask;
    public enum FlipType
    {
        SpriteFlip,
        ModelRotate
    }
    public enum FaceDir
    {
        Left, Right
    }

    public Transform Model;
    public FaceDir currentFaceDir;
    public FlipType flipType = FlipType.ModelRotate;

    public ToolStateMachine<CharacterStates.MovementStates> movementState;
    public ToolStateMachine<CharacterStates.CharacterCondition> characterCondition;

    private Coroutine hitStunCoroutine;
    private Coroutine reloadCoroutine;
    public Core core { get; private set; }

     void Awake()
    {
        enemyLayerMask = LayerMask.GetMask("Enemy");
        aniController = GetComponent<CharacterAnimatorController>();
        Init();

    }

    void Update()
    {
        UpdateAnimationParameters();
        EveryFrame();
        if (core.controllerState.IsJumping)
        {
            aniController.SetIsJumping(true);
        }
        else
        {
            aniController.SetIsJumping(false);
        }
        if (isInjured)
        {
            characterController.Move(knockbackVelocity * Time.deltaTime);
            knockbackVelocity = Vector3.Lerp(knockbackVelocity,Vector3.zero, 5f*Time.deltaTime);
        }
    }

    void UpdateAnimationParameters()
    {
        aniController.SetIsOnGround(core.controllerState.IsGrounded);
        aniController.SetXVelocity(Mathf.Abs(core.Velocity.x));
        aniController.SetZVelocity(Mathf.Abs(core.Velocity.z));
        aniController.SetYVelocity(core.Velocity.y);
        aniController.SetIsInjured(isInjured);
    }
    public void GetHurtFromPosition(Vector3 attackSourcePosition)
    {
        // ����Ƿ����˻�������
        // �����ǣ���������ǹ�������ܻ������Ľű����Ҳ�ȷ������ˮ��͡��
        if (isInjured || (PlayerHPController.Instance != null && PlayerHPController.Instance.IsDead)) return;
        isInjured = true;
        Vector3 direction = (transform.position - attackSourcePosition).normalized;
        direction.y = Mathf.Abs(direction.y) > 0.3f ? direction.y : 0.5f;
        knockbackVelocity = direction.normalized * hurtForce;
        StartCoroutine(ResetHurtState());
    }
    public void GetHurt(ControllerColliderHit hit)
    {
        // ��ײ���������绷���˺���
    }
    public void OnHurtFlashStart()
    {
        StartCoroutine(HurtFlashEffect());
    }
    public void OnHurtFlashEnd()
    {
        spriteRenderer.color = originalColor;
    }
    private IEnumerator HurtFlashEffect()
    {
        int flashCount = 3;
        float flashInterval = hurtDuration / (flashCount * 2);

        for (int i = 0; i < flashCount; i++)
        {
            spriteRenderer.color = hurtColor;
            yield return new WaitForSeconds(flashInterval);
            spriteRenderer.color = originalColor;
            yield return new WaitForSeconds(flashInterval);
        }
        spriteRenderer.color = originalColor;
    }

    private IEnumerator ResetHurtState()
    {
        yield return new WaitForSeconds(hurtDuration);
        isInjured = false;
        knockbackVelocity = Vector3.zero; 
    }
    private bool IsEnemy(GameObject obj)
    {
        return (enemyLayerMask.value & (1 << obj.layer)) != 0;
    }
    private void GetHurtFromHit(ControllerColliderHit hit)
    {
        if (isInjured) return;
        isInjured = true;
        Vector3 direction = (transform.position - hit.point).normalized;
        direction.y = Mathf.Abs(direction.y) > 0.3f ? direction.y : 0.5f;
        knockbackVelocity = direction.normalized * hurtForce;
        StartCoroutine(HurtFlashEffect());
        StartCoroutine(ResetHurtState());
    }

    protected virtual void Init()
    {
        core = GetComponent<Core>();
        characterController = GetComponent<CharacterController>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        originalColor = spriteRenderer.color;
        movementState = new ToolStateMachine<CharacterStates.MovementStates>(gameObject, false);
        characterCondition = new ToolStateMachine<CharacterStates.CharacterCondition>(gameObject, false);
        abilities = GetComponentsInChildren<Ability_Base>();
        if (Model == null)
            Model = transform;
        currentFaceDir = FaceDir.Right;
        movementState.ChangeState(CharacterStates.MovementStates.Idle);
        characterCondition.ChangeState(CharacterStates.CharacterCondition.Normal);
        GameEvents.TriggerPlayerSpawn(this);
    }
    public bool CanPerformAction()
    {
        return characterCondition.CurrentState != CharacterStates.CharacterCondition.Dead &&
               characterCondition.CurrentState != CharacterStates.CharacterCondition.Stunned;
    }

    public void StartReload(float reloadDuration)
    {
        if (characterCondition.CurrentState == CharacterStates.CharacterCondition.Dead ||
            characterCondition.CurrentState == CharacterStates.CharacterCondition.Stunned)
            return;
        if (reloadCoroutine != null)
            StopCoroutine(reloadCoroutine);

        reloadCoroutine = StartCoroutine(CompleteReload(reloadDuration));
    }

    private IEnumerator CompleteReload(float duration)
    {
        yield return new WaitForSeconds(duration);

        // ����������ӵ�ҩ�����߼�
    }

    public void CancelReload()
    {
            if (reloadCoroutine != null)
            {
                StopCoroutine(reloadCoroutine);
                reloadCoroutine = null;
            }
    }

    public void StartHitStun(float stunDuration)
    {
        if (characterCondition.CurrentState == CharacterStates.CharacterCondition.Dead)
            return;

        CancelReload();
        characterCondition.ChangeState(CharacterStates.CharacterCondition.Stunned);

        if (core != null)
        {
            core.SetVelocityXZ(0, 0);
        }

        if (hitStunCoroutine != null)
            StopCoroutine(hitStunCoroutine);

        hitStunCoroutine = StartCoroutine(RecoverFromHitStun(stunDuration));
    }

    private IEnumerator RecoverFromHitStun(float duration)
    {
        yield return new WaitForSeconds(duration);

        if (characterCondition.CurrentState == CharacterStates.CharacterCondition.Stunned)
        {
            characterCondition.ChangeState(CharacterStates.CharacterCondition.Normal);
        }
    }

    public void StartAttack()
    {
        if (!CanPerformAction()) return;
    }
    protected virtual void EveryFrame()
    {
        PreExecute();
        Execute();
        AfterExecute();
        OtherExecute();
        UpdateStateMachine();
    }

    protected virtual void PreExecute()
    {
        if (abilities != null)
        {
            foreach (Ability_Base ability in abilities)
            {
                if (ability.Initialised && ability.enabled)
                    ability.PreExecute();
            }
        }
    }

    protected virtual void Execute()
    {
        foreach (Ability_Base ability in abilities)
        {
            if (ability.Initialised && ability.enabled)
                ability.Execute();
        }
    }

    protected virtual void AfterExecute()
    {
        foreach (Ability_Base ability in abilities)
        {
            if (ability.Initialised && ability.enabled)
                ability.AfterExecute();
        }
    }

    protected virtual void OtherExecute()
    {

    }

    public void Die()
    {
        if (characterCondition.CurrentState == CharacterStates.CharacterCondition.Dead)
            return;
        characterCondition.ChangeState(CharacterStates.CharacterCondition.Dead);

        GameEvents.TriggerPlayerDeath(this);
    }

    public void Flip()
    {
        if (currentFaceDir == FaceDir.Left)
        {
            currentFaceDir = FaceDir.Right;
        }
        else
        {
            currentFaceDir = FaceDir.Left;
        }
    }

    protected virtual void UpdateStateMachine()
    {
        if (core != null)
        {
            if (core.controllerState.IsGrounded)
            {
                Vector3 horizontalVelocity = new Vector3(core.Velocity.x, 0, core.Velocity.z);
                if (horizontalVelocity.magnitude > 0.1f)
                {
                    movementState.ChangeState(CharacterStates.MovementStates.Walk);
                }
                else
                {
                    movementState.ChangeState(CharacterStates.MovementStates.Idle);
                }
            }
            else
            {
                if (core.Velocity.y > 0)
                {
                    movementState.ChangeState(CharacterStates.MovementStates.Jump);
                }
                else
                {
                    movementState.ChangeState(CharacterStates.MovementStates.Fall);
                }
            }
        }
    }
}
概念 02
概念 02
Core/Character/PlayerControl.cs
using UnityEngine;

public class PlayerControl : MonoBehaviour
{
    public bool isOnGround;
    public Core core;

    void Update()
    {
        isOnGround = core.controllerState.IsGrounded;
    }
}
Core/SystemInitializer.cs
using UnityEngine;
using System.Collections;

public class SystemInitializer : MonoBehaviour
{
    [Header("初始化顺序")]
    public bool autoInitialize = true;
    public float initializationDelay = 0.1f;

    private void Start()
    {
        if (autoInitialize)
        {
            StartCoroutine(InitializeSystemsSequentially());
        }
    }

    private IEnumerator InitializeSystemsSequentially()
    {
        // 第一层: 核心系统
        yield return CreateSystem<GameProgressManager>("GameProgressManager");
        yield return new WaitForSeconds(initializationDelay);

        // 第二层: 经济系统
        yield return CreateSystem<CurrencySys>("CurrencySystem");
        yield return CreateSystem<ExpSys>("ExperienceSystem");
        yield return CreateSystem<FansSys>("FansSystem");
        yield return CreateSystem<FlowSys>("FlowSystem");
        yield return new WaitForSeconds(initializationDelay);

        // 第三层: 游戏系统
        yield return CreateSystem<InventorySys>("InventorySystem");
        yield return CreateSystem<EquipmentSys>("EquipmentSystem");
        yield return CreateSystem<SkillManager>("SkillManager");
        yield return CreateSystem<UpgradeSys>("UpgradeSystem");
        yield return new WaitForSeconds(initializationDelay);

        // 第四层: 辅助系统
        yield return CreateSystem<AchievementSys>("AchievementSystem");
        yield return CreateSystem<EnemyRewardSys>("EnemyRewardSystem");
        yield return CreateSystem<StylishActionSys>("StylishActionSystem");
        yield return CreateSystem<GameFlowController>("GameFlowController");
        yield return new WaitForSeconds(initializationDelay);

        // 第五层: 管理器
        yield return CreateSystem<TutorialManager>("TutorialManager");
        yield return CreateSystem<UIManager>("UIManager");

        // 检查初始化状态
        CheckSystemStatus();
    }

    private IEnumerator CreateSystem<T>(string name) where T : Component
    {
        if (FindObjectOfType<T>() != null)
        {
            yield break;
        }

        GameObject systemObj = new GameObject(name);
        systemObj.AddComponent<T>();
        DontDestroyOnLoad(systemObj);
        yield return null;
    }

    private void CheckSystemStatus()
    {

        var systems = new (System.Type, string)[]
        {
            (typeof(CurrencySys), "货币系统"),
            (typeof(ExpSys), "经验系统"),
            (typeof(FansSys), "粉丝系统"),
            (typeof(FlowSys), "流量系统"),
            (typeof(InventorySys), "库存系统"),
            (typeof(SkillManager), "技能管理器"),
            (typeof(UpgradeSys), "升级系统"),
            (typeof(AchievementSys), "成就系统"),
            (typeof(TutorialManager), "教程管理器"),
            (typeof(UIManager), "UI管理器")
        };

        foreach (var (type, name) in systems)
        {
            var instance = FindObjectOfType(type);
        }
    }

    [ContextMenu("手动初始化系统")]
    public void ManualInitialize()
    {
        StartCoroutine(InitializeSystemsSequentially());
    }

    [ContextMenu("重启所有系统")]
    public void RestartAllSystems()
    {
        var systemObjects = GameObject.FindGameObjectsWithTag("System");
        foreach (var obj in systemObjects)
        {
            if (obj != gameObject)
                DestroyImmediate(obj);
        }

        StartCoroutine(InitializeSystemsSequentially());
    }
}
Core/Tools/ToolStateChangeEvent.cs
using UnityEngine;
using UnityEngine.UI;
using System.Collections; 
using System;
using System.Collections.Generic;
 
	public struct ToolStateChangeEvent<T> where T: struct, IComparable, IConvertible, IFormattable
	{
		public GameObject Target;
		public ToolStateMachine<T> TargetStateMachine;
		public T NewState;
		public T PreviousState;

		public ToolStateChangeEvent(ToolStateMachine<T> stateMachine)
		{
			Target = stateMachine.Target;
			TargetStateMachine = stateMachine;
			NewState = stateMachine.CurrentState;
			PreviousState = stateMachine.PreviousState;
		}
	}
 
	public interface MMIStateMachine
	{
		bool TriggerEvents { get; set; }
	}

 
	public class ToolStateMachine<T> : MMIStateMachine where T : struct, IComparable, IConvertible, IFormattable
	{
	 
		public bool TriggerEvents { get; set; } 
		public GameObject Target; 
		public T CurrentState { get; protected set; } 
		public T PreviousState { get; protected set; }
	 
		public ToolStateMachine(GameObject target, bool triggerEvents)
		{
			this.Target = target;
			this.TriggerEvents = triggerEvents;
		} 
	 
		public virtual void ChangeState(T newState)
		{ 
			if (newState.Equals(CurrentState))
			{
				return;
			}
			 
			PreviousState = CurrentState;
			CurrentState = newState;

		 
		}
	 
		public virtual void RestorePreviousState()
		{ 
			CurrentState = PreviousState;

		 
		}	
	} 
概念 01
概念 01
Enemy/Enemy.cs
using UnityEngine;
using System.Collections;

public class Enemy : MonoBehaviour
{
    [Header("基本设置")]
    public int scoreValue = 10;
    public float baseSpeed = 3f;
    public float attackDistance = 1f;
    public int damage = 100;
    // 改这里没有用,去unity检查器里改
    public Transform player;
    public GameObject deathEffect;
    public EnemyHealth enemyHealth;

    [Header("移动和物理设置")]
    public float gravity = -30f;
    public float groundCheckDistance = 0.1f;
    public LayerMask groundMask = 1;

    [Header("死亡设置")]
    public float deathDisappearDelay = 2f; 

    [Header("奖励")]
    public bool IsSpecialEnemy = false;
    public bool IsBossEnemy = false;

    private CharacterController characterController;
    private Vector3 spawnPosition;
    private bool isDead = false;
    private bool hasAttacked = false;
    private Vector3 moveDirection;
    private Vector3 velocity;
    private bool isGrounded;
    private Coroutine attackCoroutine;
    private Coroutine deathCoroutine;

    private Collider enemyCollider;
    private float currentSpeed;

    public bool IsDead => isDead;

    void Start()
    {
        InitializeEnemy();
    }

    private void InitializeEnemy()
    {
        spawnPosition = transform.position;
        currentSpeed = baseSpeed;

        FindPlayer();
        InitializeComponents();
        SetupHealthEvents();
    }


    private void FindPlayer()
    {
        if (player == null)
        {
            GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
            if (playerObj != null) player = playerObj.transform;
        }
    }

    private void InitializeComponents()
    {
        characterController = GetComponent<CharacterController>();
        enemyCollider = GetComponent<Collider>();

        if (characterController == null)
        {
            characterController = gameObject.AddComponent<CharacterController>();
            characterController.height = 2.0f;
            characterController.radius = 0.5f;
            characterController.center = new Vector3(0, 1f, 0);
        }

        if (enemyHealth == null)
            enemyHealth = GetComponent<EnemyHealth>();
    }

    private void SetupHealthEvents()
    {
        if (enemyHealth != null)
        {
            enemyHealth.OnDeath += OnHealthDeath;
        }
    }

    private void Update()
    {
        if (isDead || player == null) return;
        UpdateEnemyState();
    }

    private void UpdateEnemyState()
    {
        CheckGrounded();
        HandleEnemyAI();
        ApplyGravity();
    }

    private void CheckGrounded()
    {
        RaycastHit hit;
        isGrounded = Physics.Raycast(transform.position, Vector3.down, out hit, groundCheckDistance, groundMask);
    }

    private void HandleEnemyAI()
    {
        if (player == null) return;

        Vector3 toPlayer = (player.position - transform.position).normalized;
        toPlayer.y = 0;
        moveDirection = toPlayer;

        float distanceToPlayer = Vector3.Distance(transform.position, player.position);

        if (distanceToPlayer > attackDistance)
        {
            Vector3 movement = moveDirection * currentSpeed * Time.deltaTime;
            characterController.Move(movement);
            UpdateFaceDirection(moveDirection);
        }
        else if (!hasAttacked)
        {
            AttackPlayer();
        }
    }

    private void ApplyGravity()
    {
        if (isGrounded && velocity.y < 0)
        {
            velocity.y = -2f;
        }
        else
        {
            velocity.y += gravity * Time.deltaTime;
        }
        characterController.Move(velocity * Time.deltaTime);
    }

    private void UpdateFaceDirection(Vector3 direction)
    {
        if (direction.x > 0.1f)
        {
            transform.rotation = Quaternion.Euler(0, 0, 0);
        }
        else if (direction.x < -0.1f)
        {
            transform.rotation = Quaternion.Euler(0, 180, 0);
        }
    }

    private void AttackPlayer()
    {
        //死了就不打了
        if (hasAttacked || (PlayerHPController.Instance != null && PlayerHPController.Instance.IsDead)) return;
        hasAttacked = true;
        PlayerHPController.Instance?.TakeDamage(damage);
        Character playerCharacter = FindObjectOfType<Character>();
        if (playerCharacter != null)
        {
            playerCharacter.GetHurtFromPosition(transform.position);
        }

        attackCoroutine = StartCoroutine(ResetAttackAfterDelay(1f));
    }
    private IEnumerator ResetAttackAfterDelay(float delay)
    {
        yield return new WaitForSeconds(delay);
        hasAttacked = false;
    }

    private void OnHealthDeath()
    {
        if (isDead) return;
        isDead = true;

        //增加玩家击杀数
        StatisticsManager.Instance.KillPlus();

        OnDeath();
    }

    public void OnBulletHit(Character shooter, Vector3 hitPoint, float damageAmount = 35f)
    {
        if (isDead) return;
        enemyHealth?.ReduceHealth(damageAmount, hitPoint);

    }

    public void OnDeath()
    {
        if (isDead) return;

        isDead = true;
        moveDirection = Vector3.zero;
        velocity = Vector3.zero;


        if (characterController != null)
            characterController.enabled = false;

        if (enemyCollider != null)
            enemyCollider.enabled = false;

        EnemyRewardSys.Instance?.OnEnemyKilled(IsSpecialEnemy, IsBossEnemy);
        GameEvents.TriggerEnemyKilled(this, IsSpecialEnemy);
        gameObject.SetActive(false);
    }



    public void Revive()
    {
        if (!isDead) return;

        isDead = false;
        hasAttacked = false;
        currentSpeed = baseSpeed;
        moveDirection = Vector3.zero;
        velocity = Vector3.zero;

        if (characterController != null)
        {
            characterController.enabled = true;
        }

        if (enemyCollider != null)
        {
            enemyCollider.enabled = true;
        }

        transform.position = spawnPosition;

        enemyHealth?.ResetHealth();

        gameObject.SetActive(true);
    }

    private void OnEnable()
    {
        if (isDead)
        {
            Revive();
        }
        else
        {
            if (characterController != null)
                characterController.enabled = true;
            if (enemyCollider != null)
                enemyCollider.enabled = true;
        }
    }

    private void OnDisable()
    {
        if (attackCoroutine != null)
        {
            StopCoroutine(attackCoroutine);
            attackCoroutine = null;
        }
        if (deathCoroutine != null)
        {
            StopCoroutine(deathCoroutine);
            deathCoroutine = null;
        }
    }

    private void OnDestroy()
    {
        if (enemyHealth != null)
        {
            enemyHealth.OnDeath -= OnHealthDeath;
        }

        if (attackCoroutine != null)
            StopCoroutine(attackCoroutine);
        if (deathCoroutine != null)
            StopCoroutine(deathCoroutine);
    }
}
Enemy/EnemyRewardSys.cs
using UnityEngine;

public class EnemyRewardSys : MonoBehaviour
{
    public static EnemyRewardSys Instance;

    [Header("���˽�������")]
    public int baseExpReward = 10;
    public int baseCurrencyReward = 5;
    public int baseFlowReward = 15;

    [Header("������˽�������")]
    public float specialEnemyMultiplier = 2.0f;
    public float bossEnemyMultiplier = 5.0f;

    [Header("��ɱ����")]
    public float comboMultiplier = 1.0f;
    public float maxComboMultiplier = 3.0f;
    public float comboDecayTime = 3.0f;

    private int currentCombo = 0;
    private float lastKillTime = 0f;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    private void Update()
    {
        if (currentCombo > 0 && Time.time - lastKillTime > comboDecayTime)
        {
            currentCombo = 0;
            comboMultiplier = 1.0f;
        }
    }

    public void OnEnemyKilled(bool isSpecialEnemy = false, bool isStylishKill = false)
    {
        UpdateCombo();
        float multiplier = GetEnemyMultiplier(isSpecialEnemy) * comboMultiplier;
        if (isStylishKill) multiplier *= 1.5f;
        GrantRewards(multiplier, isSpecialEnemy, isStylishKill);
    }

    private void UpdateCombo()
    {
        currentCombo++;
        lastKillTime = Time.time;

        comboMultiplier = Mathf.Min(1.0f + (currentCombo / 5) * 0.5f, maxComboMultiplier);
    }

    private float GetEnemyMultiplier(bool isSpecialEnemy)
    {
        if (isSpecialEnemy) return specialEnemyMultiplier;
        return 1.0f;
    }

    private void GrantRewards(float multiplier, bool isSpecialEnemy, bool isStylishKill)
    {
        int expReward = Mathf.RoundToInt(baseExpReward * multiplier);
        ExpSys.Instance?.AddExpFromKill(isSpecialEnemy);

        int currencyReward = Mathf.RoundToInt(baseCurrencyReward * multiplier);
        CurrencySys.Instance?.AddCurrencyFromKill(isSpecialEnemy);

        int flowReward = Mathf.RoundToInt(baseFlowReward * multiplier);
        FlowSys.Instance?.AddFlow(flowReward, "enemy_kill");

        FansSys.Instance?.TryAddFansFromKill(isSpecialEnemy);

        if (isStylishKill)
        {
            StylishActionSys.Instance?.PerformStylishAction("stylish_kill");
        }
    }

    public int GetCurrentCombo()
    {
        return currentCombo;
    }

    public float GetCurrentComboMultiplier()
    {
        return comboMultiplier;
    }

    public void ResetCombo()
    {
        currentCombo = 0;
        comboMultiplier = 1.0f;
    }
}
Enemy/EnmSpawner.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class EnemySpawner : MonoBehaviour
{
    [Header("��������������")]
    public GameObject enemyPrefab;
    public float spawnRadius = 10f;
    public int maxEnemies = 10;
    public float spawnInterval = 3f;
    public bool startSpawningOnAwake = false;

    [Header("��Ҿ�������")]
    public float activationRange = 20f;
    public float detectionRange = 15f;
    public float stopTrackingRange = 20f;
    public float safeSpawnDistanceFromPlayer = 5f;

    private List<GameObject> enemyPool = new List<GameObject>();
    private Transform player;
    private bool isActive = false;
    private float spawnCooldown = 0f;

    private void Start()
    {
        InitializeSpawner();
    }

    private void Update()
    {
        if (player == null)
        {
            FindPlayer();
            return;
        }

        UpdateSpawnerState();
        UpdateCooldown();
    }

    private void InitializeSpawner()
    {
        FindPlayer();

        for (int i = 0; i < maxEnemies; i++)
        {
            CreateEnemyInPool();
        }

        if (startSpawningOnAwake)
        {
            ActivateSpawner();
        }
    }

    private void CreateEnemyInPool()
    {
        if (enemyPrefab == null) return;

        GameObject enemy = Instantiate(enemyPrefab, transform.position, Quaternion.identity, transform);
        enemy.SetActive(false);
        enemyPool.Add(enemy);
    }

    private void UpdateSpawnerState()
    {
        float distanceToPlayer = Vector3.Distance(transform.position, player.position);

        if (!isActive)
        {
            if (distanceToPlayer <= activationRange)
            {
                ActivateSpawner();
            }
        }
        else
        {
            if (distanceToPlayer > activationRange + 5f)
            {
                DeactivateSpawner();
            }
            HandleSpawning();
        }
    }

    private void HandleSpawning()
    {
        if (spawnCooldown <= 0f)
        {
            SpawnEnemy();
            spawnCooldown = spawnInterval;
        }
    }

    private void UpdateCooldown()
    {
        if (spawnCooldown > 0f)
        {
            spawnCooldown -= Time.deltaTime;
        }
    }

    private void SpawnEnemy()
    {
        GameObject enemy = GetAvailableEnemyFromPool();
        if (enemy == null) return;

        Vector3 spawnPosition = GetSafeSpawnPosition();
        if (spawnPosition == Vector3.zero) return;

        enemy.transform.position = spawnPosition;

        Enemy enemyComponent = enemy.GetComponent<Enemy>();
        if (enemyComponent != null)
        {
            enemyComponent.player = player;
            GameEvents.TriggerEnemySpawn(enemyComponent);
        }

        enemy.SetActive(true);
    }

    private GameObject GetAvailableEnemyFromPool()
    {
        foreach (GameObject enemy in enemyPool)
        {
            if (!enemy.activeInHierarchy)
            {
                return enemy;
            }
        }
        return null;
    }

    private Vector3 GetSafeSpawnPosition()
    {
        Vector3 spawnPosition = Vector3.zero;
        int attempts = 0;
        int maxSpawnAttempts = 10;

        while (attempts < maxSpawnAttempts)
        {
            Vector2 randomCircle = Random.insideUnitCircle * spawnRadius;
            spawnPosition = transform.position + new Vector3(randomCircle.x, 0, randomCircle.y);

            if (player != null && Vector3.Distance(spawnPosition, player.position) < safeSpawnDistanceFromPlayer)
            {
                attempts++;
                continue;
            }

            if (IsSpawnPositionValid(spawnPosition))
            {
                return spawnPosition;
            }

            attempts++;
        }

        return Vector3.zero;
    }

    private bool IsSpawnPositionValid(Vector3 position)
    {
        Collider[] colliders = Physics.OverlapSphere(position, 1f);
        foreach (Collider collider in colliders)
        {
            if (!collider.isTrigger && !collider.CompareTag("Ground"))
            {
                return false;
            }
        }
        return true;
    }

    public void ActivateSpawner()
    {
        if (isActive) return;
        isActive = true;
        spawnCooldown = 0f;
    }

    public void DeactivateSpawner()
    {
        if (!isActive) return;
        isActive = false;
        spawnCooldown = 0f;
    }

    private void FindPlayer()
    {
        GameObject playerObj = GameObject.FindGameObjectWithTag("Player");
        if (playerObj != null)
        {
            player = playerObj.transform;
        }
    }
}
概念 02
概念 02
GameSystem/Achievement/AchievementSys.cs
using System;
using System.Collections.Generic;
using UnityEngine;

public class AchievementSys : MonoBehaviour
{
    public static AchievementSys Instance;

    [System.Serializable]
    public class Achievement
    {
        public string achievementId;
        public string achievementName;
        public string description;
        public bool isUnlocked;
        public DateTime unlockTime;
        public AchievementType type;
        public int progressTarget;
        public int currentProgress;
    }

    [Header("�ɾ��б�")]
    public List<Achievement> achievements = new List<Achievement>();

    public event Action<Achievement> OnAchievementUnlocked;

    public enum AchievementType
    {
        Fans,           // ��˿���
        Combat,         // ս�����
        Progression,    // �������
        Collection,     // �ռ����
        Special         // ����ɾ�
    }

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            InitializeAchievements();
            LoadAchievementData();
        }
        else
        {
            Destroy(gameObject);
        }
    }

    private void InitializeAchievements()
    {
        achievements = new List<Achievement>
        {
            // ��˿��سɾ�
            new Achievement {
                achievementId = "veteran_fan",
                achievementName = "Ԫ�ϼ���˿��",
                description = "ӵ�е�һ����˿",
                type = AchievementType.Fans,
                progressTarget = 1
            },
            new Achievement {
                achievementId = "rising_star",
                achievementName = "��������",
                description = "��˿���ﵽ50��",
                type = AchievementType.Fans,
                progressTarget = 50
            },
            
            // ս����سɾ�
            new Achievement {
                achievementId = "first_kill",
                achievementName = "����������",
                description = "���ܵ�һ������",
                type = AchievementType.Combat,
                progressTarget = 1
            },
            new Achievement {
                achievementId = "zombie_slayer",
                achievementName = "��ʬɱ��",
                description = "����100������",
                type = AchievementType.Combat,
                progressTarget = 100
            },
            
            // ������سɾ�
            new Achievement {
                achievementId = "tutorial_complete",
                achievementName = "��������ҵ",
                description = "������ֽ̳�",
                type = AchievementType.Progression,
                progressTarget = 1
            },
            new Achievement {
                achievementId = "level_10",
                achievementName = "����UPǰ������",
                description = "�ﵽ10��",
                type = AchievementType.Progression,
                progressTarget = 10
            }
        };
    }
    public void UnlockAchievement(string achievementId)
    {
        var achievement = achievements.Find(a => a.achievementId == achievementId);
        if (achievement != null && !achievement.isUnlocked)
        {
            achievement.isUnlocked = true;
            achievement.unlockTime = DateTime.Now;
            OnAchievementUnlocked?.Invoke(achievement);
            GameEvents.TriggerAchievementUnlocked(achievementId);
            SaveAchievementData();
        }
    }
    public void UpdateAchievementProgress(string achievementId, int progressAmount = 1)
    {
        var achievement = achievements.Find(a => a.achievementId == achievementId);
        if (achievement != null && !achievement.isUnlocked)
        {
            achievement.currentProgress += progressAmount;

            if (achievement.currentProgress >= achievement.progressTarget)
            {
                UnlockAchievement(achievementId);
            }
            SaveAchievementData();
        }
    }
    public bool IsAchievementUnlocked(string achievementId)
    {
        var achievement = achievements.Find(a => a.achievementId == achievementId);
        return achievement?.isUnlocked ?? false;
    }
    public float GetAchievementProgress(string achievementId)
    {
        var achievement = achievements.Find(a => a.achievementId == achievementId);
        if (achievement != null)
        {
            return (float)achievement.currentProgress / achievement.progressTarget;
        }
        return 0f;
    }
    private void SaveAchievementData()
    {
        foreach (var achievement in achievements)
        {
            string key = $"Achievement_{achievement.achievementId}";
            PlayerPrefs.SetInt($"{key}_Unlocked", achievement.isUnlocked ? 1 : 0);
            PlayerPrefs.SetInt($"{key}_Progress", achievement.currentProgress);

            if (achievement.isUnlocked)
            {
                PlayerPrefs.SetString($"{key}_UnlockTime", achievement.unlockTime.ToString());
            }
        }
        PlayerPrefs.Save();
    }
    private void LoadAchievementData()
    {
        foreach (var achievement in achievements)
        {
            string key = $"Achievement_{achievement.achievementId}";
            achievement.isUnlocked = PlayerPrefs.GetInt($"{key}_Unlocked", 0) == 1;
            achievement.currentProgress = PlayerPrefs.GetInt($"{key}_Progress", 0);

            string unlockTimeStr = PlayerPrefs.GetString($"{key}_UnlockTime", "");
            if (!string.IsNullOrEmpty(unlockTimeStr))
            {
                DateTime.TryParse(unlockTimeStr, out achievement.unlockTime);
            }
        }
    }

    public void ResetAllAchievements()
    {
        foreach (var achievement in achievements)
        {
            achievement.isUnlocked = false;
            achievement.currentProgress = 0;
        }

        foreach (var achievement in achievements)
        {
            string key = $"Achievement_{achievement.achievementId}";
            PlayerPrefs.DeleteKey($"{key}_Unlocked");
            PlayerPrefs.DeleteKey($"{key}_Progress");
            PlayerPrefs.DeleteKey($"{key}_UnlockTime");
        }
        PlayerPrefs.Save();
    }
    public int GetUnlockedAchievementCount()
    {
        int count = 0;
        foreach (var achievement in achievements)
        {
            if (achievement.isUnlocked) count++;
        }
        return count;
    }

    public int GetTotalAchievementCount()
    {
        return achievements.Count;
    }
}
GameSystem/Achievement/StylishActionSys.cs
using System.Collections.Generic;
using UnityEngine;

public class StylishActionSys : MonoBehaviour
{
    public static StylishActionSys Instance;

    [System.Serializable]
    public class StylishAction
    {
        public string actionId;
        public string displayName;
        public string description;
        public int baseFansReward;
        public int baseFlowReward;
        public float cooldown = 5f;

        [System.NonSerialized]
        public float lastPerformTime = 0f;
    }

    public List<StylishAction> availableActions = new List<StylishAction>
    {
        new StylishAction {
            actionId = "multi_kill",
            displayName = "���ػ�ɱ",
            description = "��ʱ���ڻ�ɱ�������",
            baseFansReward = 5,
            baseFlowReward = 25
        },
        new StylishAction {
            actionId = "no_damage",
            displayName = "����ͨ��",
            description = "��ɹؿ������˺�",
            baseFansReward = 10,
            baseFlowReward = 50
        }
    };

    private Dictionary<string, StylishAction> actionDictionary = new Dictionary<string, StylishAction>();

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            InitializeActions();
        }
        else
        {
            Destroy(gameObject);
        }
    }

    private void InitializeActions()
    {
        foreach (var action in availableActions)
        {
            actionDictionary[action.actionId] = action;
        }
    }

    public bool PerformStylishAction(string actionId, int multiplier = 1)
    {
        if (!actionDictionary.ContainsKey(actionId)) return false;

        var action = actionDictionary[actionId];

        if (Time.time - action.lastPerformTime < action.cooldown)
        {
            return false;
        }

        int fansReward = action.baseFansReward * multiplier;
        int flowReward = action.baseFlowReward * multiplier;

        if (FlowSys.Instance.isInFlowWave)
        {
            fansReward = Mathf.RoundToInt(fansReward * FlowSys.Instance.fansMultiplierInWave);
            flowReward = Mathf.RoundToInt(flowReward * FlowSys.Instance.flowMultiplierInWave);
        }
        FansSys.Instance.AddFans(fansReward, $"stylish_{actionId}");
        FlowSys.Instance.AddFlow(flowReward, $"stylish_{actionId}");
        ExpSys.Instance.AddExp(fansReward, $"stylish_{actionId}");

        action.lastPerformTime = Time.time;
        return true;
    }

    public void OnMultiKill(int killCount)
    {
        int multiplier = Mathf.Min(killCount / 2, 3);
        PerformStylishAction("multi_kill", multiplier);
    }

    public void OnNoDamageComplete()
    {
        PerformStylishAction("no_damage");
    }

    public bool IsActionAvailable(string actionId)
    {
        if (!actionDictionary.ContainsKey(actionId)) return false;

        var action = actionDictionary[actionId];
        return Time.time - action.lastPerformTime >= action.cooldown;
    }
}
GameSystem/Economy/CurrencySys.cs
using System;
using UnityEngine;

public class CurrencySys : MonoBehaviour
{
    public static CurrencySys Instance;

    [Header("����ϵͳ����")]
    [SerializeField] private int currentCurrency = 0;
    [SerializeField] private int totalCurrencyEarned = 0;

    [Header("���һ�ȡ����")]
    public int currencyPerKill = 10;
    public int currencyPerSpecialKill = 25;
    public int currencyPerFanMilestone = 50;
    public int currencyPerLevelUp = 100;

    public event Action<int> OnCurrencyChanged;
    public event Action<int, string> OnCurrencySpent;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            LoadCurrencyData();
        }
        else
        {
            Destroy(gameObject);
        }
    }

    public void AddCurrency(int amount, string source = "default")
    {
        if (amount <= 0) return;

        currentCurrency += amount;
        totalCurrencyEarned += amount;
        OnCurrencyChanged?.Invoke(currentCurrency);
        GameEvents.TriggerCurrencyChanged(currentCurrency);
        SaveCurrencyData();
    }

    public bool SpendCurrency(int amount, string purpose = "purchase")
    {
        if (currentCurrency < amount)
        {
            return false;
        }

        currentCurrency -= amount;
        OnCurrencyChanged?.Invoke(currentCurrency);
        GameEvents.TriggerCurrencyChanged(currentCurrency);
        OnCurrencySpent?.Invoke(amount, purpose);

        SaveCurrencyData();
        return true;
    }

    public void AddCurrencyFromKill(bool isSpecial = false)
    {
        int amount = isSpecial ? currencyPerSpecialKill : currencyPerKill;
        AddCurrency(amount, isSpecial ? "special_kill" : "kill");
    }

    public void AddCurrencyFromFanMilestone()
    {
        AddCurrency(currencyPerFanMilestone, "fan_milestone");
    }

    public void AddCurrencyFromLevelUp()
    {
        AddCurrency(currencyPerLevelUp, "level_up");
    }

    public bool HasEnoughCurrency(int amount)
    {
        return currentCurrency >= amount;
    }

    private void SaveCurrencyData()
    {
        PlayerPrefs.SetInt("PlayerCurrency", currentCurrency);
        PlayerPrefs.SetInt("TotalCurrencyEarned", totalCurrencyEarned);
        PlayerPrefs.Save();
    }

    private void LoadCurrencyData()
    {
        currentCurrency = PlayerPrefs.GetInt("PlayerCurrency", 0);
        totalCurrencyEarned = PlayerPrefs.GetInt("TotalCurrencyEarned", 0);
    }

    public void ResetCurrency()
    {
        currentCurrency = 0;
        totalCurrencyEarned = 0;
        OnCurrencyChanged?.Invoke(currentCurrency);

        PlayerPrefs.DeleteKey("PlayerCurrency");
        PlayerPrefs.DeleteKey("TotalCurrencyEarned");
    }

    public int CurrentCurrency => currentCurrency;
    public int TotalCurrencyEarned => totalCurrencyEarned;
}
概念 01
概念 01
GameSystem/Economy/ExpSys.cs
using System;
using System.Collections.Generic;
using UnityEngine;

public class ExpSys : MonoBehaviour
{
    public static ExpSys Instance;

    [Header("����ϵͳ����")]
    public int currentLevel = 1; 
    public int currentExp = 0;
    public int totalExpEarned = 0;

    [System.Serializable]
    public class LevelData
    {
        public int level;
        public int expRequired;
        public UnlockableContent[] unlocks;
    }

    [System.Serializable]
    public class UnlockableContent
    {
        public string contentId;
        public ContentType type;
        public string displayName;
        public string description;
    }

    [System.Serializable]
    public class StarterReward
    {
        public RewardType type;
        public string itemId;
        public int amount;
        public string description;
    }

    public enum ContentType
    {
        Skill,
        Weapon,
        GameMode,
        SystemFeature,
        LevelAccess,
        ShopItem
    }

    public enum RewardType
    {
        Skill,
        Weapon,
        Currency,
        Consumable,
        Equipment
    }

    [Header("�����ȡ����")]
    public int expPerKill = 5;
    public int expPerSpecialKill = 25;
    public int expPerFlowWave = 10;
    public int expPerFanMilestone = 20;
    public int expPerStylishMove = 3;
    public float fansToExpRatio = 0.1f; // ÿ10��˿��1���飨��������ֵ��

    [Header("�ȼ�����")]
    public LevelData[] levelData = {
        new LevelData {
            level = 1,
            expRequired = 0,
            unlocks = new UnlockableContent[] {
                new UnlockableContent {
                    contentId = "double_jump",
                    type = ContentType.Skill,
                    displayName = "������",
                    description = "��������������"
                },
                new UnlockableContent {
                    contentId = "starter_pistol",
                    type = ContentType.Weapon,
                    displayName = "������ǹ",
                    description = "��û�������"
                },
                new UnlockableContent {
                    contentId = "main_menu",
                    type = ContentType.SystemFeature,
                    displayName = "���˵�",
                    description = "�������˵�����"
                }
            }
        },
        new LevelData {
            level = 2,
            expRequired = 100,
            unlocks = new UnlockableContent[] {
                new UnlockableContent {
                    contentId = "dash_ability",
                    type = ContentType.Skill,
                    displayName = "���",
                    description = "�����������"
                },
                new UnlockableContent {
                    contentId = "shop_access",
                    type = ContentType.SystemFeature,
                    displayName = "�̵�",
                    description = "�����̵깦��"
                }
            }
        },
        new LevelData {
            level = 3,
            expRequired = 300,
            unlocks = new UnlockableContent[] {
                new UnlockableContent {
                    contentId = "level_2",
                    type = ContentType.LevelAccess,
                    displayName = "�ڶ���",
                    description = "�����ڶ��ؿ�"
                }
            }
        }
    };

    [Header("���ֽ̳̽���")]
    public StarterReward[] tutorialRewards = {
        new StarterReward {
            type = RewardType.Skill,
            itemId = "double_jump",
            description = "����������"
        },
        new StarterReward {
            type = RewardType.Weapon,
            itemId = "starter_pistol",
            description = "������ǹ"
        },
        new StarterReward {
            type = RewardType.Currency,
            amount = 100,
            description = "100���"
        },
        new StarterReward {
            type = RewardType.Consumable,
            itemId = "health_potion",
            amount = 3,
            description = "ҽ�ư� x3"
        }
    };

    public event Action<int> OnLevelUp;
    public event Action<int, int> OnExpChanged; 
    public event Action<UnlockableContent> OnContentUnlocked;
    private Dictionary<int, LevelData> levelDataDict = new Dictionary<int, LevelData>();
    private Dictionary<string, bool> unlockedContent = new Dictionary<string, bool>();

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
            return;
        }

        InitializeExpSystem();
    }

    private void InitializeExpSystem()
    {
        levelDataDict.Clear();
        foreach (var data in levelData)
        {
            levelDataDict[data.level] = data;
        }

        unlockedContent.Clear();
        currentLevel = 1; 
        currentExp = 0;
        UnlockLevelContent(1);

        OnExpChanged?.Invoke(currentExp, GetExpRequiredForNextLevel());

    }
    public void AddExp(int amount, string source = "default")
    {
        if (currentLevel >= GetMaxLevel())
        {
            currentExp = GetExpRequiredForLevel(GetMaxLevel());
            OnExpChanged?.Invoke(currentExp, GetExpRequiredForNextLevel());
            return;
        }

        int oldExp = currentExp;
        currentExp += amount;
        totalExpEarned += amount;

        OnExpChanged?.Invoke(currentExp, GetExpRequiredForNextLevel());
        GameEvents.TriggerPlayerExpChanged(currentExp, GetExpRequiredForNextLevel());
        CheckLevelUp(oldExp);
    }

    public void AddExpFromKill(bool isSpecial = false)
    {
        int expAmount = isSpecial ? expPerSpecialKill : expPerKill;
        AddExp(expAmount, isSpecial ? "special_kill" : "kill");
    }

    public void AddExpFromFans(int fansAmount)
    {
        int expAmount = Mathf.RoundToInt(fansAmount * fansToExpRatio);
        if (expAmount > 0)
        {
            AddExp(expAmount, "fans");
        }
    }

    public void AddExpFromFlowWave()
    {
        AddExp(expPerFlowWave, "flow_wave");
    }

    public void AddExpFromStylishMove()
    {
        AddExp(expPerStylishMove, "stylish_move");
    }

    public void AddExpFromFansMilestone()
    {
        AddExp(expPerFanMilestone, "fan_milestone");
    }

    private void CheckLevelUp(int oldExp)
    {
        while (currentLevel < GetMaxLevel() && currentExp >= GetExpRequiredForNextLevel())
        {
            LevelUp();
        }
    }

    private void LevelUp()
    {
        currentLevel++;
        UnlockLevelContent(currentLevel);
        OnLevelUp?.Invoke(currentLevel);
        GameEvents.TriggerPlayerLevelUp(currentLevel);
        if (currentLevel < GetMaxLevel() && currentExp >= GetExpRequiredForNextLevel())
        {
            CheckLevelUp(0);
        }
    }

    private void UnlockLevelContent(int level)
    {
        if (levelDataDict.ContainsKey(level))
        {
            var levelUnlocks = levelDataDict[level].unlocks;
            foreach (var content in levelUnlocks)
            {
                if (!unlockedContent.ContainsKey(content.contentId))
                {
                    unlockedContent[content.contentId] = true;
                    OnContentUnlocked?.Invoke(content);
                    GameEvents.TriggerContentUnlocked(content.contentId);
                    ApplyUnlockedContent(content);
                }
            }
        }
    }
    private void ApplyUnlockedContent(UnlockableContent content)
    {
        // ������Ը����������͵�����Ӧ��ϵͳ
        // ���磺�������ܡ�������ϵͳ���ܵ�
        switch (content.type)
        {
            case ContentType.Skill:
                SkillManager.Instance?.UnlockSkill(content.contentId);
                break;
            case ContentType.Weapon:
                InventorySys.Instance?.AddWeapon(content.contentId);
                break;
            case ContentType.SystemFeature:
                GameProgressManager.Instance?.UnlockSystemFeature(content.contentId);
                break;
            case ContentType.LevelAccess:
                LevelManager.Instance?.UnlockLevel(content.contentId);
                break;
        }
    }

    public void ForceLevelUp(int targetLevel)
    {
        if (targetLevel > currentLevel)
        {
            for (int i = currentLevel + 1; i <= targetLevel; i++)
            {
                currentLevel = i;
                UnlockLevelContent(i);
                OnLevelUp?.Invoke(currentLevel);
            }
            currentExp = GetExpRequiredForLevel(targetLevel);
            OnExpChanged?.Invoke(currentExp, GetExpRequiredForNextLevel());
        }
    }

    public void CompleteTutorial()
    {
        foreach (var reward in tutorialRewards)
        {
            GrantStarterReward(reward);
        }
        ForceLevelUp(1);
        GameEvents.TriggerTutorialCompleted();
    }

    private void GrantStarterReward(StarterReward reward)
    {
        switch (reward.type)
        {
            case RewardType.Skill:
                SkillManager.Instance?.UnlockSkill(reward.itemId);
                break;
            case RewardType.Weapon:
                InventorySys.Instance?.AddWeapon(reward.itemId);
                break;
            case RewardType.Currency:
                CurrencySys.Instance?.AddCurrency(reward.amount);
                break;
            case RewardType.Consumable:
                InventorySys.Instance?.AddItem(reward.itemId, reward.amount);
                break;
            case RewardType.Equipment:
                EquipmentSys.Instance?.UnlockEquipment(reward.itemId);
                break;
        }
    }

    // ���߷���
    public int GetExpRequiredForNextLevel()
    {
        return GetExpRequiredForLevel(currentLevel + 1);
    }

    public int GetExpRequiredForLevel(int level)
    {
        if (level <= 1) return 0; 

        if (levelDataDict.ContainsKey(level))
        {
            return levelDataDict[level].expRequired;
        }
        return int.MaxValue; 
    }

    public int GetMaxLevel()
    {
        return levelData.Length;
    }

    public bool IsContentUnlocked(string contentId)
    {
        return unlockedContent.ContainsKey(contentId) && unlockedContent[contentId];
    }

    public float GetExpProgress()
    {
        if (currentLevel >= GetMaxLevel()) return 1f;

        int currentLevelExp = GetExpRequiredForLevel(currentLevel);
        int nextLevelExp = GetExpRequiredForNextLevel();

        if (nextLevelExp <= currentLevelExp) return 1f;

        return Mathf.Clamp01((float)(currentExp - currentLevelExp) / (nextLevelExp - currentLevelExp));
    }

    public void ResetExp()
    {
        currentLevel = 1; 
        currentExp = 0;
        unlockedContent.Clear();
        UnlockLevelContent(1);
        OnExpChanged?.Invoke(currentExp, GetExpRequiredForNextLevel());
    }

    public string GetLevelInfo()
    {
        return $"�ȼ�: {currentLevel}, ����: {currentExp}/{GetExpRequiredForNextLevel()}";
    }
    public bool HasReachedLevel(int level)
    {
        return currentLevel >= level;
    }
}
GameSystem/Economy/InventorySys.cs
using System;
using System.Collections.Generic;
using UnityEngine;

public class InventorySys : MonoBehaviour
{
    public static InventorySys Instance;

    [System.Serializable]
    public class InventoryItem
    {
        public string itemId;
        public string itemName;
        public string description;
        public int quantity;
        public ItemType type;
        public Sprite icon;
        public bool isEquipped = false;
    }

    [System.Serializable]
    public class WeaponItem : InventoryItem
    {
        public int damage;
        public float fireRate;
        public string weaponPrefabPath;
    }

    [System.Serializable]
    public class ConsumableItem : InventoryItem
    {
        public int healAmount;
        public float duration;
    }

    [Header("�������")]
    public List<InventoryItem> inventoryItems = new List<InventoryItem>();
    public int maxInventorySlots = 20;

    [Header("��ʼ��Ʒ")]
    public string[] startingWeapons = { "starter_pistol" };
    public string[] startingItems = { "health_potion" };

    public event Action<InventoryItem> OnItemAdded;
    public event Action<InventoryItem> OnItemRemoved;
    public event Action<InventoryItem> OnItemEquipped;
    public event Action<InventoryItem> OnItemUsed;

    private Dictionary<string, InventoryItem> itemDictionary = new Dictionary<string, InventoryItem>();

    public enum ItemType
    {
        Weapon,
        Consumable,
        Material,
        Special
    }

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            InitializeInventory();
        }
        else
        {
            Destroy(gameObject);
        }
    }

    private void InitializeInventory()
    {
        itemDictionary.Clear();
        foreach (var weaponId in startingWeapons)
        {
            AddWeapon(weaponId);
        }

        foreach (var itemId in startingItems)
        {
            AddItem(itemId, 1);
        }
    }

    public void AddItem(string itemId, int quantity = 1)
    {
        InventoryItem item = GetOrCreateItem(itemId);

        if (item != null)
        {
            item.quantity += quantity;
            OnItemAdded?.Invoke(item);

        }
    }
    public void AddWeapon(string weaponId)
    {
        WeaponItem weapon = CreateWeaponItem(weaponId);
        if (weapon != null)
        {
            if (itemDictionary.ContainsKey(weaponId))
            {
                itemDictionary[weaponId].quantity++;
            }
            else
            {
                inventoryItems.Add(weapon);
                itemDictionary[weaponId] = weapon;
            }

            OnItemAdded?.Invoke(weapon);
        }
    }
    public bool UseItem(string itemId)
    {
        if (!itemDictionary.ContainsKey(itemId)) return false;

        var item = itemDictionary[itemId];

        if (item.quantity <= 0) return false;

        bool used = ExecuteItemEffect(item);

        if (used)
        {
            item.quantity--;
            OnItemUsed?.Invoke(item);

            if (item.quantity <= 0)
            {
                RemoveItem(itemId);
            }
        }

        return used;
    }
    public void RemoveItem(string itemId)
    {
        if (itemDictionary.ContainsKey(itemId))
        {
            var item = itemDictionary[itemId];
            inventoryItems.Remove(item);
            itemDictionary.Remove(itemId);

            OnItemRemoved?.Invoke(item);
        }
    }

    public void EquipItem(string itemId)
    {
        if (!itemDictionary.ContainsKey(itemId)) return;

        var item = itemDictionary[itemId];

        if (item.type == ItemType.Weapon)
        {
            foreach (var invItem in inventoryItems)
            {
                if (invItem.type == ItemType.Weapon)
                {
                    invItem.isEquipped = false;
                }
            }

            item.isEquipped = true;
            OnItemEquipped?.Invoke(item);
        }
    }
    private bool ExecuteItemEffect(InventoryItem item)
    {
        switch (item.type)
        {
            case ItemType.Consumable:
                var consumable = item as ConsumableItem;
                if (consumable != null)
                {
                    var playerHP = FindObjectOfType<PlayerHPController>();
                    if (playerHP != null)
                    {
                        playerHP.Heal(consumable.healAmount);
                        return true;
                    }
                }
                break;

            case ItemType.Weapon:
                return false;

            default:
                return true;
        }

        return false;
    }

    private InventoryItem GetOrCreateItem(string itemId)
    {
        if (itemDictionary.ContainsKey(itemId))
        {
            return itemDictionary[itemId];
        }

        InventoryItem newItem = CreateItemFromId(itemId);
        if (newItem != null)
        {
            inventoryItems.Add(newItem);
            itemDictionary[itemId] = newItem;
        }

        return newItem;
    }
    private InventoryItem CreateItemFromId(string itemId)
    {
        // ������Ը�����ƷID������Ӧ����Ʒ����
        // ʵ����Ŀ�п��Դ����ñ���ScriptableObject����

        switch (itemId)
        {
            case "health_potion":
                return new ConsumableItem
                {
                    itemId = "health_potion",
                    itemName = "����ҩˮ",
                    description = "�ָ�50������ֵ",
                    type = ItemType.Consumable,
                    healAmount = 50
                };

            case "special_token":
                return new InventoryItem
                {
                    itemId = "special_token",
                    itemName = "�������",
                    description = "���ڶһ�ϡ����Ʒ",
                    type = ItemType.Special
                };

            default:
                return new InventoryItem
                {
                    itemId = itemId,
                    itemName = itemId,
                    description = "δ֪��Ʒ",
                    type = ItemType.Material
                };
        }
    }
    private WeaponItem CreateWeaponItem(string weaponId)
    {
        switch (weaponId)
        {
            case "starter_pistol":
                return new WeaponItem
                {
                    itemId = "starter_pistol",
                    itemName = "������ǹ",
                    description = "�����������ʺ�����ʹ��",
                    type = ItemType.Weapon,
                    damage = 10,
                    fireRate = 1.0f
                };

            default:
                return new WeaponItem
                {
                    itemId = weaponId,
                    itemName = weaponId,
                    description = "δ֪����",
                    type = ItemType.Weapon,
                    damage = 5,
                    fireRate = 1.0f
                };
        }
    }
    public bool HasItem(string itemId)
    {
        return itemDictionary.ContainsKey(itemId) && itemDictionary[itemId].quantity > 0;
    }
    public int GetItemQuantity(string itemId)
    {
        if (itemDictionary.ContainsKey(itemId))
        {
            return itemDictionary[itemId].quantity;
        }
        return 0;
    }
    public WeaponItem GetEquippedWeapon()
    {
        foreach (var item in inventoryItems)
        {
            if (item.type == ItemType.Weapon && item.isEquipped)
            {
                return item as WeaponItem;
            }
        }
        return null;
    }
    public void ResetInventory()
    {
        inventoryItems.Clear();
        itemDictionary.Clear();
        InitializeInventory();
    }
}
GameSystem/Progression/FansSys.cs
using System;
using UnityEngine;
using System.Collections.Generic;

public class FansSys : MonoBehaviour
{
    public static FansSys Instance;

    [Header("��˿ϵͳ����")]
    [SerializeField] private int currentFans = 0;
    [SerializeField] private int totalFansEarned = 0;

    [Header("��˿�׶�����")]
    public FanStage[] fanStages = {
        new FanStage { stageName = "Ԫ�ϼ���˿", fansRequired = 1,
            rewards = new StageReward[] {
                new StageReward { type = RewardType.Currency, amount = 100, description = "�������" },
                new StageReward { type = RewardType.Exp, amount = 50, description = "��������" },
                new StageReward { type = RewardType.Item, itemId = "special_token", amount = 1, description = "����������" }
            }},
        new FanStage { stageName = "������", fansRequired = 50,
            rewards = new StageReward[] {
                new StageReward { type = RewardType.Currency, amount = 500, description = "�еȽ��" },
                new StageReward { type = RewardType.UnlockContent, contentId = "shop_access", description = "�����̵�" }
            }},
        new FanStage { stageName = "�������", fansRequired = 200,
            rewards = new StageReward[] {
                new StageReward { type = RewardType.UnlockContent, contentId = "mission_system", description = "��������ϵͳ" },
                new StageReward { type = RewardType.UnlockContent, contentId = "hard_level_1", description = "�������ѹؿ�1" }
            }},
        new FanStage { stageName = "��������", fansRequired = 1000,
            rewards = new StageReward[] {
                new StageReward { type = RewardType.UnlockContent, contentId = "training_ground", description = "����ѵ����" },
                new StageReward { type = RewardType.UnlockContent, contentId = "expert_levels", description = "����ר�ҹؿ�" }
            }}
    };

    [Header("��˿��ȡ����")]
    public int minFansPerAction = 1;
    public int maxFansPerAction = 5;
    public float stylishMoveFanChance = 0.3f;
    public float killFanChance = 0.1f;
    public float specialKillFanChance = 0.5f;

    public event Action<int> OnFansChanged;
    public event Action<FanStage> OnFanStageReached;
    public event Action<string> OnAchievementUnlocked;

    private int currentStageIndex = 0;
    private bool[] stageAchieved;

    [System.Serializable]
    public class FanStage
    {
        public string stageName;
        public int fansRequired;
        public StageReward[] rewards;
    }

    [System.Serializable]
    public class StageReward
    {
        public RewardType type;
        public string itemId;
        public int amount;
        public string description;
        public string contentId;
    }

    public enum RewardType
    {
        Currency,
        Exp,
        Item,
        UnlockContent
    }

    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            InitializeFansSystem();
        }
        else
        {
            Destroy(gameObject);
        }
    }

    private void InitializeFansSystem()
    {
        stageAchieved = new bool[fanStages.Length];
        CheckStageProgress();
    }

    public void AddFans(int amount, string source = "default")
    {
        if (amount <= 0) return;

        int actualAmount = Mathf.Clamp(amount, minFansPerAction, maxFansPerAction);

        int oldFans = currentFans;
        currentFans += actualAmount;
        totalFansEarned += actualAmount;

        OnFansChanged?.Invoke(currentFans);
        GameEvents.TriggerFansChanged(currentFans);
        CheckAchievements(oldFans);
        CheckStageProgress();
    }

    public void TryAddFansFromStylishMove()
    {
        if (UnityEngine.Random.value <= stylishMoveFanChance)
        {
            int fansAmount = UnityEngine.Random.Range(minFansPerAction, maxFansPerAction + 1);
            AddFans(fansAmount, "stylish_move");
        }
    }

    public void TryAddFansFromKill(bool isSpecial = false)
    {
        float chance = isSpecial ? specialKillFanChance : killFanChance;
        if (UnityEngine.Random.value <= chance)
        {
            int fansAmount = UnityEngine.Random.Range(minFansPerAction, maxFansPerAction + 1);
            AddFans(fansAmount, isSpecial ? "special_kill" : "kill");
        }
    }

    private void CheckAchievements(int oldFans)
    {
        if (oldFans == 0 && currentFans >= 1)
        {
            OnAchievementUnlocked?.Invoke("veteran_fan");
        }
    }

    private void CheckStageProgress()
    {
        for (int i = currentStageIndex; i < fanStages.Length; i++)
        {
            if (!stageAchieved[i] && currentFans >= fanStages[i].fansRequired)
            {
                stageAchieved[i] = true;
                currentStageIndex = i;
                GrantStageRewards(fanStages[i]);
                OnFanStageReached?.Invoke(fanStages[i]);

                Debug.Log($"�ﵽ�·�˿�׶�: {fanStages[i].stageName}");
            }
        }
    }

    private void GrantStageRewards(FanStage stage)
    {
        foreach (var reward in stage.rewards)
        {
            switch (reward.type)
            {
                case RewardType.Currency:
                    CurrencySys.Instance?.AddCurrency(reward.amount);
                    break;

                case RewardType.Exp:
                    ExpSys.Instance?.AddExp(reward.amount, "fan_stage");
                    break;

                case RewardType.Item:
                    InventorySys.Instance?.AddItem(reward.itemId, reward.amount);
                    break;

                case RewardType.UnlockContent:
                    UnlockGameContent(reward.contentId);
                    break;
            }
        }
    }

    private void UnlockGameContent(string contentId)
    {
        switch (contentId)
        {
            case "shop_access":
                GameProgressManager.Instance?.UnlockShop();
                break;

            case "mission_system":
                GameProgressManager.Instance?.UnlockMissionSystem();
                break;

            case "training_ground":
                GameProgressManager.Instance?.UnlockTrainingGround();
                break;

            case "hard_level_1":
            case "expert_levels":
                LevelManager.Instance?.UnlockLevel(contentId);
                break;
        }
    }

    public int CurrentFans => currentFans;
    public int TotalFansEarned => totalFansEarned;
    public FanStage CurrentStage => fanStages[currentStageIndex];
    public int GetCurrentStageIndex()
    {
        return currentStageIndex;
    }

    public void ResetFans()
    {
        currentFans = 0;
        totalFansEarned = 0;
        currentStageIndex = 0;
        stageAchieved = new bool[fanStages.Length];
        OnFansChanged?.Invoke(currentFans);
    }
}
概念 02
概念 02
GameSystem/Progression/FlowSys.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FlowSys : MonoBehaviour
{
    public static FlowSys Instance;

    [Header("����ϵͳ����")]
    [SerializeField] private float currentFlow = 0f;
    [SerializeField] private float maxFlow = 100f;
    [SerializeField] public bool isInFlowWave = false;

    [Header("�����ȳ�����")]
    public float flowWaveThreshold = 80f;
    public float flowDrainRate = 10f;
    public float flowWaveDuration = 10f;
    public float baseFanMultiplier = 1.5f;
    public float baseFlowMultiplier = 2f;
    public float moveSpeedBonus = 1.2f;
    public float jumpHeightBonus = 1.3f;

    [Header("����ѡ������")]
    public UpgradeOption[] upgradeOptions = {
        new UpgradeOption {
            type = UpgradeType.MoveSpeed,
            value = 0.2f,
            name = "�����ƶ�",
            description = "�ƶ��ٶ�����20%",
            icon = null
        },
        new UpgradeOption {
            type = UpgradeType.JumpHeight,
            value = 0.25f,
            name = "������Ծ",
            description = "��Ծ�߶�����25%",
            icon = null
        },
        new UpgradeOption {
            type = UpgradeType.FanGain,
            value = 0.3f,
            name = "��˿����",
            description = "��˿��ȡ����30%",
            icon = null
        },
        new UpgradeOption {
            type = UpgradeType.FlowGain,
            value = 0.4f,
            name = "��������",
            description = "������ȡ����40%",
            icon = null
        }
    };

    public event Action<float> OnFlowChanged;
    public event Action OnFlowWaveStarted;
    public event Action OnFlowWaveEnded;
    public event Action<UpgradeOption[]> OnUpgradeSelection;

    private Dictionary<UpgradeType, float> activeUpgrades = new Dictionary<UpgradeType, float>();
    private float flowWaveTimer = 0f;
    private Coroutine flowWaveCoroutine;

    // ������������������
    public float fansMultiplierInWave => GetFanMultiplier();
    public float flowMultiplierInWave => GetFlowMultiplier();

    [System.Serializable]
    public class UpgradeOption
    {
        public UpgradeType type;
        public float value;
        public string name;
        public string description;
        public Sprite icon;
    }

    public enum UpgradeType
    {
        MoveSpeed,
        JumpHeight,
        FanGain,
        FlowGain
    }

    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    private void Update()
    {
        if (isInFlowWave)
        {
            UpdateFlowWave();
        }
    }

    public void AddFlow(float amount, string source = "default")
    {
        if (isInFlowWave) return;

        float oldFlow = currentFlow;
        currentFlow = Mathf.Min(currentFlow + amount, maxFlow);

        OnFlowChanged?.Invoke(currentFlow);
        GameEvents.TriggerFlowChanged(currentFlow);
        if (!isInFlowWave && currentFlow >= flowWaveThreshold)
        {
            StartFlowWave();
        }
    }

    public void StartFlowWave()
    {
        if (isInFlowWave) return;

        isInFlowWave = true;
        flowWaveTimer = flowWaveDuration;

        ShowUpgradeSelection();
        OnFlowWaveStarted?.Invoke();
        GameEvents.TriggerFlowWaveStarted();
        ExpSys.Instance?.AddExpFromFlowWave();
    }

    private void ShowUpgradeSelection()
    {
        List<UpgradeOption> selectedUpgrades = new List<UpgradeOption>();
        List<UpgradeOption> availableOptions = new List<UpgradeOption>(upgradeOptions);

        for (int i = 0; i < 3 && availableOptions.Count > 0; i++)
        {
            int randomIndex = UnityEngine.Random.Range(0, availableOptions.Count);
            selectedUpgrades.Add(availableOptions[randomIndex]);
            availableOptions.RemoveAt(randomIndex);
        }

        OnUpgradeSelection?.Invoke(selectedUpgrades.ToArray());
    }

    public void ApplyUpgrade(UpgradeOption selectedUpgrade)
    {
        activeUpgrades[selectedUpgrade.type] = selectedUpgrade.value;
        if (flowWaveCoroutine != null) StopCoroutine(flowWaveCoroutine);
        flowWaveCoroutine = StartCoroutine(FlowWaveCountdown());
    }

    private IEnumerator FlowWaveCountdown()
    {
        while (flowWaveTimer > 0 && currentFlow > 0)
        {
            currentFlow -= flowDrainRate * Time.deltaTime;
            currentFlow = Mathf.Max(0, currentFlow);

            OnFlowChanged?.Invoke(currentFlow);
            flowWaveTimer -= Time.deltaTime;

            yield return null;
        }

        EndFlowWave();
    }

    private void UpdateFlowWave()
    {
        if (flowWaveTimer <= 0 || currentFlow <= 0)
        {
            EndFlowWave();
        }
    }

    public void EndFlowWave()
    {
        if (!isInFlowWave) return;

        isInFlowWave = false;
        activeUpgrades.Clear();

        OnFlowWaveEnded?.Invoke();
        GameEvents.TriggerFlowWaveEnded();
        if (flowWaveCoroutine != null)
        {
            StopCoroutine(flowWaveCoroutine);
            flowWaveCoroutine = null;
        }
    }
    public float GetFanMultiplier()
    {
        float multiplier = baseFanMultiplier;
        if (activeUpgrades.ContainsKey(UpgradeType.FanGain))
        {
            multiplier *= (1 + activeUpgrades[UpgradeType.FanGain]);
        }
        return multiplier;
    }

    public float GetFlowMultiplier()
    {
        float multiplier = baseFlowMultiplier;
        if (activeUpgrades.ContainsKey(UpgradeType.FlowGain))
        {
            multiplier *= (1 + activeUpgrades[UpgradeType.FlowGain]);
        }
        return multiplier;
    }

    public float GetMoveSpeedMultiplier()
    {
        float multiplier = moveSpeedBonus;
        if (activeUpgrades.ContainsKey(UpgradeType.MoveSpeed))
        {
            multiplier *= (1 + activeUpgrades[UpgradeType.MoveSpeed]);
        }
        return multiplier;
    }

    public float GetJumpHeightMultiplier()
    {
        float multiplier = jumpHeightBonus;
        if (activeUpgrades.ContainsKey(UpgradeType.JumpHeight))
        {
            multiplier *= (1 + activeUpgrades[UpgradeType.JumpHeight]);
        }
        return multiplier;
    }

    public float CurrentFlow => currentFlow;
    public float MaxFlow => maxFlow;
    public bool IsInFlowWave => isInFlowWave;
    public float FlowWaveProgress => isInFlowWave ? (flowWaveTimer / flowWaveDuration) : 0f;

    public void ResetFlow()
    {
        currentFlow = 0f;
        isInFlowWave = false;
        activeUpgrades.Clear();
        OnFlowChanged?.Invoke(currentFlow);

        if (flowWaveCoroutine != null)
        {
            StopCoroutine(flowWaveCoroutine);
            flowWaveCoroutine = null;
        }
    }
}
GameSystem/Progression/GameProgressManager.cs
using System;
using System.Collections.Generic;
using UnityEngine;

public class GameProgressManager : MonoBehaviour
{
    public static GameProgressManager Instance;

    public bool tutorialCompleted = false;
    public bool shopUnlocked = false;
    public bool missionSystemUnlocked = false;
    public bool trainingGroundUnlocked = false;

    public event Action<string> OnSystemUnlocked;
    public event Action<Enemy, bool> OnEnemyKilledEvent;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            InitializeGameProgress();
        }
        else
        {
            Destroy(gameObject);
        }
    }

    private void InitializeGameProgress()
    {
        tutorialCompleted = false;
        shopUnlocked = false;
        missionSystemUnlocked = false;
        trainingGroundUnlocked = false;
    }

    public void ProcessEnemyKill(Enemy enemy, bool isCombo, bool isSpecial)
    {
        if (enemy == null) return;

        HandleEnemyKillRewards(enemy, isSpecial);

        OnEnemyKilledEvent?.Invoke(enemy, isSpecial);
    }

    private void HandleEnemyKillRewards(Enemy enemy, bool isSpecial)
    {
        ExpSys.Instance?.AddExpFromKill(isSpecial);

        FansSys.Instance?.TryAddFansFromKill(isSpecial);

        //if (enemy.FlowReward > 0)
        //{
        //    FlowSys.Instance?.AddFlow(enemy.FlowReward, "enemy_kill");
        //}

        if (enemy.scoreValue > 0)
        {
            CurrencySys.Instance?.AddCurrency(enemy.scoreValue, "enemy_kill");
        }
    }

    public void CompleteTutorial()
    {
        tutorialCompleted = true;
        ExpSys.Instance?.CompleteTutorial();
        UnlockSystemFeature("main_menu");
        UnlockSystemFeature("basic_movement");
    }

    public void UnlockSystemFeature(string featureId)
    {
        switch (featureId)
        {
            case "shop_access":
                shopUnlocked = true;
                OnSystemUnlocked?.Invoke("�̵깦���ѽ���");
                GameEvents.TriggerSystemUnlocked(featureId);
                break;

            case "mission_system":
                missionSystemUnlocked = true;
                OnSystemUnlocked?.Invoke("����ϵͳ�ѽ���");
                GameEvents.TriggerSystemUnlocked(featureId);
                break;

            case "training_ground":
                trainingGroundUnlocked = true;
                OnSystemUnlocked?.Invoke("ѵ�����ѽ���");
                GameEvents.TriggerSystemUnlocked(featureId);
                break;

            case "main_menu":
                OnSystemUnlocked?.Invoke("���˵��ѽ���");
                GameEvents.TriggerSystemUnlocked(featureId);
                break;

            case "basic_movement":
                OnSystemUnlocked?.Invoke("�����ƶ������ѽ���");
                GameEvents.TriggerSystemUnlocked(featureId);
                break;
        }
    }

    public void UnlockShop()
    {
        UnlockSystemFeature("shop_access");
    }

    public void UnlockMissionSystem()
    {
        UnlockSystemFeature("mission_system");
    }

    public void UnlockTrainingGround()
    {
        UnlockSystemFeature("training_ground");
    }

    public bool IsFeatureUnlocked(string featureId)
    {
        switch (featureId)
        {
            case "shop_access": return shopUnlocked;
            case "mission_system": return missionSystemUnlocked;
            case "training_ground": return trainingGroundUnlocked;
            default: return false;
        }
    }

    public string GetProgressStatus()
    {
        return $"�̳����: {tutorialCompleted}, �̵����: {shopUnlocked}, ����ϵͳ: {missionSystemUnlocked}, ѵ����: {trainingGroundUnlocked}";
    }

    public void ResetProgress()
    {
        tutorialCompleted = false;
        shopUnlocked = false;
        missionSystemUnlocked = false;
        trainingGroundUnlocked = false;
    }
}
GameSystem/Progression/LevelManager.cs
using UnityEngine;

public class LevelManager : MonoBehaviour
{
    public static LevelManager Instance;
    public void UnlockLevel(string levelId)
    {
        // ���ݹؿ�IDִ�в�ͬ�Ľ����߼�
        switch (levelId)
        {
            case "hard_level_1":
                // �������ѹؿ�1���߼�
                GameEvents.TriggerContentUnlocked(levelId);
                break;

            case "expert_levels":
                // ����ר�ҹؿ����߼�
                GameEvents.TriggerContentUnlocked(levelId);
                break;

            default:
                // �������عؿ����߼�
                GameEvents.TriggerContentUnlocked(levelId);
                break;
        }
    }

    public bool IsLevelUnlocked(string levelId)
    {
        // ����Ӧ��ʵ��ʵ�ʵĽ���״̬���
        // ��ʱ����true���ڲ���
        return true;
    }
}
概念 01
概念 01
GameSystem/Upgrade/EquipmentSys.cs
using System.Collections.Generic;
using UnityEngine;

public class EquipmentSys : MonoBehaviour
{
    public static EquipmentSys Instance;

    [System.Serializable]
    public class EquipmentSlot
    {
        public string slotType;
        public string equippedItemId;
    }

    public List<EquipmentSlot> equipmentSlots = new List<EquipmentSlot>();
    public List<string> unlockedEquipment = new List<string>();

    public event System.Action<string> OnEquipmentChanged;
    public event System.Action<string> OnEquipmentUnlocked;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            InitializeEquipmentSlots();
        }
        else
        {
            Destroy(gameObject);
        }
    }

    private void InitializeEquipmentSlots()
    {
        equipmentSlots = new List<EquipmentSlot>
        {
            new EquipmentSlot { slotType = "weapon" },
            new EquipmentSlot { slotType = "armor" },
            new EquipmentSlot { slotType = "accessory" }
        };
    }

    public void UnlockEquipment(string equipmentId)
    {
        if (!unlockedEquipment.Contains(equipmentId))
        {
            unlockedEquipment.Add(equipmentId);
            OnEquipmentUnlocked?.Invoke(equipmentId);
            GameEvents.TriggerContentUnlocked(equipmentId);
        }
    }

    public bool EquipItem(string itemId, string slotType)
    {
        var slot = equipmentSlots.Find(s => s.slotType == slotType);
        if (slot != null && unlockedEquipment.Contains(itemId))
        {
            slot.equippedItemId = itemId;
            OnEquipmentChanged?.Invoke(slotType);
            return true;
        }
        return false;
    }

    public void UnequipItem(string slotType)
    {
        var slot = equipmentSlots.Find(s => s.slotType == slotType);
        if (slot != null)
        {
            slot.equippedItemId = null;
            OnEquipmentChanged?.Invoke(slotType);
        }
    }

    public string GetEquippedItem(string slotType)
    {
        var slot = equipmentSlots.Find(s => s.slotType == slotType);
        return slot?.equippedItemId;
    }

    public void ResetEquipment()
    {
        unlockedEquipment.Clear();
        foreach (var slot in equipmentSlots)
        {
            slot.equippedItemId = null;
        }
    }
}
GameSystem/Upgrade/UpgradeSys.cs
using System.Collections.Generic;
using UnityEngine;

public class UpgradeSys : MonoBehaviour
{
    public static UpgradeSys Instance;

    [System.Serializable]
    public class UpgradeEffect
    {
        public UpgradeType type;
        public float value;
        public string displayName;
        public string description;
    }

    [System.Serializable]
    public class UpgradeOption
    {
        public string name;
        public string description;
        public UpgradeType type;
        public float value;
        public UpgradeRarity rarity;
    }

    public enum UpgradeType
    {
        HealthBoost,
        DamageBoost,
        MoveSpeed,
        JumpHeight,
        SpecialAbility,
        FanGain,
        FlowGain
    }

    public enum UpgradeRarity
    {
        Common,
        Rare,
        Epic,
        Legendary
    }

    private Dictionary<UpgradeType, float> activeUpgrades = new Dictionary<UpgradeType, float>();
    private List<UpgradeOption> currentSelection = new List<UpgradeOption>();

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    public List<UpgradeOption> GenerateUpgradeOptions(int count)
    {
        List<UpgradeOption> allOptions = new List<UpgradeOption>
        {
            new UpgradeOption {
                name = "Ѫ����ǿ",
                description = "�������ֵ����20%",
                type = UpgradeType.HealthBoost,
                value = 0.2f,
                rarity = UpgradeRarity.Common
            },
            new UpgradeOption {
                name = "�˺�����",
                description = "�����˺�����25%",
                type = UpgradeType.DamageBoost,
                value = 0.25f,
                rarity = UpgradeRarity.Common
            },
            new UpgradeOption {
                name = "��������",
                description = "�ƶ��ٶ�����15%",
                type = UpgradeType.MoveSpeed,
                value = 0.15f,
                rarity = UpgradeRarity.Common
            },
            new UpgradeOption {
                name = "��Ծ��ǿ",
                description = "��Ծ�߶�����20%",
                type = UpgradeType.JumpHeight,
                value = 0.2f,
                rarity = UpgradeRarity.Common
            },
            new UpgradeOption {
                name = "������",
                description = "�����Է��䱣���Ե�Ļ",
                type = UpgradeType.SpecialAbility,
                value = 1f,
                rarity = UpgradeRarity.Rare
            },
            new UpgradeOption {
                name = "���󹥻�",
                description = "����ʱ�м��ʴ����������",
                type = UpgradeType.SpecialAbility,
                value = 1f,
                rarity = UpgradeRarity.Rare
            },
            new UpgradeOption {
                name = "�ȵ�׷��",
                description = "�Զ�׷��������ȵ����",
                type = UpgradeType.SpecialAbility,
                value = 1f,
                rarity = UpgradeRarity.Rare
            }
        };

        List<UpgradeOption> selected = new List<UpgradeOption>();
        List<UpgradeOption> available = new List<UpgradeOption>(allOptions);

        for (int i = 0; i < count && available.Count > 0; i++)
        {
            int randomIndex = Random.Range(0, available.Count);
            selected.Add(available[randomIndex]);
            available.RemoveAt(randomIndex);
        }

        currentSelection = selected;
        return selected;
    }

    public void ApplyUpgrade(UpgradeOption upgrade)
    {
        if (activeUpgrades.ContainsKey(upgrade.type))
        {
            activeUpgrades[upgrade.type] += upgrade.value;
        }
        else
        {
            activeUpgrades[upgrade.type] = upgrade.value;
        }
        ExecuteUpgradeEffect(upgrade);
    }

    private void ExecuteUpgradeEffect(UpgradeOption upgrade)
    {
        switch (upgrade.type)
        {
            case UpgradeType.HealthBoost:
                var hpController = FindObjectOfType<PlayerHPController>();
                if (hpController != null)
                {
                    float oldMaxHP = hpController.maxHP;
                    float increase = Mathf.RoundToInt(hpController.maxHP * upgrade.value);
                    hpController.IncreaseMaxHPTem(increase);
                    GameEvents.TriggerPlayerStatChanged("maxHealth", oldMaxHP, hpController.maxHP);
                }
                break;

            case UpgradeType.DamageBoost:
                var shootAbility = FindObjectOfType<ShootATK>();
                if (shootAbility != null)
                {
                    float oldDamage = shootAbility.damage;
                    shootAbility.damage = Mathf.RoundToInt(shootAbility.damage * (1 + upgrade.value));
                    GameEvents.TriggerPlayerStatChanged("damage", oldDamage, shootAbility.damage);
                }
                break;

            case UpgradeType.MoveSpeed:
            var moveAbility = FindObjectOfType<Ability_Move>();
            if (moveAbility != null && moveAbility.core != null)
            {
                float oldSpeed = moveAbility.core.defaultControllerParams.MoveSpeed;
                moveAbility.core.defaultControllerParams.MoveSpeed *= (1 + upgrade.value);
                float newSpeed = moveAbility.core.defaultControllerParams.MoveSpeed;
                GameEvents.TriggerPlayerStatChanged("moveSpeed", oldSpeed, newSpeed);
            }
            break;

        case UpgradeType.JumpHeight:
            var jumpAbility = FindObjectOfType<Ability_Jump>();
            if (jumpAbility != null)
            {
                float oldJumpHeight = jumpAbility.JumpHeight;
                jumpAbility.JumpHeight *= (1 + upgrade.value);
                float newJumpHeight = jumpAbility.JumpHeight;
                GameEvents.TriggerPlayerStatChanged("jumpHeight", oldJumpHeight, newJumpHeight);
            }
            break;

            case UpgradeType.SpecialAbility:
                ActivateSpecialAbility(upgrade.name);
                GameEvents.TriggerContentUnlocked(upgrade.name);
                break;
        }
    }

    private void ActivateSpecialAbility(string abilityName)
    {
        switch (abilityName)
        {
            case "������":
                // ������Ļ����Ч��
                break;
            case "���󹥻�":
                // �������󹥻�Ч��  
                break;
            case "�ȵ�׷��":
                // �����ȵ�׷��Ч��
                break;
        }
    }

    public float GetUpgradeMultiplier(UpgradeType type)
    {
        return activeUpgrades.ContainsKey(type) ? activeUpgrades[type] : 0f;
    }

    public void SelectUpgradeByIndex(int index)
    {
        if (index >= 0 && index < currentSelection.Count)
        {
            ApplyUpgrade(currentSelection[index]);
            currentSelection.Clear();
        }
    }

    public void ClearActiveUpgrades()
    {
        activeUpgrades.Clear();
    }
}
HP/EnemyHealth.cs
using UnityEngine;
using System.Collections;

public class EnemyHealth : MonoBehaviour
{
    public float health;
    public float maxHealth = 100f;
    public System.Action<float> OnHealthChanged;
    public System.Action OnDeath;
    public Material hurtMaterial;
    private Material originalMaterial;
    public Renderer enemyRenderer;
    public float hurtFlashTime = 0.1f;
    public GameObject deathEffect;

    private void Start()
    {
        health = maxHealth;

        if (enemyRenderer == null)
            enemyRenderer = GetComponentInChildren<Renderer>();

        if (enemyRenderer != null)
            originalMaterial = enemyRenderer.material;
    }

    public void ReduceHealth(float amount, Vector3 hitPoint = default(Vector3))
    {
        if (health <= 0) return;

        health = Mathf.Clamp(health - amount, 0, maxHealth);

        StartCoroutine(HurtFlash());
        OnHealthChanged?.Invoke(health);

        if (health <= 0)
        {
            OnDeath?.Invoke();
            HandleDeath();
        }
    }

    private IEnumerator HurtFlash()
    {
        if (enemyRenderer != null && hurtMaterial != null)
        {
            enemyRenderer.material = hurtMaterial;
            yield return new WaitForSeconds(hurtFlashTime);
            enemyRenderer.material = originalMaterial;
        }
    }

    private void HandleDeath()
    {
        if (deathEffect != null)
        {
            Instantiate(deathEffect, transform.position, Quaternion.identity);
        }

    }

    public void ResetHealth()
    {
        health = maxHealth;
        OnHealthChanged?.Invoke(health);
    }
}
概念 02
概念 02
HP/PlayerHPController.cs
using System;
using System.Collections;
using System.ComponentModel;
using UnityEngine;
using UnityEngine.TextCore.Text;

public class PlayerHPController : MonoBehaviour
{
    public static PlayerHPController Instance;

    public float maxHP;
    public float currentHP;
    public float healCooldown = 10f;
    private float lastHealTime = -10f;

    public bool IsDead { get; private set; }
    public System.Action OnPlayerDied;

    public Renderer playerRenderer;
    public float hurtFlashTime = 0.2f;
    private Coroutine hurtFlashCoroutine;
    private Animator animator;
    private void Awake()
    {
        if (Instance == null)
            Instance = this;
        else
            Destroy(gameObject);

        animator = GetComponent<Animator>();
    }

    private void Start()
    {
        IsDead = false;

        if (playerRenderer == null)
        {
            playerRenderer = GetComponentInChildren<Renderer>();
        }

    }

    private void Update()
    {
        HandleHealInput();
    }

    //局外升级HP上限(永久)
    public void IncreaseMaxHPPre(float increaseAmount)
    {
        currentHP = StatisticsManager.Instance.CurrentHealth + increaseAmount;
        maxHP = StatisticsManager.Instance.MaxHealthPre + increaseAmount;

        GameEvents.TriggerPlayerMaxHealthPreChanged(maxHP);
        GameEvents.TriggerPlayerCurrentHealthChanged(currentHP);
    }

    //局内升级HP上限(单局)
    public void IncreaseMaxHPTem(float increaseAmount)
    {
        currentHP = StatisticsManager.Instance.CurrentHealth + increaseAmount;
        maxHP = StatisticsManager.Instance.MaxHealthTem + increaseAmount;

        GameEvents.TriggerPlayerCurrentHealthChanged(currentHP);
        GameEvents.TriggerPlayerMaxHealthTemChanged(maxHP);
    }

    //扣血
    public void TakeDamage(int damage)
    {
        
        if (IsDead) return;
        currentHP = StatisticsManager.Instance.CurrentHealth;
        maxHP = StatisticsManager.Instance.MaxHealth;
        currentHP = Mathf.Clamp(currentHP - damage, 0, maxHP);
        GameEvents.TriggerPlayerCurrentHealthChanged(currentHP);
        if (hurtFlashCoroutine != null)
            StopCoroutine(hurtFlashCoroutine);
        if (currentHP <= 0)
            HandlePlayerDeath();
    }

    private void HandleHealInput()
    {
        if (Input.GetKeyDown(KeyCode.Q) && CanHeal())
        {
            Heal(1);
            lastHealTime = Time.time;
        }
    }

    //回血
    public void Heal(int amount = 1)
    {
        currentHP = StatisticsManager.Instance.CurrentHealth;
        maxHP = StatisticsManager.Instance.MaxHealth;
        currentHP = Mathf.Clamp(currentHP + amount, 0, maxHP);
        GameEvents.TriggerPlayerCurrentHealthChanged(currentHP);
    }

    private bool CanHeal()
    {
        float timeSinceHeal = Time.time - lastHealTime;
        bool isCooldownOver = timeSinceHeal >= healCooldown;
        bool isNotFullHP = currentHP < maxHP;

        return isCooldownOver && isNotFullHP;
    }

    public void HandlePlayerDeath()
    {
        Debug.Log("玩家死亡");
        if (IsDead) return;
        IsDead = true;
        OnPlayerDied?.Invoke();
        animator.SetTrigger("Death");
        animator.Update(0);
        Character character = GetComponent<Character>();
        if (character != null)
        {
            character.Die();
        }
        else
        {
            Debug.LogWarning("PlayerHPController: 找不到组件");
        }
        // 这里可以添加游戏结束逻辑
        // 比如:重新开始场景、显示游戏结束画面等
    }
}
Managers/GameFlowController.cs
using UnityEngine;

// �������̿���ϵͳ
// �˴���ֵ��Ҫ�϶࣬�������ã������õ�AI
// �д�������ʹ��AI�IJ���������ϸ��ע

public class GameFlowController : MonoBehaviour
{
    public static GameFlowController Instance;

    public enum GamePhase
    {
        Tutorial,
        EarlyGame,
        MidGame,
        LateGame,
        BossFight
    }

    public GamePhase currentPhase = GamePhase.Tutorial;

    [Header("�Ѷȵ���")]
    public int fansForMidGame = 50;
    public int fansForLateGame = 100;
    public int fansForBossFight = 200;

    public float enemySpawnRateMultiplier = 1f;
    public float enemyHealthMultiplier = 1f;
    public float enemyDamageMultiplier = 1f;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            SetupEventListeners();
        }
        else
        {
            Destroy(gameObject);
        }
    }

    private void SetupEventListeners()
    {
        FansSys.Instance.OnFansChanged += OnFansChanged;
        FlowSys.Instance.OnFlowWaveStarted += OnFlowWaveStarted;
        FlowSys.Instance.OnFlowWaveEnded += OnFlowWaveEnded;
        ExpSys.Instance.OnLevelUp += OnLevelUp;
    }

    // ���ݷ�˿��������Ϸ�׶�
    private void OnFansChanged(int fans)
    {
        GamePhase newPhase = currentPhase;

        if (fans >= fansForBossFight)
        {
            newPhase = GamePhase.BossFight;
        }
        else if (fans >= fansForLateGame)
        {
            newPhase = GamePhase.LateGame;
        }
        else if (fans >= fansForMidGame)
        {
            newPhase = GamePhase.MidGame;
        }
        else if (fans > 0)
        {
            newPhase = GamePhase.EarlyGame;
        }

        if (newPhase != currentPhase)
        {
            ChangeGamePhase(newPhase);
        }

        AdjustDifficulty(fans);
    }

    private void ChangeGamePhase(GamePhase newPhase)
    {
        GamePhase oldPhase = currentPhase;
        currentPhase = newPhase;
        OnGamePhaseChanged(oldPhase, newPhase);
    }

    private void OnGamePhaseChanged(GamePhase oldPhase, GamePhase newPhase)
    {
        switch (newPhase)
        {
            case GamePhase.EarlyGame:
                // ������������
                break;

            case GamePhase.MidGame:
                // �����м����ܣ����ӵ�������
                break;

            case GamePhase.LateGame:
                // �����߼����ܣ�׼��Bossս
                break;

            case GamePhase.BossFight:
                // ����Bossս
                TriggerBossFight();
                break;
        }
    }

    private void AdjustDifficulty(int fans)
    {
        // ���ڷ�˿����̬�����Ѷ�
        // AI�Ƽ���ƽ�������ߣ�ǰ�������������
        float difficultyCurve = Mathf.Pow(fans / 100f, 0.5f); 

        enemySpawnRateMultiplier = 1f + difficultyCurve * 2f; // 1-3��
        enemyHealthMultiplier = 1f + difficultyCurve * 1.5f;  // 1-2.5��  
        enemyDamageMultiplier = 1f + difficultyCurve * 1f;    // 1-2��

        // Ӧ���Ѷȵ���������������
        //var spawners = FindObjectsOfType<EnemySpawner>();
        //foreach (var spawner in spawners)
        //{
        //    spawner.SetSpawnerParameters(
        //        Mathf.RoundToInt(spawner.maxEnemies * enemySpawnRateMultiplier),
        //        spawner.spawnInterval / enemySpawnRateMultiplier
        //    );
        //}
    }

    private void OnFlowWaveStarted()
    {
        // �����ȳ��ڼ��ȫ��Ч��
        // �����������
        var moveAbility = FindObjectOfType<Ability_Move>();
        if (moveAbility != null)
        {
            // ��ʱ�����ƶ��ٶȵ�
        }
    }

    private void OnFlowWaveEnded()
    {
        // �ȳ�����
    }

    private void OnLevelUp(int level)
    {
        // �ȼ�������ȫ��Ч��
        // ���ڵȼ�΢���Ѷ�
        float levelBonus = level * 0.05f; // ÿ������5%�Ѷ�
        enemySpawnRateMultiplier += levelBonus;
        enemyHealthMultiplier += levelBonus;
    }

    private void TriggerBossFight()
    {
        // ����Boss����
        // �����������Bossս�ض����߼�
        // ������������Boss���ˣ��ı䱳�����ֵ�
    }

    public void StartTutorialPhase()
    {
        currentPhase = GamePhase.Tutorial;
        // ��ʼ���̳��ض�������
        enemySpawnRateMultiplier = 0.5f; // �̳̽׶ν����Ѷ�
    }

    public void CompleteTutorial()
    {
        if (currentPhase == GamePhase.Tutorial)
        {
            ChangeGamePhase(GamePhase.EarlyGame);
        }
    }
}
Managers/SettingsManager.cs
using System;
using UnityEditor;
using UnityEngine;

// ����������
[System.Serializable]
public class GameSettingsData
{
    // ��Ƶ����
    public float masterVolume = 1.0f;
    public float musicVolume = 1.0f;
    public float sfxVolume = 1.0f;
    public bool musicEnabled = true;
    public bool sfxEnabled = true;

    // �������ã�������߻����8��������
    public KeyCode moveForwardKey = KeyCode.W;
    public KeyCode moveBackKey = KeyCode.S;
    public KeyCode moveLeftKey = KeyCode.A;
    public KeyCode moveRightKey = KeyCode.D;
    public KeyCode jumpKey = KeyCode.Space;
    public KeyCode interactKey = KeyCode.E;
    public KeyCode skillKey = KeyCode.Q;
    public KeyCode likeKey = KeyCode.F;

    // ��������
    public int resolutionIndex = 5;
    public Vector2Int resolution = new Vector2Int(1920, 1080);
    public readonly Vector2Int[] resolutions = new Vector2Int[]
    {
        new(800, 600),
        new(1024, 768),
        new(1280, 720),  // HD
        new(1366, 768),
        new(1600, 900),
        new(1920, 1080), // Full HD
        new(2560, 1440)
    };

    public int qualityLevel = 2; // ���
    public bool isFullscreen = true;
    public float brightness = 0.8f;
}

// �������ù�����
public class SettingsManager : MonoBehaviour
{
    public static SettingsManager Instance { get; private set; }

    // ��ǰ��������
    private GameSettingsData _currentSettings;

    // �¼����壨����ʵʱ֪ͨ����ϵͳ��
    public event Action OnSettingsChanged;
    public event Action<float> OnMasterVolumeChanged;
    public event Action<KeyCode, string> OnKeyBindingChanged;

    // ������
    private const string SETTINGS_SAVE_KEY = "GameSettings_V1";

    void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }

        Instance = this;
        DontDestroyOnLoad(gameObject);
        LoadSettings();
    }

    #region �����ӿ� - ��ˮ��͡��UI�е���

    // ��Ƶ��������
    public float MasterVolume
    {
        get => _currentSettings.masterVolume;
        set
        {
            _currentSettings.masterVolume = Mathf.Clamp01(value);
            SaveSettings();
            OnMasterVolumeChanged?.Invoke(_currentSettings.masterVolume);
            OnSettingsChanged?.Invoke();
        }
    }

    public float MusicVolume
    {
        get => _currentSettings.musicVolume;
        set
        {
            _currentSettings.musicVolume = Mathf.Clamp01(value);
            SaveSettings();
            OnSettingsChanged?.Invoke();
        }
    }

    public bool MusicEnabled
    {
        get => _currentSettings.musicEnabled;
        set
        {
            _currentSettings.musicEnabled = value;
            SaveSettings();
            OnSettingsChanged?.Invoke();
        }
    }

    // ������������
    public KeyCode MoveForwardKey
    {
        get => _currentSettings.moveForwardKey;
        set
        {
            _currentSettings.moveForwardKey = value;
            SaveSettings();
            OnKeyBindingChanged?.Invoke(value, "MoveForward");
            OnSettingsChanged?.Invoke();
        }
    }

//����Ϊˮ��͡���µĹ������÷���

    public KeyCode MoveBackKey
    {
        get => _currentSettings.moveBackKey;
        set
        {
            _currentSettings.moveBackKey = value;
            SaveSettings();
            OnKeyBindingChanged?.Invoke(value, "MoveBack");
            OnSettingsChanged?.Invoke();
        }
    }

    public KeyCode MoveLeftKey
    {
        get => _currentSettings.moveLeftKey;
        set
        {
            _currentSettings.moveLeftKey = value;
            SaveSettings();
            OnKeyBindingChanged?.Invoke(value, "MoveLeft");
            OnSettingsChanged?.Invoke();
        }
    }

    public KeyCode MoveRightKey
    {
        get => _currentSettings.moveRightKey;
        set
        {
            _currentSettings.moveRightKey = value;
            SaveSettings();
            OnKeyBindingChanged?.Invoke(value, "MoveRight");
            OnSettingsChanged?.Invoke();
        }
    }

    public KeyCode JumpKey
    {
        get => _currentSettings.jumpKey;
        set
        {
            _currentSettings.jumpKey = value;
            SaveSettings();
            OnKeyBindingChanged?.Invoke(value, "Jump");
            OnSettingsChanged?.Invoke();
        }
    }

    public KeyCode InteractKey
    {
        get => _currentSettings.interactKey;
        set
        {
            _currentSettings.interactKey = value;
            SaveSettings();
            OnKeyBindingChanged?.Invoke(value, "Interact");
            OnSettingsChanged?.Invoke();
        }
    }

    public KeyCode SkillKey
    {
        get => _currentSettings.skillKey;
        set
        {
            _currentSettings.skillKey = value;
            SaveSettings();
            OnKeyBindingChanged?.Invoke(value, "Skill");
            OnSettingsChanged?.Invoke();
        }
    }

    public KeyCode LikeKey
    {
        get => _currentSettings.likeKey;
        set
        {
            _currentSettings.likeKey = value;
            SaveSettings();
            OnKeyBindingChanged?.Invoke(value, "Like");
            OnSettingsChanged?.Invoke();
        }
    }

    public float SfxVolume
    {
        get => _currentSettings.sfxVolume;
        set
        {
            _currentSettings.sfxVolume= Mathf.Clamp01(value);
            SaveSettings();
            OnSettingsChanged?.Invoke();
        }
    }

    public int ResolutionIndex
    {
        get => _currentSettings.resolutionIndex;
        set
        {
            _currentSettings.resolutionIndex = value;
            _currentSettings.resolution = _currentSettings.resolutions[value];
            SaveSettings();
            OnSettingsChanged?.Invoke();
            Screen.SetResolution(_currentSettings.resolution.x, _currentSettings.resolution.y, _currentSettings.isFullscreen);
        }
    }

//ˮ��͡�ĸ��½��������滹��һ�䣩

    // ������������
    public int QualityLevel
    {
        get => _currentSettings.qualityLevel;
        set
        {
            _currentSettings.qualityLevel = value;
            QualitySettings.SetQualityLevel(value);
            SaveSettings();
            OnSettingsChanged?.Invoke();
        }
    }

    public bool IsFullscreen
    {
        get => _currentSettings.isFullscreen;
        set
        {
            _currentSettings.isFullscreen = value;
            Screen.fullScreen = value;
            SaveSettings();
            OnSettingsChanged?.Invoke();
            //ˮ��͡����������һ��
            Screen.SetResolution(_currentSettings.resolution.x, _currentSettings.resolution.y, _currentSettings.isFullscreen);

        }
    }

    #endregion

    #region ���ݳ־û�

    private void SaveSettings()
    {
        try
        {
            string jsonData = JsonUtility.ToJson(_currentSettings);
            PlayerPrefs.SetString(SETTINGS_SAVE_KEY, jsonData);
            PlayerPrefs.Save();
            Debug.Log("�����ѱ���");
        }
        catch (System.Exception e)
        {
            Debug.LogError($"��������ʧ��: {e.Message}");
        }
    }

    private void LoadSettings()
    {
        try
        {
            if (PlayerPrefs.HasKey(SETTINGS_SAVE_KEY))
            {
                string jsonData = PlayerPrefs.GetString(SETTINGS_SAVE_KEY);
                _currentSettings = JsonUtility.FromJson<GameSettingsData>(jsonData);
                Debug.Log("���ü��سɹ�");
            }
            else
            {
                _currentSettings = new GameSettingsData();
                SaveSettings();
                Debug.Log("����Ĭ������");
            }

            // Ӧ�ü��ص�����
            ApplyCurrentSettings();
        }
        catch (System.Exception e)
        {
            Debug.LogError($"��������ʧ��: {e.Message}");
            _currentSettings = new GameSettingsData();
        }
    }

    // Ӧ�õ�ǰ���õ���Ϸϵͳ
    private void ApplyCurrentSettings()
    {
        // Ӧ�û�������
        QualitySettings.SetQualityLevel(_currentSettings.qualityLevel);
        Screen.fullScreen = _currentSettings.isFullscreen;

        // ��Ƶ���û���AudioSystem��ͨ���¼���Ӧ
    }

    #endregion

    #region ��������

    // ����ΪĬ������
    public void ResetToDefault()
    {
        _currentSettings = new GameSettingsData();
        SaveSettings();
        ApplyCurrentSettings();
        OnSettingsChanged?.Invoke();
        Debug.Log("������ΪĬ������");
    }

    // ��ȡ�����������ݣ�������ϵͳʹ�ã�
    public GameSettingsData GetCurrentSettings()
    {
        return _currentSettings;
    }

    // ���ý����еĵ���ʾ��
    public void ApplySettingsImmediately()
    {
        SaveSettings();
        OnSettingsChanged?.Invoke();
        Debug.Log("����������Ӧ��");
    }

    #endregion
}
概念 01
概念 01
Managers/SkillManager.cs
using System.Collections.Generic;
using UnityEngine;

public class SkillManager : MonoBehaviour
{
    public static SkillManager Instance;

    [System.Serializable]
    public class SkillData
    {
        public string skillId;
        public string skillName;
        public string description;
        public bool isUnlocked;
        public int requiredLevel;
    }

    public List<SkillData> skills = new List<SkillData>();

    public event System.Action<string> OnSkillUnlocked;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            InitializeSkills();
        }
        else
        {
            Destroy(gameObject);
        }
    }

    private void InitializeSkills()
    {
        // ��ʼ�������б�
        skills = new List<SkillData>
        {
            new SkillData { skillId = "double_jump", skillName = "������", description = "�����ڿ����ٴ���Ծ", requiredLevel = 1 },
            new SkillData { skillId = "dash_ability", skillName = "���", description = "������ǰ���һ�ξ���", requiredLevel = 2 },
            new SkillData { skillId = "wall_jump", skillName = "��ǽ��", description = "������ǽ����Ծ", requiredLevel = 3 }
        };
    }

    public void UnlockSkill(string skillId)
    {
        var skill = skills.Find(s => s.skillId == skillId);
        if (skill != null && !skill.isUnlocked)
        {
            skill.isUnlocked = true;
            OnSkillUnlocked?.Invoke(skillId);
            GameEvents.TriggerContentUnlocked(skillId);
            // �����Ӧ�ļ������
            ActivateSkillComponent(skillId);
        }
    }

    public bool IsSkillUnlocked(string skillId)
    {
        var skill = skills.Find(s => s.skillId == skillId);
        return skill?.isUnlocked ?? false;
    }

    private void ActivateSkillComponent(string skillId)
    {
        switch (skillId)
        {
            case "double_jump":
                var player = FindObjectOfType<Character>();
                if (player != null)
                {
                    var jumpAbility = player.GetComponent<Ability_Jump>();
                    if (jumpAbility != null)
                    {
                        jumpAbility.JumpNum = 2; 
                    }
                }
                break;
        }
    }

    public void ResetSkills()
    {
        foreach (var skill in skills)
        {
            skill.isUnlocked = false;
        }
    }
}
Managers/TutorialManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TutorialManager : MonoBehaviour
{
    public static TutorialManager Instance;

    [System.Serializable]
    public class TutorialStep
    {
        public string stepName;
        public string instruction;
        public string triggerEvent; 
        public bool isCompleted = false;
        public float timeout = 30f; // ���賬ʱʱ��
    }

    [Header("Tutorial Configuration")]
    public List<TutorialStep> tutorialSteps = new List<TutorialStep>();
    public bool tutorialCompleted = false;

    [Header("Tutorial Rewards")]
    public int tutorialExpReward = 100;
    public int tutorialCurrencyReward = 200;

    private int currentStepIndex = 0;
    private Coroutine tutorialCoroutine;
    private Coroutine currentStepCoroutine;

    private Dictionary<string, System.Action> stepCompletionHandlers = new Dictionary<string, System.Action>();

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
            
            // �ȼ���Ƿ���Ҫ���ý̳�״̬�����ڲ��ԣ�
            if (PlayerPrefs.HasKey("TutorialCompleted"))
            {
                Debug.Log("[TutorialManager] Tutorial was completed before, resetting for new test");
                ResetTutorial();
            }
            
            InitializeTutorial();
            RegisterEventHandlers();
        }
        else
        {
            Destroy(gameObject);
        }
    }

    private void InitializeTutorial()
    {
        tutorialSteps = new List<TutorialStep>
        {
            new TutorialStep {
                stepName = "Move",
                instruction = "Use WASD keys to move your character",
                triggerEvent = "player_moved",
                timeout = 20f
            },
            new TutorialStep {
                stepName = "Jump",
                instruction = "Press SPACE to jump",
                triggerEvent = "player_jumped",
                timeout = 15f
            },
            new TutorialStep {
                stepName = "Attack",
                instruction = "Use LEFT MOUSE BUTTON to attack enemies",
                triggerEvent = "player_attacked",
                timeout = 25f
            },
            new TutorialStep {
                stepName = "Reload",
                instruction = "Use R to reload your weapon",
                triggerEvent = "player_reloaded",
                timeout = 25f
            },
            /*��ʱ������ϵͳ��ˮ��͡��
            new TutorialStep {
                stepName = "Fans System Introduction",
                instruction = "Gain fans through stylish moves, more fans unlock more content",
                triggerEvent = "fans_explained",
                timeout = 15f
            },
            new TutorialStep {
                stepName = "Flow System Introduction",
                instruction = "Build up flow to trigger flow waves for powerful bonuses",
                triggerEvent = "flow_explained",
                timeout = 15f
            }
            */
        };
    }

    private void RegisterEventHandlers()
    {
        GameEvents.OnPlayerAction += OnPlayerAction;
        stepCompletionHandlers["player_moved"] = () => CompleteCurrentStep();
        stepCompletionHandlers["player_jumped"] = () => CompleteCurrentStep();
        stepCompletionHandlers["player_attacked"] = () => CompleteCurrentStep();
        stepCompletionHandlers["player_reloaded"] = () => CompleteCurrentStep();
        //��ʱ������ϵͳ��ˮ��͡��
        //stepCompletionHandlers["fans_explained"] = () => CompleteCurrentStep();
        //stepCompletionHandlers["flow_explained"] = () => CompleteCurrentStep();
    }

    private void OnPlayerAction(string action)
    {
        if (!tutorialCompleted && currentStepIndex < tutorialSteps.Count)
        {
            var currentStep = tutorialSteps[currentStepIndex];
            string expectedEvent = $"player_{action}";
            if (currentStep.triggerEvent == expectedEvent && !currentStep.isCompleted)
            {
                stepCompletionHandlers[currentStep.triggerEvent]?.Invoke();
            }
        }
    }

    public void StartTutorial()
    {
        if (tutorialCompleted)
        {
            return;
        }

        if (tutorialCoroutine != null)
            StopCoroutine(tutorialCoroutine);

        tutorialCoroutine = StartCoroutine(RunTutorialSequence());
    }

    private IEnumerator RunTutorialSequence()
    {
        for (currentStepIndex = 0; currentStepIndex < tutorialSteps.Count; currentStepIndex++)
        {
            var currentStep = tutorialSteps[currentStepIndex];
            yield return StartCoroutine(ExecuteTutorialStep(currentStep));
        }
        CompleteTutorial();
    }

    private IEnumerator ExecuteTutorialStep(TutorialStep step)
    {
        GameEvents.TriggerTutorialStepStarted(step.stepName);

        // ��ʾUI��ʾ
        // UIManager.Instance.ShowTutorialPrompt(step.instruction);
        // ��Ǹ����û�������ˮ��͡��

        bool stepCompleted = false;
        float stepStartTime = Time.time;

        while (!stepCompleted && Time.time - stepStartTime < step.timeout)
        {
            if (step.isCompleted)
            {
                Debug.Log($"[ExecuteTutorialStep] Step completed by player action: {step.stepName}");
                stepCompleted = true;
            }
            yield return null;
        }

        if (!stepCompleted)
        {
            // ����ѡ���Զ���ɻ������ʾ
            Debug.Log($"[ExecuteTutorialStep] Step timed out: {step.stepName}");
            step.isCompleted = true; 
        }

        Debug.Log($"[ExecuteTutorialStep] Triggering step completed: {step.stepName}");
        GameEvents.TriggerTutorialStepCompleted(step.stepName);
    }

    private void CompleteCurrentStep()
    {
        if (currentStepIndex < tutorialSteps.Count)
        {
            var currentStep = tutorialSteps[currentStepIndex];
            if (!currentStep.isCompleted)
            {
                currentStep.isCompleted = true;
                Debug.Log($"Step completed: {currentStep.stepName}");
            }
        }
    }

    public void CompleteTutorial()
    {
        if (tutorialCompleted) return;

        tutorialCompleted = true;
        GrantTutorialRewards();

        GameEvents.TriggerTutorialCompleted();
        PlayerPrefs.SetInt("TutorialCompleted", 1);
        PlayerPrefs.Save();
    }

    private void GrantTutorialRewards()
    {
        if (ExpSys.Instance != null)
            ExpSys.Instance.AddExp(tutorialExpReward, "tutorial");
        else
            Debug.LogError("ExpSys instance not found!");

        if (CurrencySys.Instance != null)
            CurrencySys.Instance.AddCurrency(tutorialCurrencyReward, "tutorial");
        else
            Debug.LogError("CurrencySys instance not found!");

        if (SkillManager.Instance != null)
        {
            SkillManager.Instance.UnlockSkill("double_jump");
        }

        if (GameProgressManager.Instance != null)
        {
            GameProgressManager.Instance.CompleteTutorial();
        }
    }

    // �ֶ������̳̲������
    // ������ϵͳ����
    public void CompleteTutorialStep(string stepName)
    {
        var step = tutorialSteps.Find(s => s.stepName == stepName);
        if (step != null && !step.isCompleted)
        {
            step.isCompleted = true;
            GameEvents.TriggerTutorialStepCompleted(stepName);
        }
    }

    private void OnDestroy()
    {
        GameEvents.OnPlayerAction -= OnPlayerAction;

        if (tutorialCoroutine != null)
            StopCoroutine(tutorialCoroutine);

        if (currentStepCoroutine != null)
            StopCoroutine(currentStepCoroutine);
    }

    public bool ShouldStartTutorial()
    {
        return !PlayerPrefs.HasKey("TutorialCompleted");
    }

    public void ResetTutorial()
    {
        tutorialCompleted = false;
        currentStepIndex = 0;

        foreach (var step in tutorialSteps)
        {
            step.isCompleted = false;
        }

        PlayerPrefs.DeleteKey("TutorialCompleted");
    }

    [ContextMenu("Skip Tutorial")]
    public void SkipTutorial()
    {
        if (tutorialCoroutine != null)
            StopCoroutine(tutorialCoroutine);

        CompleteTutorial();
        Debug.Log("Tutorial skipped");
    }
}
Others/Background_Parallax.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Background_Parallax : MonoBehaviour
{
    [Tooltip("视差效果乘数:越近的层,这个值应该越大。")]
    public float parallaxEffectMultiplier = 0.5f;
    
    [Tooltip("平滑移动的插值速度")]
    public float smoothSpeed = 5f;
    
    [Tooltip("是否启用Y轴视差效果")]
    public bool enableYParallax = true;

    public Transform targetObject;
    private Vector3 lastTargetPosition;
    private Vector3 targetPosition;

    void Start()
    {
        lastTargetPosition = targetObject.position;
        targetPosition = transform.position;
    }

    void LateUpdate()
    {
        Vector3 deltaMovement = targetObject.position - lastTargetPosition;
        
        float parallaxX = deltaMovement.x * parallaxEffectMultiplier;
        float parallaxY = enableYParallax ? deltaMovement.y * parallaxEffectMultiplier : 0f;
        
        targetPosition += new Vector3(parallaxX, parallaxY, 0);
        
        transform.position = Vector3.Lerp(transform.position, targetPosition, smoothSpeed * Time.deltaTime);
        
        lastTargetPosition = targetObject.position;
    }
}
概念 02
概念 02
Others/ShadowCotroller.cs
// URPShadowProjector.cs - URPר�õ�ͶӰ������
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class URPShadowProjector : MonoBehaviour
{
    [Header("URP DecalͶӰ����")]
    public DecalProjector decalProjector;
    public Transform target;
    public LayerMask groundMask = 1;

    [Header("��̬Ч��")]
    public float sizeSmoothness = 0.1f;
    public float maxSize = 2f;
    public float minSize = 0.5f;
    public float maxDistance = 8f;
    public AnimationCurve sizeCurve = AnimationCurve.Linear(0, 1, 1, 0.3f);

    private float currentSize;
    private Material decalMaterial;

    void Start()
    {
        if (target == null)
            target = transform.parent;

        if (decalProjector == null)
            decalProjector = GetComponent<DecalProjector>();

        // ������̬����ʵ��
        if (decalProjector != null && decalProjector.material != null)
        {
            decalMaterial = new Material(decalProjector.material);
            decalProjector.material = decalMaterial;
        }

        currentSize = maxSize;
    }

    void Update()
    {
        if (target == null || decalProjector == null) return;

        UpdateProjectorPosition();
        UpdateProjectorSize();
        UpdateProjectorOpacity();
    }

    void UpdateProjectorPosition()
    {
        // ��DecalͶӰ�����ڽ�ɫλ��
        transform.position = target.position;

        // ������߶�������ͶӰλ��
        RaycastHit hit;
        if (Physics.Raycast(target.position, Vector3.down, out hit, maxDistance, groundMask))
        {
            // ����ͶӰ���߶������ϵ���
            Vector3 position = transform.position;
            position.y = hit.point.y + 0.1f; // ��΢���ڵ���
            transform.position = position;
        }
    }

    void UpdateProjectorSize()
    {
        // ����ɫ��ظ߶�
        RaycastHit hit;
        float height = maxDistance;
        if (Physics.Raycast(target.position, Vector3.down, out hit, maxDistance, groundMask))
        {
            height = hit.distance;
        }

        // ���ݸ߶ȵ���ͶӰ��С
        float heightFactor = Mathf.Clamp01(height / maxDistance);
        float targetSize = Mathf.Lerp(maxSize, minSize, sizeCurve.Evaluate(heightFactor));
        currentSize = Mathf.Lerp(currentSize, targetSize, sizeSmoothness);

        // ����DecalͶӰ����С
        decalProjector.size = new Vector3(currentSize, currentSize, decalProjector.size.z);
    }

    void UpdateProjectorOpacity()
    {
        if (decalMaterial == null) return;

        // ���ݸ߶ȵ���͸����
        RaycastHit hit;
        float height = maxDistance;
        if (Physics.Raycast(target.position, Vector3.down, out hit, maxDistance, groundMask))
        {
            height = hit.distance;
        }

        float opacity = Mathf.Lerp(1f, 0.2f, height / maxDistance);
        decalMaterial.SetFloat("_Alpha", opacity);
    }

    void OnDestroy()
    {
        // ������̬����
        if (decalMaterial != null)
        {
            DestroyImmediate(decalMaterial);
        }
    }

#if UNITY_EDITOR
    void OnDrawGizmosSelected()
    {
        if (target == null) return;

        // ���ӻ�����
        Gizmos.color = Color.blue;
        Gizmos.DrawLine(target.position, target.position + Vector3.down * maxDistance);

        if (decalProjector != null)
        {
            Gizmos.color = Color.green;
            Gizmos.DrawWireCube(transform.position, new Vector3(decalProjector.size.x, 0.1f, decalProjector.size.y));
        }
    }
#endif
}
Statistics/StatisticsManager.cs
using System;
using UnityEngine;
using System.Collections.Generic;

// 命名提醒:"session"开头代表单局变量

#region 数据类
[System.Serializable]

public class TemporaryData
{
    public float currentHealth;          // 当前生命值   
    public float maxHealth_tem;              // 生命值上限[暂时]
    public float defense_tem;                // 防御值[暂时]
    public float moveSpeed_tem;              // 移动速度[暂时]
    public float attackSpeed_tem;            // 攻击速度[暂时]
    public float attackDamage_tem;           // 攻击力[暂时]

    public float maxHealth;              // 生命值上限[总计]
    public float defense;                // 防御值[总计]
    public float moveSpeed;              // 移动速度[总计]
    public float attackSpeed;            // 攻击速度[总计]
    public float attackDamage;           // 攻击力[总计]

    [Header("单局统计")]
    public float sessionTrafficEarned;      // 本局流量增加
    public int sessionFansGained;         // 本局粉丝增加
    public int sessionKillCount;          // 本局击杀数
    public List<string> sessionAchievements = new List<string>(); // 本局完成的成就名称列表

    public int sessionTrafficTime;             // 本局(用流量的)升级次数

}


public class StatisticsData
{
    [Header("战斗属性")]
    public float maxHealth_pre;              // 生命值上限[永久]
    public float defense_pre;                // 防御值[永久]
    public float moveSpeed_pre;              // 移动速度[永久]
    public float attackSpeed_pre;            // 攻击速度[永久]
    public float attackDamage_pre;           // 攻击力[永久]

    [Header("直播相关")]
    public int liveViewerCount;           // 直播间观众数(类似于幸运)
    public float traffic;                   // 流量(局内升级货币)
    public int gold;                      // 金币(局外升级货币)
    public int totalFans;                 // 粉丝总数

    [Header("永久化数据")]
    public int totalKills;                // 历史总击杀
    public int totalSessionsPlayed;       // 总游戏局数
    public int highestViewerCount;        // 历史最高观众数
    public List<string> unlockedAchievements = new List<string>();  // 已解锁的成就名称列表

}

#endregion 

public class StatisticsManager : MonoBehaviour
{
    public static StatisticsManager Instance { get; private set; }

    // 当前统计数据
    private StatisticsData _currentStats;
    private TemporaryData _temporaryStats;

    // 事件定义(用于实时通知其他系统)
    public event Action OnStatisticsUpdated;
    public event Action<float> OnTrafficChanged;
    public event Action<int> OnGoldChanged;
    public event Action<int> OnKillCountChanged;
    public event Action<int> OnViewerCountChanged;

    // 保存的键名
    private const string STATS_SAVE_KEY = "GameStatistics_V1";

    void Awake()
    {
        _temporaryStats = new TemporaryData();

        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }

        Instance = this;
        DontDestroyOnLoad(gameObject);
        LoadStatistics();
    }

    #region 初始值
    // 即“新建统计存档”,有两种自动调用情况:
    // 1 游戏启动时,若没有存档会被自动调用
    // 2 运行“重置”后会被自动调用
    private void InitializeStats()
    {
        // 战斗属性
        _temporaryStats.currentHealth = 500f; //当前生命值

        _currentStats.maxHealth_pre = 500f;     //生命值上限
        _currentStats.defense_pre = 0f;         //防御值
        _currentStats.moveSpeed_pre = 5f;       //移动速度
        _currentStats.attackSpeed_pre = 1f;     //攻击速度
        _currentStats.attackDamage_pre = 10f;   //攻击力

        _temporaryStats.maxHealth_tem = 0f;     //生命值上限
        _temporaryStats.defense_tem = 0f;         //防御值
        _temporaryStats.moveSpeed_tem = 0f;       //移动速度
        _temporaryStats.attackSpeed_tem = 0f;     //攻击速度
        _temporaryStats.attackDamage_tem = 0f;   //攻击力

        // 单局统计
        _currentStats.traffic = 0;          //流量(局内升级货币)
        _temporaryStats.sessionTrafficEarned = 0;       //本局流量增加
        _temporaryStats.sessionFansGained = 0;          //本局粉丝增加
        _temporaryStats.sessionKillCount = 0;           //本局击杀数
        if (_temporaryStats.sessionAchievements != null)//本局解锁成就
            _temporaryStats.sessionAchievements.Clear();
        if (_currentStats.unlockedAchievements != null)//全部解锁成就
            _currentStats.unlockedAchievements.Clear();

        //更新总和的值
        _temporaryStats.maxHealth = _currentStats.maxHealth_pre + _temporaryStats.maxHealth_tem;
        _temporaryStats.defense = _currentStats.defense_pre + _temporaryStats.defense_tem;
        _temporaryStats.moveSpeed = _temporaryStats.moveSpeed_tem + _currentStats.moveSpeed_pre;
        _temporaryStats.attackSpeed = _currentStats.attackSpeed_pre + _temporaryStats.attackSpeed_tem;
        _temporaryStats.attackDamage = _currentStats.attackDamage_pre + _temporaryStats.attackDamage_tem;

        SaveStatistics();
    }
    #endregion

    #region 新局值
    // 游戏内新一局开始时,请*手动*调用它
    public void StartNewSession()
    {
        //总局数统计加1
        _currentStats.totalSessionsPlayed++;

        // 清零单局统计(观众数,流量上涨,粉丝获取,击杀数,成就解锁,升级次数)
        _temporaryStats.sessionTrafficTime = 0;
        _temporaryStats.sessionTrafficEarned = 0;
        _temporaryStats.sessionFansGained = 0;
       _temporaryStats.sessionKillCount = 0;
        if (_temporaryStats.sessionAchievements != null)
            _temporaryStats.sessionAchievements.Clear();

        // 重置局内属性为默认值(生命回满,流量清零)
        _temporaryStats.currentHealth = _currentStats.maxHealth_pre;
        _currentStats.traffic = 0;

        // 暂时类战斗属性重置
        _temporaryStats.maxHealth_tem = 0f;     //生命值上限
        _temporaryStats.defense_tem = 0f;         //防御值
        _temporaryStats.moveSpeed_tem = 0f;       //移动速度
        _temporaryStats.attackSpeed_tem = 0f;     //攻击速度
        _temporaryStats.attackDamage_tem = 0f;   //攻击力

        //更新总和的值
        _temporaryStats.maxHealth = _currentStats.maxHealth_pre + _temporaryStats.maxHealth_tem;
        _temporaryStats.defense = _currentStats.defense_pre +_temporaryStats.defense_tem;
        _temporaryStats.moveSpeed = _temporaryStats.moveSpeed_tem + _currentStats.moveSpeed_pre;
        _temporaryStats.attackSpeed = _currentStats.attackSpeed_pre + _temporaryStats.attackSpeed_tem;
        _temporaryStats.attackDamage = _currentStats.attackDamage_pre + _temporaryStats.attackDamage_tem;
        SaveStatistics();
        OnStatisticsUpdated?.Invoke();
        Debug.Log("*新一局开始");
    }

    #endregion

    #region 战斗相关
    // 战斗属性

    //当前生命值(不用分回血是单局还算永远,因为每局开局都是满血)
    public float CurrentHealth
    {
        get => _temporaryStats.currentHealth;
        set
        {
            _temporaryStats.currentHealth = value;
        }
    }

    #region 永久

    public float MaxHealthPre
    {
        get => _currentStats.maxHealth_pre;
        set
        {
            _currentStats.maxHealth_pre = value;
           _temporaryStats.maxHealth = _currentStats.maxHealth_pre + _temporaryStats.maxHealth_tem;
            SaveStatistics();
        }
    }

    public float DefensePre
    {
        get => _currentStats.defense_pre;
        set
        {
            _currentStats.defense_pre = value;
            _temporaryStats.defense = _currentStats.defense_pre + _temporaryStats.defense_tem;
            SaveStatistics();
        }
    }

    public float MoveSpeedPre
    {
        get => _currentStats.moveSpeed_pre;
        set { _currentStats.moveSpeed_pre = value; _temporaryStats.moveSpeed = _currentStats.moveSpeed_pre + _temporaryStats.moveSpeed_tem; SaveStatistics(); }
    }

    public float AttackSpeedPre
    {
        get => _currentStats.attackSpeed_pre;
        set { _currentStats.attackSpeed_pre = value; _temporaryStats.attackSpeed = _currentStats.attackSpeed_pre + _temporaryStats.attackSpeed_tem; SaveStatistics(); }
    }

    public float AttackDamagePre
    {
        get => _currentStats.attackDamage_pre;
        set { _currentStats.attackDamage_pre = value; _temporaryStats.attackDamage = _currentStats.attackDamage_pre + _temporaryStats.attackDamage_tem; SaveStatistics(); }
    }
    #endregion
    #region 暂时

    public int SessionTrafficTime
    {
        get => _temporaryStats.sessionTrafficTime;
        set { _temporaryStats.sessionTrafficTime = value; }
    }

    public float MaxHealthTem
    {
        get => _temporaryStats.maxHealth_tem;
        set
        {
            _temporaryStats.maxHealth_tem = value;
            _temporaryStats.maxHealth = _currentStats.maxHealth_pre + _temporaryStats.maxHealth_tem;
        }
    }

    public float DefenseTem
    {
        get => _temporaryStats.defense_tem;
        set { _temporaryStats.defense_tem = value; _temporaryStats.defense = _currentStats.defense_pre + _temporaryStats.defense_tem; }

    }

    public float MoveSpeedTem
    {
        get => _temporaryStats.moveSpeed_tem;
        set { _temporaryStats.moveSpeed_tem = value; _temporaryStats.moveSpeed = _currentStats.moveSpeed_pre + _temporaryStats.moveSpeed_tem; }
    }

    public float AttackSpeedTem
    {
        get => _temporaryStats.attackSpeed_tem;
        set { _temporaryStats.attackSpeed_tem = value;_temporaryStats.attackSpeed = _currentStats.attackSpeed_pre + _temporaryStats.attackSpeed_tem; }
    }

    public float AttackDamageTem
    {
        get => _temporaryStats.attackDamage_tem;
        set { _temporaryStats.attackDamage_tem = value; _temporaryStats.attackDamage = _currentStats.attackDamage_pre + _temporaryStats.attackDamage_tem; }
    }
    #endregion
    #region 总和

    public float MaxHealth
    {
        get => _temporaryStats.maxHealth;
    }

    public float Defense
    {
        get => _temporaryStats.defense;
    }

    public float MoveSpeed
    {
        get => _temporaryStats.moveSpeed;
    }

    public float AttackSpeed
    {
        get => _temporaryStats.attackSpeed;
    }

    public float AttackDamage
    {
        get => _temporaryStats.attackDamage;
    }
    #endregion

    #region 更新记录

    // 更新历史观众最高记录
    public int LiveViewerCount
    {
        get => _currentStats.liveViewerCount;
        set
        {
            _currentStats.liveViewerCount = Mathf.Max(0, value);
            if (_currentStats.liveViewerCount > _currentStats.highestViewerCount)
            {
                _currentStats.highestViewerCount = _currentStats.liveViewerCount;
            }

            SaveStatistics();
            OnViewerCountChanged?.Invoke(_currentStats.liveViewerCount);
            OnStatisticsUpdated?.Invoke();
        }
    }
    //查询历史最高观众数
    public int HighestViewerCount
    {
        get => _currentStats.highestViewerCount;
    }
    #endregion

    #region 货币相关
    // 查询流量(局内货币)
    public float Traffic
    {
        get => _currentStats.traffic;
    }
    /// 增加流量
    public void ChangTraffic(float amount)
    {
        if (amount < 0) return;

        _currentStats.traffic += amount;
        _temporaryStats.sessionTrafficEarned += amount;

        SaveStatistics();
        OnTrafficChanged?.Invoke(_currentStats.traffic);
        OnStatisticsUpdated?.Invoke();
    }

    // 消耗流量(局内升级),返回是否成功
    public bool SpendTraffic(float amount)
    {
        if (_currentStats.traffic < amount) return false;

        _currentStats.traffic -= amount;
        SaveStatistics();
        OnTrafficChanged?.Invoke(_currentStats.traffic);
        OnStatisticsUpdated?.Invoke();
        return true;
    }

    // 查询金币(局外货币)
    public int Gold
    {
        get => _currentStats.gold;
    }

    // 增加金币
    public void ChangGold(int amount)
    {
        if (amount < 0) return;

        _currentStats.gold += amount;
        SaveStatistics();
        OnGoldChanged?.Invoke(_currentStats.gold);
        OnStatisticsUpdated?.Invoke();
    }

    // 消耗金币(局外升级),返回是否成功
    public bool SpendGold(int amount)
    {
        if (_currentStats.gold < amount) return false;

        _currentStats.gold -= amount;
        SaveStatistics();
        OnGoldChanged?.Invoke(_currentStats.gold);
        OnStatisticsUpdated?.Invoke();
        return true;
    }

    // 查询总粉丝
    public int TotalFans
    {
        get => _currentStats.totalFans;
    }
    //查询本局增加粉丝
    public int SessionFansGained
    {
        get => _temporaryStats.sessionFansGained;
    }
    // 增加粉丝
    public void ChangFans(int amount)
    {
        if (amount < 0) return;

        _currentStats.totalFans += amount;
        _temporaryStats.sessionFansGained += amount;
        SaveStatistics();
        OnStatisticsUpdated?.Invoke();
    }
    #endregion
    #region 成就统计
    //查询总击杀
    public int TotalKills
    {
        get => _currentStats.totalKills;
    }
    // 查询本局击杀
    public int SessionKillCount
    {
        get => _temporaryStats.sessionKillCount;
    }
    // 增加击杀(加1)
    public void KillPlus()
    {
        _temporaryStats.sessionKillCount++;
        _currentStats.totalKills++;

        SaveStatistics();
        OnKillCountChanged?.Invoke(_currentStats.totalKills);
        OnStatisticsUpdated?.Invoke();
    }

    // 查询总成就(一个字符串列表)
    public List<string> GetUnlockedAchievements()
    {
        return new List<string>(_currentStats.unlockedAchievements);
    }
    // 查询本局成就(一个字符串列表)
    public List<string> GetSessionAchievements()
    {
        return new List<string>(_temporaryStats.sessionAchievements);
    }
    //增加新完成的成就(输入字符串)
    public void UnlockAchievement(string achievementName)
    {
        if (_currentStats.unlockedAchievements.Contains(achievementName))
            return;

        _currentStats.unlockedAchievements.Add(achievementName);
        _temporaryStats.sessionAchievements.Add(achievementName);

        SaveStatistics();
        OnStatisticsUpdated?.Invoke();
        Debug.Log("*解锁成就:" + achievementName);
    }

    // 查询游戏总局数
    public int TotalSessionsPlayed
    {
        get => _currentStats.totalSessionsPlayed;
    }
    #endregion
    #endregion

    #region 持久化

    private void SaveStatistics()
    {
        try
        {
            string jsonData = JsonUtility.ToJson(_currentStats);
            PlayerPrefs.SetString(STATS_SAVE_KEY, jsonData);
            PlayerPrefs.Save();
            Debug.Log("*统计数据已保存");
        }
        catch (System.Exception e)
        {
            Debug.LogError($"*保存统计失败: {e.Message}");
        }
    }

    private void LoadStatistics()
    {
        //PlayerPrefs.DeleteKey(STATS_SAVE_KEY);
        //可以用这句测试删除存档的情况
        try
        {
            if (PlayerPrefs.HasKey(STATS_SAVE_KEY))
            {
                string jsonData = PlayerPrefs.GetString(STATS_SAVE_KEY);
                _currentStats = JsonUtility.FromJson<StatisticsData>(jsonData);

                // 确保 List 不为 null
                if (_temporaryStats.sessionAchievements == null)
                    _temporaryStats.sessionAchievements = new List<string>();
                if (_currentStats.unlockedAchievements == null)
                    _currentStats.unlockedAchievements = new List<string>();

                Debug.Log("*统计数据加载成功");
            }
            else
            {
                //初始化新统计数据
                Debug.Log("*创建默认统计数据");
                _currentStats = new StatisticsData();
                InitializeStats();

            }
        }
        catch (System.Exception e)
        {
            Debug.LogError($"*加载统计失败:{e.Message}");
            _currentStats = new StatisticsData();
        }
    }

    #endregion

    #region 公共方法

    // 重置为默认数据(谨慎使用!)
    public void ResetToDefault()
    {
        _currentStats = new StatisticsData();
        Debug.Log("*统计数据已重置为默认");
        InitializeStats();
        OnStatisticsUpdated?.Invoke();
    }

    // 获取完整统计数据(供其他系统使用)
    public StatisticsData GetCurrentStatistics()
    {
        return _currentStats;
    }

    // 强制保存(比如游戏退出前)
    public void ForceSave()
    {
        SaveStatistics();
    }

    #endregion

    #region 生命周期

    void OnApplicationQuit()
    {
        SaveStatistics();
    }

    void OnDisable()
    {
        SaveStatistics();
    }

    #endregion

    #region 事件监听
    void Start()
    {
        GameEvents.OnPlayerCurrentHealthChanged += health => CurrentHealth = Mathf.Min(health, MaxHealth);
        GameEvents.OnPlayerMaxHealthPreChanged += health => MaxHealthPre = health;
        GameEvents.OnPlayerMaxHealthTemChanged += health => MaxHealthTem = health;
        GameEvents.OnPlayerAttackDamagePreChanged += damage => AttackDamagePre = damage;
        GameEvents.OnPlayerAttackDamageTemChanged += damage => AttackDamageTem = damage;
        GameEvents.OnPlayerAttackSpeedPreChanged += speed => AttackSpeedPre = speed;
        GameEvents.OnPlayerAttackSpeedTemChanged += speed => AttackSpeedTem = speed;
        GameEvents.OnPlayerMoveSpeedPreChanged += speed => MoveSpeedPre = speed;
        GameEvents.OnPlayerMoveSpeedTemChanged += speed => MoveSpeedTem = speed;

        GameEvents.OnFansChanged += count => ChangFans(count);
        GameEvents.OnGoldChanged += amount => ChangGold(amount);
        GameEvents.OnTrafficChanged += amount => ChangTraffic(amount);
        GameEvents.OnViewerCountChanged += amount => LiveViewerCount = amount;

        GameEvents.OnAchievementUnlocked += achievementId => UnlockAchievement(achievementId);

    }
    #endregion
}
UI/GameHUDPanel.cs
/// <summary>
/// ��ϷHUD���ӿ�
/// UI�Ŷ���Ҫʵ�������
/// ע�⣬��ΪAI�ο������������Ҫ�޸�
/// ����ֱ��ʹ�ô˴��룡
/// </summary>
public abstract class GameHUDPanel : UIPanel
{
    public abstract void UpdateFlow(float percentage);
    public abstract void UpdateFans(int count);
    public abstract void UpdateExp(float percentage, int level);
    public abstract void UpdateCurrency(int amount);
    public abstract void UpdateLevel(int level);
}
public abstract class CombatHUDPanel : UIPanel
{
    public abstract void UpdateCurrentHealth(float amount);
    public abstract void UpdateMaxHealth(float amount);

    public abstract void UpdateBullet(int count);
}
/// <summary>
/// ����ѡ�����ӿ�
/// UI�Ŷ���Ҫʵ�������
/// </summary>
public abstract class UpgradeSelectionPanel : UIPanel
{
    public abstract void SetupOptions(UpgradeSys.UpgradeOption[] options);
}

/// <summary>
/// �̳����ӿ�  
/// UI�Ŷ���Ҫʵ�������
/// </summary>
public abstract class TutorialPanel : UIPanel
{
    public abstract void ShowTutorialStep(string stepName);
}
概念 01
概念 01
UI/Panels/OperationPanel.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class OperationPanel : UIPanel
{   
    public Button Operation;
    public Button VisualsAndSoundeffects;
    public Button Other; 

    public Button ResetButton;
    public Button ApplyButton;

    public ScrollRect ScrollRect;

    public TextMeshProUGUI ForwardKey;
    public Button ForwardButton;

    public TextMeshProUGUI BackKey;
    public Button BackButton;

    public TextMeshProUGUI LeftKey;
    public Button LeftButton;

    public TextMeshProUGUI RightKey;
    public Button RightButton;

    public TextMeshProUGUI JumpKey;
    public Button JumpButton;

    public TextMeshProUGUI InteractKey;
    public Button InteractButton;

    public TextMeshProUGUI SkillKey;
    public Button SkillButton;

    public TextMeshProUGUI LikeKey;
    public Button LikeButton;

    private bool WaitingForKey = false;
    private KeyCode UserDefinedKey;
    private string KeyDirection="";

    public Button PanelLeft;
    public Button PanelRight;
    void Start()
    {   
        VisualsAndSoundeffects.onClick.AddListener(ChangeVisuals);
        Other.onClick.AddListener(ChangeOther);

        ForwardButton.onClick.AddListener(ForwardChange);
        BackButton.onClick.AddListener(BackChange);
        LeftButton.onClick.AddListener(LeftChange);
        RightButton.onClick.AddListener(RightChange);
        JumpButton.onClick.AddListener(JumpChange);
        InteractButton.onClick.AddListener(InteractChange);
        SkillButton.onClick.AddListener(SkillChange);
        LikeButton.onClick.AddListener(LikeChange);

        ResetButton.onClick.AddListener(Reset);
        ApplyButton.onClick.AddListener(Apply);
        PanelLeft.onClick.AddListener(PanelToLeft);
        PanelRight.onClick.AddListener(PanelToRight);
    
    }

    void Update()
    {
        if(WaitingForKey)
        {
            foreach (KeyCode k in System.Enum.GetValues(typeof(KeyCode)))
            {
                if (Input.GetKeyDown(k) && k != KeyCode.Escape) // 排除 ESC 防退出
                {
                    Debug.Log("你设置了键: " + k);
                    UserDefinedKey = k;
                    WaitingForKey = false;
                    SetTheKey();
                    break;
                }
            }
        }
    }

    
    public override void Initialize()
    {
        base.Initialize(); // 调用基类初始化
        // 你的初始化代码
    }
    public override void Show()
    {
        SetAllData();
        base.Show(); // 调用基类显示逻辑
        ScrollRect.verticalNormalizedPosition = 1f;
    }
    public override void Hide()
    {
        base.Hide(); // 调用基类隐藏逻辑
        // 面板隐藏时的自定义逻辑
    }

    void ChangeOther()
    {
        
    }
    void ChangeVisuals()
    {
        UIManager.Instance.ShowPanel("VisualsAndSoundeffectsPanel");
    }

    void ForwardChange()
    {
        WaitingForKey = true;
        ForwardKey.text = "press any key";
        KeyDirection = "Forward";
        SettingsManager.Instance.MoveForwardKey = UserDefinedKey;
    }

    void BackChange()
    {
        WaitingForKey = true;
        BackKey.text = "press any key";
        KeyDirection = "Back";
        SettingsManager.Instance.MoveBackKey = UserDefinedKey;
    }

    void LeftChange()
    {
        WaitingForKey = true;
        LeftKey.text = "press any key";
        KeyDirection = "Left";
        SettingsManager.Instance.MoveLeftKey = UserDefinedKey;
    }

    void RightChange()
    {
        WaitingForKey = true;
        RightKey.text = "press any key";
        KeyDirection = "Right";
        SettingsManager.Instance.MoveRightKey = UserDefinedKey;
    }

    void JumpChange()
    {
        WaitingForKey = true;
        JumpKey.text = "press any key";
        KeyDirection = "Jump";
        SettingsManager.Instance.JumpKey = UserDefinedKey;
    }

    void InteractChange()
    {
        WaitingForKey = true;
        InteractKey.text = "press any key";
        KeyDirection = "Interact";
        SettingsManager.Instance.InteractKey = UserDefinedKey;
    }

    void LikeChange()
    {
        WaitingForKey = true;
        LikeKey.text = "press any key";
        KeyDirection = "Like";
        SettingsManager.Instance.LikeKey = UserDefinedKey;
    }

    void SkillChange()
    {
        WaitingForKey = true;
        SkillKey.text = "press any key";
        KeyDirection = "Skill";
        SettingsManager.Instance.SkillKey = UserDefinedKey;
    }

    void SetTheKey()
    {
        switch(KeyDirection)
        {
            case "Forward":
            SettingsManager.Instance.MoveForwardKey = UserDefinedKey;
            break;
            case "Back":
            SettingsManager.Instance.MoveBackKey = UserDefinedKey;
            break;
            case "Left":
            SettingsManager.Instance.MoveLeftKey = UserDefinedKey;
            break;
            case "Right":
            SettingsManager.Instance.MoveRightKey = UserDefinedKey;
            break;
            case "Jump":
            SettingsManager.Instance.JumpKey = UserDefinedKey;
            break;
            case "Interact":
            SettingsManager.Instance.InteractKey = UserDefinedKey;
            break;
            case "Skill":
            SettingsManager.Instance.SkillKey = UserDefinedKey;
            break;
            case "Like":
            SettingsManager.Instance.LikeKey = UserDefinedKey;
            break;
        }
        SetAllData();
    }
    void Reset()
    {
        SettingsManager.Instance.ResetToDefault();
        SetAllData();
    }
    void Apply()
    {
        SettingsManager.Instance.ApplySettingsImmediately();
    }

    void SetAllData()
    {
        ForwardKey.text = SettingsManager.Instance.MoveForwardKey.ToString();
        BackKey.text= SettingsManager.Instance.MoveBackKey.ToString();
        LeftKey.text= SettingsManager.Instance.MoveLeftKey.ToString();
        RightKey.text= SettingsManager.Instance.MoveRightKey.ToString();
        JumpKey.text= SettingsManager.Instance.JumpKey.ToString();
        InteractKey.text= SettingsManager.Instance.InteractKey.ToString();
        SkillKey.text= SettingsManager.Instance.SkillKey.ToString();
        LikeKey.text= SettingsManager.Instance.LikeKey.ToString();
    }

    void PanelToLeft()
    {
        
    }

    void PanelToRight()
    {
        UIManager.Instance.ShowPanel("VisualsAndSoundeffectsPanel");
    }
}
UI/Panels/PanelsTester.cs
using UnityEngine;

//测试面板是否能打开并正常运作

public class PanelsTester : MonoBehaviour
{
    private string str;
    private int show = 1;
    void Start()
    {
        str= "InGame_Main_Combat";
        UIManager.Instance.ShowPanel(str);
        GameEvents.TriggerPlayerMaxHealthPreChanged(500);
        GameEvents.TriggerPlayerCurrentHealthChanged(500);
    }

    void Update()
    {
        if(Input.GetKeyDown(KeyCode.Alpha1)||Input.GetKeyDown(KeyCode.Alpha2)||Input.GetKeyDown(KeyCode.Alpha3)||Input.GetKeyDown(KeyCode.Alpha4)||Input.GetKeyDown(KeyCode.Alpha5)||Input.GetKeyDown(KeyCode.Alpha6)){

            if(Input.GetKeyDown(KeyCode.Alpha1)){ str= "InGame_End_Win";}
            if(Input.GetKeyDown(KeyCode.Alpha2)){ str= "InGame_End_Lose";}
            if(Input.GetKeyDown(KeyCode.Alpha3)){ str= "InGame_Main_Combat";}
            if(Input.GetKeyDown(KeyCode.Alpha4)){ str= "InGame_Main_Live";}
            if(Input.GetKeyDown(KeyCode.Alpha5)){ str= "InGame_Main_Map";}
            if(Input.GetKeyDown(KeyCode.Alpha6)){ str= "VisualsAndSoundeffectsPanel";}
         if (show == 0)
        {
            UIManager.Instance.ShowPanel(str);
            
            show = 1;
        }
        else
        {
            var CurrentPanel = UIManager.Instance.GetCurrentPanel();
            UIManager.Instance.HidePanel(CurrentPanel);

            show = 0;
        }
        }

         if(Input.GetKeyDown(KeyCode.Alpha7))
        {
            PlayerHPController.Instance.IncreaseMaxHPPre(100);
        }
        if(Input.GetKeyDown(KeyCode.Alpha8))
        {
            
            GameEvents.TriggerPlayerMaxHealthPreChanged(500);
            GameEvents.TriggerPlayerCurrentHealthChanged(500);
        }
        if(Input.GetKeyDown(KeyCode.Alpha9))
        {
            PlayerHPController.Instance.Heal(200);
        }
        if(Input.GetKeyDown(KeyCode.Alpha0))
        {
            PlayerHPController.Instance.TakeDamage(200);
        }
        if(Input.GetKeyDown(KeyCode.T))
        {
            TutorialManager.Instance.ResetTutorial();
            TutorialManager.Instance.StartTutorial();
        }
        if(Input.GetKeyDown(KeyCode.U))
        {
            UIManager.Instance.ShowPanel("InGame_Upgrade");
        }
    }
}
UI/Panels/VisualsAndSoundeffectsPanel.cs
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class VisualsAndSoundeffectsPanel : UIPanel
{
    public TextMeshProUGUI MasterVolumeText;
    public TextMeshProUGUI SoundEffectVolumeText;
    public TextMeshProUGUI MusicVolumeText;
    public TextMeshProUGUI ScreenModeText;
    public TextMeshProUGUI ResolutionText;
    public TextMeshProUGUI ImageQualityText;
    
    public Button Operation;
    public Button VisualsAndSoundeffects;
    public Button Other;

    public Button ResolutionLeft;
    public Button ResolutionRight;
    public string[] Resolutions = new string[]
    {
        "800 x 600",
        "1024 x 768",
        "1280 x 720",
        "1366 x 768",
        "1600 x 900",
        "1920 x 1080",
        "2560 x 1440"
    };
    public Button ImageQualityLeft;
    public Button ImageQualityRight;

    public Button ScreenModeLeft;
    public Button ScreenModeRight;

    public Slider MasterVolume;
    public Slider MusicVolume;
    public Slider SoundEffectVolume;

    public Button PanelLeft;
    public Button PanelRight;

    // Start is called before the first frame update
    void Start()
    {
        Operation.onClick.AddListener(ChangeOperation);
        Other.onClick.AddListener(ChangeOther);
        ResolutionLeft.onClick.AddListener(ResolutionSub);
        ResolutionRight.onClick.AddListener(ResolutionSubPlus);
        ImageQualityLeft.onClick.AddListener(ImageQualitySub);
        ImageQualityRight.onClick.AddListener(ImageQualityPlus);
        ScreenModeLeft.onClick.AddListener(ScreenModeSub);
        ScreenModeRight.onClick.AddListener(ScreenModePlus);
        MasterVolume.onValueChanged.AddListener(MasterVolumeChanged);
        MusicVolume.onValueChanged.AddListener(MusicVolumeChanged);
        SoundEffectVolume.onValueChanged.AddListener(SoundEffectVolumeChanged);
        PanelLeft.onClick.AddListener(PanelToLeft);
        PanelRight.onClick.AddListener(PanelToRight);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    public override void Initialize()
    {
        base.Initialize(); // 调用基类初始化
        // 你的初始化代码
    }
    public override void Show()
    {
        SetAllData();
        base.Show(); // 调用基类显示逻辑
        // 面板显示时的自定义逻辑
    }
    public override void Hide()
    {
        base.Hide(); // 调用基类隐藏逻辑
        // 面板隐藏时的自定义逻辑
    }
    
    void ChangeOperation()
    {
        UIManager.Instance.ShowPanel("OperationPanel");
    }
    void ChangeOther()
    {
        
    }
    void ResolutionSub()
    {
        int value = SettingsManager.Instance.ResolutionIndex;
        if(value>0){value-=1;}
        SettingsManager.Instance.ResolutionIndex = value;
        ResolutionText.text=Resolutions[value];
    }
    void ResolutionSubPlus()
    {
        int value = SettingsManager.Instance.ResolutionIndex;
        if(value<6){value+=1;}
        SettingsManager.Instance.ResolutionIndex = value;
        ResolutionText.text=Resolutions[value];
    }
    void ImageQualitySub()
    {
        int value = SettingsManager.Instance.QualityLevel;
        value-=1;
        SettingsManager.Instance.QualityLevel=value;
        ImageQualityText.text=value.ToString();
    }
    void ImageQualityPlus()
    {
        int value = SettingsManager.Instance.QualityLevel;
        value+=1;
        SettingsManager.Instance.QualityLevel=value;
        ImageQualityText.text=value.ToString();
    }
    void ScreenModeSub()
    {
        bool value = SettingsManager.Instance.IsFullscreen;
        if(value){value = false;}
        else { value = true; }
        SettingsManager.Instance.IsFullscreen=value;
        if(value){ScreenModeText.text="Fullscreen";}else{ScreenModeText.text="Window";}
    }
    void ScreenModePlus()
    {
        bool value = SettingsManager.Instance.IsFullscreen;
        if(value){value = false;}
        else { value = true; }
        SettingsManager.Instance.IsFullscreen=value;
        if(value){ScreenModeText.text="Fullscreen";}else{ScreenModeText.text="Window";}
    }
    void MasterVolumeChanged(float value)
    {
        SettingsManager.Instance.MasterVolume = value;
        MasterVolumeText.text=(value*100).ToString("F0")+"%";
    }
    void MusicVolumeChanged(float value)
    {
        SettingsManager.Instance.MusicVolume = value;
        MusicVolumeText.text=(value*100).ToString("F0")+"%";
    }
    void SoundEffectVolumeChanged(float value)
    {
        SettingsManager.Instance.SfxVolume = value;
        SoundEffectVolumeText.text=(value*100).ToString("F0")+"%";
    }

    void PanelToLeft()
    {
        UIManager.Instance.ShowPanel("OperationPanel");
    }

    void PanelToRight()
    {
        
    }
    void SetAllData()
    {
        MasterVolumeText.text = (SettingsManager.Instance.MasterVolume*100).ToString("F0")+"%";
        SoundEffectVolumeText.text = (SettingsManager.Instance.SfxVolume*100).ToString("F0")+"%";
        MusicVolumeText.text = (SettingsManager.Instance.MusicVolume*100).ToString("F0")+"%";

        MasterVolume.value = SettingsManager.Instance.MasterVolume;
        MusicVolume.value = SettingsManager.Instance.MusicVolume;
        SoundEffectVolume.value = SettingsManager.Instance.SfxVolume;
        
        ImageQualityText.text = SettingsManager.Instance.QualityLevel.ToString();
        if(SettingsManager.Instance.IsFullscreen){ScreenModeText.text="Fullscreen";}else{ScreenModeText.text="Window";}
        ResolutionText.text = Resolutions[SettingsManager.Instance.ResolutionIndex];
    }
}
概念 02
概念 02
UI/UI_InGame_End/InGame_End_Lose.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class InGame_End_Lose : UIPanel
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}
UI/UI_InGame_End/InGame_End_Win.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class InGame_End_Win : UIPanel
{
    public TextMeshProUGUI Kill;

    public TextMeshProUGUI FansGained;

    public TextMeshProUGUI TrafficTime;

    public override void Show()
    {
    base.Show();  // 先调用基类的显示逻辑
    Kill = transform.Find("Kill").GetComponent<TextMeshProUGUI>();
    FansGained = transform.Find("FansGained").GetComponent<TextMeshProUGUI>();
    TrafficTime = transform.Find("TrafficTime").GetComponent<TextMeshProUGUI>();
    Kill.text = "Kill:"+(StatisticsManager.Instance.SessionKillCount).ToString();
    FansGained.text = "FansGained:"+(StatisticsManager.Instance.SessionFansGained).ToString();
    TrafficTime.text = "TrafficTime:"+(StatisticsManager.Instance.SessionTrafficTime).ToString();
    }
}
UI/UI_InGame_Main/InGame_Main_Combat.cs
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Unity.VisualStudio.Editor;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UI;

public class InGame_Main_Combat : CombatHUDPanel
{

    private int CurrentBullet;
    private float Currenthealth;
    private float Maxhealth;

    private float CurrenthealthScale = 0.68F;
    private float MaxhealthScale = 1;
    public UnityEngine.UI.Image HealthImage;
    public UnityEngine.UI.Image HealthFillerImage;

    public List<UnityEngine.UI.Image> images = new List<UnityEngine.UI.Image>();

    private void Awake()
    {
        if (HealthImage == null)
            HealthImage = transform.Find("Health")?.GetComponent<UnityEngine.UI.Image>();
        Transform filler = transform.Find("Health/HealthFillerBackground/HealthFiller");
        if (HealthFillerImage == null)
            HealthFillerImage = filler.GetComponent<UnityEngine.UI.Image>();

        Transform container = transform.Find("Bullets");
        if (container == null)
        {
            Debug.Log("InGame_Main_Combat: 找不到 Bullets 容器!检查层级名称");
            return;
        }

        // ⭐ 查找 8 个子弹 Image
        for (int i = 1; i <= 8; i++)
        {
            Transform bullet = container.Find($"Bullet{i}");

            if (bullet != null)
            {
                var img = bullet.GetComponent<UnityEngine.UI.Image>();
                if (img != null)
                {
                    images.Add(img); Debug.Log($"Bullet{i}已加入");
                }
            }
        }
        UpdateBullet(8);
    }

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

        GameEvents.OnPlayerCurrentHealthChanged += UpdateCurrentHealth;

        Currenthealth = StatisticsManager.Instance.CurrentHealth;
        Maxhealth = StatisticsManager.Instance.MaxHealth;

        UpdateCurrentHealth(Currenthealth);
        UpdateMaxHealth(Maxhealth);
    }

    public override void Hide()
    {
        base.Hide();
        GameEvents.OnPlayerCurrentHealthChanged -= UpdateCurrentHealth;
    }

    public override void UpdateBullet(int count)
    {
        CurrentBullet = count;
        RefreshBullets();
    }

    private void RefreshBullets()
    {
        for (int i = 0; i < images.Count; i++)
        {
            images[i].gameObject.SetActive(i < CurrentBullet);
        }
    }

    public override void UpdateCurrentHealth(float amount)
    {
        Currenthealth = amount;
        RectTransform rect = HealthFillerImage.rectTransform;
        Vector2 size = rect.sizeDelta;
        float width = Currenthealth * CurrenthealthScale;
        size.x = width;
        rect.sizeDelta = size;
    }

    public override void UpdateMaxHealth(float amount)
    {
        Maxhealth = amount;
        RectTransform rect = HealthImage.rectTransform;
        Vector2 size = rect.sizeDelta;
        float width = Maxhealth * MaxhealthScale;
        size.x = width;
        rect.sizeDelta = size;
    }
}
概念 01
概念 01
UI/UI_InGame_Main/InGame_Main_Live.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class InGame_Main_Live : UIPanel
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}
UI/UI_InGame_Main/InGame_Main_Map.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class InGame_Main_Map : UIPanel
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}
UI/UI_InGame_Upgrade/InGame_Upgrade.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class InGame_Upgrade : UpgradeSelectionPanel
{
    public TextMeshProUGUI Option1;
    public TextMeshProUGUI Option2;
    public TextMeshProUGUI Option3;

    public Button Btn1;
    public Button Btn2;
    public Button Btn3;

    private List<UpgradeSys.UpgradeOption> currentOptions;

    public override void SetupOptions(UpgradeSys.UpgradeOption[] options)
{
    // 这个方法由其他系统调用,但我们使用 OnEnable 自动获取
    // 这里可以留空,或者调用 DisplayOptions()
    if (options != null && options.Length > 0)
    {
        currentOptions = new List<UpgradeSys.UpgradeOption>(options);
        DisplayOptions();
    }
}

    private void Awake()
    {
        // 初始化按钮监听
        Btn1.onClick.AddListener(() => OnOptionSelected(0));
        Btn2.onClick.AddListener(() => OnOptionSelected(1));
        Btn3.onClick.AddListener(() => OnOptionSelected(2));
    }

    private void OnEnable()
    {
        // 面板显示时,自动从UpgradeSys获取3个随机升级选项
        if (UpgradeSys.Instance != null)
        {
            currentOptions = UpgradeSys.Instance.GenerateUpgradeOptions(3);
            DisplayOptions();
        }
    }

    private void DisplayOptions()
    {
        if (currentOptions == null) return;

        // 选项1
        if (currentOptions.Count >= 1)
        {
            var opt = currentOptions[0];
            Option1.text = $"{opt.name}\n<size=70%><color=#AAAAAA>{opt.description}</color></size>";
            Btn1.gameObject.SetActive(true);
        }
        else
        {
            Btn1.gameObject.SetActive(false);
        }

        // 选项2
        if (currentOptions.Count >= 2)
        {
            var opt = currentOptions[1];
            Option2.text = $"{opt.name}\n<size=70%><color=#AAAAAA>{opt.description}</color></size>";
            Btn2.gameObject.SetActive(true);
        }
        else
        {
            Btn2.gameObject.SetActive(false);
        }

        // 选项3
        if (currentOptions.Count >= 3)
        {
            var opt = currentOptions[2];
            Option3.text = $"{opt.name}\n<size=70%><color=#AAAAAA>{opt.description}</color></size>";
            Btn3.gameObject.SetActive(true);
        }
        else
        {
            Btn3.gameObject.SetActive(false);
        }
    }

    private void OnOptionSelected(int index)
    {
        if (currentOptions != null && index < currentOptions.Count)
        {
            // 应用选中的升级
            UpgradeSys.Instance.ApplyUpgrade(currentOptions[index]);

            // 隐藏面板
            gameObject.SetActive(false);
            Hide();
            UIManager.Instance.ShowPanel("InGame_Main_Combat");
        }
    }
}
概念 02
概念 02
UI/UI_Main/Main_AchievementBtn.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UI_Main_AchievementBtn : MonoBehaviour
{
    private Button button;
    void Start()
    {
        // 获取当前 GameObject 上的 Button 组件
        button = GetComponent<Button>();
        
        // 直接添加点击事件
        button.onClick.AddListener(OnStartButtonClick);
    }
    
    void OnStartButtonClick()
    {
        
    }
}
UI/UI_Main/Main_Manager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEditor.Search;

public class UI_Main_Panel : MonoBehaviour
{

    private int show = 0;
    public Image Text;

    public Button[] Buttons;

    void Start()
    {
        Text.gameObject.SetActive(true);
        foreach (var btn in Buttons)
        {
            btn.gameObject.SetActive(false);
        }
    }

    void Update()
    {
        if (show == 0 && Input.anyKeyDown)
        {
            show = 1;
            Text.gameObject.SetActive(false);
            foreach (var btn in Buttons)
            {
                btn.gameObject.SetActive(true);
            }
        }
    }
}
UI/UI_Main/Main_SettingBtn.cs
using UnityEngine;
using UnityEngine.UI;

public class UI_Main_SettingBtn : MonoBehaviour
{
    private bool isPanelOpen = false;
    
    void Start()
    {
        GetComponent<Button>().onClick.AddListener(TogglePanel);
    }
    
    void TogglePanel()
    {
        if (isPanelOpen)
        {
            UIManager.Instance.HidePanel(UIManager.Instance.GetCurrentPanel());
        }
        else
        {
            UIManager.Instance.ShowPanel("VisualsAndSoundeffectsPanel");
        }
        isPanelOpen = !isPanelOpen;
    }
}
概念 01
概念 01
UI/UI_Main/Main_StartBtn.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
using UnityEngine.SceneManagement;

public class UI_Main_StartBtn : MonoBehaviour
{
    private Button button;
    void Start()
    {
        // 获取当前 GameObject 上的 Button 组件
        button = GetComponent<Button>();
        
        // 直接添加点击事件
        button.onClick.AddListener(OnStartButtonClick);
    }
    
    void OnStartButtonClick()
    {
        SceneManager.LoadScene(1);
    }
    
}
UI/UIBridge.cs
using Unity.Collections;
using Unity.VisualScripting;
using UnityEngine;

/**************************************************
 * ������gongneng��
 * 1. ��Ϊ��Ϸ�߼�ϵͳ��UIϵͳ֮���**Ψһ�¼�����**��
 * 2. ���������� `GameEvents` �ж����ȫ����Ϸ�¼���
 * 3. ���¼�����ת������Ӧ��UI�����и��¡�
 * 
 * ���޸�˵����
 * 1. �¼���·�Ѽ�������¼����������е�UI���ô�����ȡ��ע�͡�
 * 2. ģ�����ݣ�`OnFlowChanged` �� `OnExpChanged` ������ʹ������ʱģ��ֵ��`maxFlow = 100f`, `currentLevel = 1`����
 *     �� ������ `FlowSys`��`ExpSys` ��ϵͳ��ɺ��뽫ģ��ֵ�滻Ϊʵ�ʵ�ϵͳ���Ե��ã����� `FlowSys.Instance.MaxFlow`����
 * 3. ����ȡ��ͳһʹ�� `UIManager.Instance?.GetPanel<T>()` ����ȡ������塣
 *     �� ������ȷ����ʵ�ֵľ�����壨�� `ConcreteGameHUDPanel`������ȷ�̳� `GameHUDPanel` �ȳ����࣬������ `UIManager` ��ע�ᡣ
 * 4. ���������ڹؼ�λ�������� `Debug.LogWarning`�����������δ�ҵ�ʱ��ʾ������ʱ���ע����̨�����
 **************************************************/

public class UIBridge : MonoBehaviour
{

    void Start()
    {
        InitializeUIBridge();
    }

    void InitializeUIBridge()
{
    if (UIManager.Instance == null)
    {
        Debug.Log("UIManager not found!");
        return;
    }
    SubscribeToGameEvents();
}

    /// <summary>
    /// ��������GameEvents�ж������Ϸ�¼���
    /// ��ע�⡿ȷ��GameEvents����Щ�¼��ѱ���ȷ����������UI������¡�
    /// </summary>
    void SubscribeToGameEvents()
    {
        // Ѫ������ϵͳ�¼�
        GameEvents.OnPlayerCurrentHealthChanged += OnCurrenthealthChanged;
        GameEvents.OnPlayerMaxHealthTemChanged += OnMaxhealthTemChanged;
        GameEvents.OnPlayerMaxHealthPreChanged += OnMaxhealthPreChanged;
        // ��������ϵͳ�¼�
        GameEvents.OnFlowChanged += OnFlowChanged;
        GameEvents.OnFlowWaveStarted += OnFlowWaveStarted;
        GameEvents.OnFlowWaveEnded += OnFlowWaveEnded;

        // ���ķ�˿ϵͳ�¼�
        GameEvents.OnFansChanged += OnFansChanged;

        // ���ľ���ϵͳ�¼�
        GameEvents.OnPlayerExpChanged += OnExpChanged;
        GameEvents.OnPlayerLevelUp += OnLevelUp;

        // ���Ļ���ϵͳ�¼�
        GameEvents.OnCurrencyChanged += OnCurrencyChanged;

        // ���Ľ̳�ϵͳ�¼�
        GameEvents.OnTutorialStepStarted += OnTutorialStepStarted;
        GameEvents.OnTutorialStepCompleted += OnTutorialStepCompleted;
        GameEvents.OnTutorialCompleted += OnTutorialCompleted;

        // ���ijɾ�ϵͳ�¼�
        GameEvents.OnAchievementUnlocked += OnAchievementUnlocked;

        // ���Ľ���ϵͳ�¼�
        GameEvents.OnSystemUnlocked += OnSystemUnlocked;
        GameEvents.OnContentUnlocked += OnContentUnlocked;

        //�ӵ��仯�¼�
         GameEvents.OnBulletChanged += OnBulletChanged;
    }

    #region �¼���������(����UI����)
    // Ѫ���仯�¼�
    void OnCurrenthealthChanged(float amount)
    {
        var hud = UIManager.Instance?.GetPanel<CombatHUDPanel>();
        if (hud != null) 
        {
            hud.UpdateCurrentHealth(amount);
        }
        else
        {
            Debug.LogWarning("UIBridge: δ�ҵ�GmaeHUDPanel����ȷ��HUD���ڣ�");
        }
    }

    void OnMaxhealthTemChanged(float amount)
    {
        amount = amount + StatisticsManager.Instance.MaxHealthPre;
        var hud = UIManager.Instance?.GetPanel<CombatHUDPanel>();
        if (hud != null) 
        {
            hud.UpdateMaxHealth(amount);
        }
        else
        {
            Debug.LogWarning("UIBridge: δ�ҵ�GmaeHUDPanel����ȷ��HUD���ڣ�");
        }
    }

    void OnMaxhealthPreChanged(float amount)
    {
        amount = amount + StatisticsManager.Instance.MaxHealthTem;
        var hud = UIManager.Instance?.GetPanel<CombatHUDPanel>();
        if (hud != null) 
        {

            hud.UpdateMaxHealth(amount);
        }
        else
        {
            Debug.LogWarning("UIBridge: δ�ҵ�GmaeHUDPanel����ȷ��HUD���ڣ�");
        }
    }

    // �����仯�¼�������HUD������
    void OnFlowChanged(float currentFlow)
    {
        Debug.Log($"Flow updated: {currentFlow}");

        var hud = UIManager.Instance?.GetPanel<GameHUDPanel>();
        if (hud != null) 
        {
            float maxFlow = 100f;//��ʱģ�����ֵ����Ҫ�滻ʵ��ֵ
            float percentage = currentFlow / maxFlow;
            hud.UpdateFlow(percentage);
        }
        else
        {
            // �������������Ҷ�ûд������Կ��żӣ������������Ҳ����
            Debug.LogWarning("UIBridge: δ�ҵ�GmaeHUDPanel����ȷ��HUD���ڣ�");
        }
    }

    // ��˿�仯�¼�������HUD��˿��
    void OnFansChanged(int fansCount)
    {
        Debug.Log($"Fans updated: {fansCount}");
        var hud = UIManager.Instance?.GetPanel<GameHUDPanel>();
        if (hud != null) 
        {
            hud.UpdateFans(fansCount);
        }
    }

    // ����仯�¼�������HUD�������͵ȼ�
    void OnExpChanged(int currentExp, int nextLevelExp)
    {
        Debug.Log($"Exp updated: {currentExp}/{nextLevelExp}");
        var hud = UIManager.Instance?.GetPanel<GameHUDPanel>();
        if (hud != null) 
        {
            float percentage = (float)currentExp / nextLevelExp;
            int currentLevel = 1;
            hud.UpdateExp(percentage, currentLevel);
        }
    }

    // ���ұ仯�¼�������HUD������ʾ
    void OnCurrencyChanged(int currency)
    {
        Debug.Log($"Currency updated: {currency}");
        var hud = UIManager.Instance?.GetPanel<GameHUDPanel>();
        if (hud != null)
        {

            hud.UpdateCurrency(currency);
        }

    }

    // ��������¼�������HUD�ȼ�����ʾ��ʾ
    void OnLevelUp(int level)
    {
        Debug.Log($"Level up: {level}");
        var hud = UIManager.Instance?.GetPanel<GameHUDPanel>();
        if (hud != null)
        {
            hud.UpdateLevel(level);
        }

        ShowLevelUpMessage(level);
    }

    // �������ο�ʼ�¼�����ʾ����ѡ�����
    void OnFlowWaveStarted()
    {
        Debug.Log("Flow wave started");
        var upgradePanel = UIManager.Instance?.GetPanel<UpgradeSelectionPanel>();
        if (upgradePanel != null)
        {
            // ��ܻ��Զ���ʾ��塣
            // ������Ҫ��չ��
            // ��������ʾʱ��������ѡ������
            // ���ڴ˴����� upgradePanel.SetupOptions(data)
            UIManager.Instance.ShowPanel(upgradePanel);
        }
    }

    // �̳̲��迪ʼ�¼�����ʾ�̳����
    void OnTutorialStepStarted(string stepName)
    {
        Debug.Log($"Tutorial step started: {stepName}");
        var tutorialPanel = UIManager.Instance?.GetPanel<TutorialPanel>();
        if (tutorialPanel != null)
        {
            UIManager.Instance.ShowPanel(tutorialPanel);
            tutorialPanel.ShowTutorialStep(stepName);
        }
    }

    //�ӵ��仯�¼�
    void OnBulletChanged(int count)
    {
        var hud = UIManager.Instance?.GetPanel<CombatHUDPanel>();
        if (hud != null) 
        {
            hud.UpdateBullet(count);
        }
        else
        {
            Debug.LogWarning("UIBridge: δ�ҵ�GmaeHUDPanel����ȷ��HUD���ڣ�");
        }
    }

    void OnTutorialStepCompleted(string stepName)
    {
        Debug.Log($"Tutorial step completed: {stepName}");
        // ���Լ�һЩ��һ���Ĺ��ܣ��������ز�����߽�����һ��ָ��

    }

    void OnTutorialCompleted()
    {
        Debug.Log("Tutorial completed");
        var tutorialPanel = UIManager.Instance?.GetPanel<TutorialPanel>();
        if (tutorialPanel != null)
        {
            UIManager.Instance.HidePanel(tutorialPanel);
        }
    }

    void OnAchievementUnlocked(string achievementId)
    {
        Debug.Log($"Achievement unlocked: {achievementId}");
        ShowAchievementMessage(achievementId, "Achievement description");
    }

    void OnSystemUnlocked(string systemId)
    {
        Debug.Log($"System unlocked: {systemId}");
        // ���ڴ˴�����ϵͳ������UI��ʾ
    }

    void OnContentUnlocked(string contentId)
    {
        Debug.Log($"Content unlocked: {contentId}");
        ShowContentUnlockedMessage(contentId, "Content description");
    }

    void OnFlowWaveEnded()
    {
        Debug.Log("Flow wave ended");
        // ���ڴ˴���������������в��ν�������
        var upgradePanel = UIManager.Instance?.GetPanel<UpgradeSelectionPanel>();
        if (upgradePanel != null && upgradePanel.IsVisible())
        {
            UIManager.Instance.HidePanel(upgradePanel);
        }
    }

    #endregion

    #region ��ʾ��Ϣ��ʾ (��ʵ��)

    /// <summary>
    /// ��Ҫ��ʵ�֣��˴�������ʾ�������ʱ��ȫ���򵯴���ʾ��
    /// �ɽ���һ��ȫ�ֵġ���Ϣ��ʾϵͳ����
    /// </summary>
    void ShowLevelUpMessage(int level)
    {
        Debug.Log($"Level up to {level}!");
        // Ŀǰ��Ҫ����չ: ����ȫ����Ϣ��ʾϵͳ�ĵ���
        // ���磺MessageSystem.Show($"��ϲ������ {level} ����");
    }

    void ShowAchievementMessage(string name, string description)
    {
        Debug.Log($"Achievement: {name} - {description}");
        // ������Ե��óɾͽ�����ʾUIϵͳ
    }

    void ShowContentUnlockedMessage(string itemName, string description)
    {
        Debug.Log($"Content: {itemName} - {description}");
        // ������Ե������ݽ�����ʾUIϵͳ
    }

    #endregion

    private void OnDestroy()
    {
        GameEvents.OnFlowChanged -= OnFlowChanged;
        GameEvents.OnFlowWaveStarted -= OnFlowWaveStarted;
        GameEvents.OnFlowWaveEnded -= OnFlowWaveEnded;
        GameEvents.OnFansChanged -= OnFansChanged;
        GameEvents.OnPlayerExpChanged -= OnExpChanged;
        GameEvents.OnPlayerLevelUp -= OnLevelUp;
        GameEvents.OnCurrencyChanged -= OnCurrencyChanged;
        GameEvents.OnTutorialStepStarted -= OnTutorialStepStarted;
        GameEvents.OnTutorialStepCompleted -= OnTutorialStepCompleted;
        GameEvents.OnTutorialCompleted -= OnTutorialCompleted;
        GameEvents.OnAchievementUnlocked -= OnAchievementUnlocked;
        GameEvents.OnSystemUnlocked -= OnSystemUnlocked;
        GameEvents.OnContentUnlocked -= OnContentUnlocked;
    }
}
UI/UIManager.cs
using System.Collections.Generic;
using UnityEngine;

// ��������������ʾ������
public class UIManager : MonoBehaviour
{
    public static UIManager Instance;
    private Dictionary<string, UIPanel> panelDictionary = new Dictionary<string, UIPanel>();

    [Header("�������")]
    private List<UIPanel> allPanels = new List<UIPanel>();
    [Header("������Ϣ")]
    [SerializeField] private UIPanel currentPanel;
    [SerializeField] private Stack<UIPanel> panelStack = new Stack<UIPanel>();

    // �����ʾ/�����¼�
    public System.Action<UIPanel> OnPanelShown;
    public System.Action<UIPanel> OnPanelHidden;

    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            InitializeManager();
        }
        else
        {
            Destroy(gameObject);
        }
    }

    void InitializeManager()
    {
        panelDictionary.Clear();
        allPanels.Clear();

        // �Զ����ҵ�ǰ���������� UIPanel
        var panels = FindObjectsOfType<UIPanel>(true);

        foreach (var panel in panels)
        {
            allPanels.Add(panel);
            panel.Initialize();
            panel.Hide();
            if (!string.IsNullOrEmpty(panel.name) && !panelDictionary.ContainsKey(panel.PanelName))
            {
                panelDictionary.Add(panel.PanelName, panel);
            }
        }

        Debug.Log($"UIManager: �ҵ� {panels.Length} �����");
    }
    // ��ʾָ�����
    public void ShowPanel(UIPanel panel)
    {
        if (panel == null)
        {
            return;
        }

        // ���ص�ǰ���
        if (currentPanel != null && currentPanel != panel)
        {
            currentPanel.Hide();
            OnPanelHidden?.Invoke(currentPanel);
        }

        // ��ʾ�����
        panel.Show();
        currentPanel = panel;
        panelStack.Push(panel);
        OnPanelShown?.Invoke(panel);
    }

    // ��ʾ��壨ͨ�����ƣ�
    public void ShowPanel(string panelName)
    {
        if (panelDictionary.TryGetValue(panelName, out UIPanel panel))
        {
            ShowPanel(panel);
        }
        else
        {
            Debug.LogWarning($"Panel not found: {panelName}");
        }
    }

    // ���ص�ǰ��壬������һ�����
    public void BackToPrevious()
    {
        if (panelStack.Count > 0)
        {
            var current = panelStack.Pop();
            current.Hide();
            OnPanelHidden?.Invoke(current);

            if (panelStack.Count > 0)
            {
                var previous = panelStack.Peek();
                previous.Show();
                currentPanel = previous;
                OnPanelShown?.Invoke(previous);
            }
            else
            {
                currentPanel = null;
            }
        }
    }

    // ����ָ�����
    public void HidePanel(UIPanel panel)
    {
        if (panel != null && panel.IsVisible())
        {
            panel.Hide();
            OnPanelHidden?.Invoke(panel);

            // �Ӷ�ջ���Ƴ�
            var tempStack = new Stack<UIPanel>();
            while (panelStack.Count > 0)
            {
                var p = panelStack.Pop();
                if (p != panel) tempStack.Push(p);
            }

            // �ָ���ջ
            while (tempStack.Count > 0)
            {
                panelStack.Push(tempStack.Pop());
            }

            if (currentPanel == panel)
            {
                currentPanel = panelStack.Count > 0 ? panelStack.Peek() : null;
            }
        }
    }

    // �����������
    public void HideAllPanels()
    {
        foreach (var panel in allPanels)
        {
            if (panel != null && panel.IsVisible())
            {
                panel.Hide();
            }
        }
        panelStack.Clear();
        currentPanel = null;
    }

    // ��ȡ��ǰ��ʾ�����
    public UIPanel GetCurrentPanel()
    {
        return currentPanel;
    }

    // �������Ƿ���ʾ
    public bool IsPanelVisible(UIPanel panel)
    {
        return panel != null && panel.IsVisible();
    }
    // ע����嵽������
    public void RegisterPanel(UIPanel panel)
    {
        if (!allPanels.Contains(panel))
        {
            allPanels.Add(panel);
            panel.Initialize();
        }
    }

    //����ض��������ķ��ͷ���
    public T GetPanel<T>() where T : UIPanel
    {
        foreach (var panel in allPanels)
        {
            if (panel is T panelofType)
            {
                return panelofType;
            }
        }
        return null;
    }
}
概念 02
概念 02
UI/UIPanel.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// ÿ����嶼Ҫ�̳��������
public abstract class UIPanel : MonoBehaviour
{
    [Header("��������Ϣ")]
    public string PanelName;
    [SerializeField] protected bool isActive = false;

    // Ҫʵ�ֵĺ�������
    public virtual void Show()
    {
        gameObject.SetActive(true);
        isActive = true;
    }

    public virtual void Hide()
    {
        gameObject.SetActive(false);
        isActive = false;
    }

    // ����dz�ʼ�����
    public virtual void Initialize()
    {
        if(UIManager.Instance != null)
        {
            UIManager.Instance.RegisterPanel(this);
        }
        isActive = false;
    }

    //����Ƿ�ɼ�
    public virtual bool IsVisible()
    {
        return isActive;
    }
}

CH-04 记录

流浪尸潮

个人项目 · 主程序 / 主策划

2.5D 动作肉鸽游戏,初始平均帧率仅 35FPS、内存占用高;团队策划 / 美工产出不足。

C# 面向对象 + 组件化架构;泛型状态机(FSM);2.5D 渲染管线(URP Decal 动态阴影、视差背景);事件驱动解耦战斗/UI/经济;数据驱动(ScriptableObject)实现肉鸽三选一、动态难度、局外成长;对象池管理。

主策划 + 主程序:技术选型、架构搭建、Git 协作流程、任务拆分与带新人、能力系统设计;角色移动/二段跳/冲刺/射击手感优化。

平均帧率 35→60FPS、内存峰值 −18%;核心战斗与架构框架完善可扩展。

  • 平均帧率 35 FPS → 60 FPS +71%
  • 内存峰值 100% → 82% −18%

滚轮切台

01 / 04