CH-01 信号
CH-02 实机
CH-03 制作
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,
}
}
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;
}
}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);
}
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;
}
}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()
{
}
}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();
}
}
}
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;
}
}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;
}
}
}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);
}
}
}
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);
}
}
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;
}
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);
}
}
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));
}
}
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();
}
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);
}
}
}
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
}
}
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);
}
}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);
}
}
}
}
}
using UnityEngine;
public class PlayerControl : MonoBehaviour
{
public bool isOnGround;
public Core core;
void Update()
{
isOnGround = core.controllerState.IsGrounded;
}
}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());
}
}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;
}
}
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);
}
}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;
}
}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;
}
}
}
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;
}
}
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;
}
}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;
}
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;
}
}
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();
}
}
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);
}
}
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;
}
}
}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;
}
}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;
}
}
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;
}
}
}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();
}
}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);
}
}
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: 找不到组件");
}
// 这里可以添加游戏结束逻辑
// 比如:重新开始场景、显示游戏结束画面等
}
}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);
}
}
}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
}
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;
}
}
}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");
}
}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;
}
}
// 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
}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
}/// <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);
}
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");
}
}
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");
}
}
}
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];
}
}
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()
{
}
}
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();
}
}
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;
}
}
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()
{
}
}
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()
{
}
}
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");
}
}
}
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()
{
}
}
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);
}
}
}
}
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;
}
}
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);
}
}
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;
}
}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;
}
}
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%;核心战斗与架构框架完善可扩展。
滚轮切台
01 / 04