Add player controller, state management, and input handling

- Implemented PlayerController.cs to manage player movement and actions.
- Created PlayerState.cs to track player lives, coins, and key status.
- Added CameraFollow.cs for smooth camera movement following the player.
- Developed Character.cs as an abstract class for character behavior.
- Introduced Enums.cs for defining TreasureType and MapElementType.
- Added IDoor interface for door interactions.
- Created InputActions.cs for handling player input actions.
- Implemented MainMenu.cs for basic menu functionality including play and exit options.
This commit is contained in:
2026-06-17 22:43:59 +03:00
parent dabd056e8b
commit 39e4e51866
70 changed files with 1807 additions and 99 deletions
+97
View File
@@ -0,0 +1,97 @@
using UnityEngine;
public class Hammer : MonoBehaviour
{
[SerializeField] private float _lifespan = 5f;
[SerializeField] private float _stunDuration = 1f;
[SerializeField] private float _impactNoiseRadius = 10f;
[SerializeField] private bool _emitNoiseOnImpact = true;
private float _lifeTimer;
private Vector2 _velocity;
private bool _facingRight;
private Rigidbody2D _rigidbody;
private bool _hasCollided = false;
private void Awake()
{
_rigidbody = GetComponent<Rigidbody2D>();
_lifeTimer = _lifespan;
}
public void Initialize(bool facingRight, float speed)
{
_facingRight = facingRight;
_velocity = new Vector2(facingRight ? speed : -speed, 0);
if (_rigidbody != null)
{
_rigidbody.linearVelocity = _velocity;
}
}
private void Update()
{
// Self-destruct after lifespan expires
_lifeTimer -= Time.deltaTime;
if (_lifeTimer <= 0)
{
Destroy(gameObject);
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (_hasCollided)
return;
_hasCollided = true;
// Check for enemy collision (stun)
var enemy = collision.gameObject.GetComponent<Character>();
if (enemy != null)
{
HandleEnemyCollision(enemy, collision.relativeVelocity);
EmitImpactNoise(collision.GetContact(0).point);
Destroy(gameObject);
return;
}
// Check for breakable wall collision
var mapElement = collision.collider.GetComponent<MapElement>();
if (mapElement != null && mapElement is BreakableWall)
{
mapElement.Hit();
EmitImpactNoise(collision.GetContact(0).point);
Destroy(gameObject);
return;
}
// Fallback: destroy on any collision
EmitImpactNoise(collision.GetContact(0).point);
Destroy(gameObject);
}
private void HandleEnemyCollision(Character enemy, Vector2 impactVelocity)
{
// Apply stun to enemy
var enemyAI = enemy as EnemyAI;
if (enemyAI != null)
{
enemyAI.OnHitByHammer(_stunDuration);
}
}
private void EmitImpactNoise(Vector2 position)
{
if (!_emitNoiseOnImpact)
return;
// Check if NoiseSystem exists and emit noise
var noiseSystem = NoiseSystem.Instance;
if (noiseSystem != null)
{
noiseSystem.Emit(position, _impactNoiseRadius);
}
}
}
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 4d7148405c33f2f45ae0479592e8cb6e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -52,7 +52,7 @@ public class PlayerController : Character
private void OnFireButtonPressed()
{
if (_hammer == null)
if (_hammerThrower.CanThrow)
{
_animator.SetTrigger("Body_ThrowHammer");
}
@@ -61,7 +61,7 @@ public class PlayerController : Character
// Animation event
public void ThrowHammerObject()
{
_hammerThrower.ThrowHammer();
_hammerThrower.TryThrowHammer();
UpdatePlayerSprite();
}
+171
View File
@@ -0,0 +1,171 @@
using UnityEngine;
using System;
public enum EnemyState
{
Patrol,
Investigate,
Chase,
Stunned
}
public class EnemyAI : Character
{
[SerializeField] private float _patrolSpeed = 1f;
[SerializeField] private float _patrolRange = 5f;
[SerializeField] private float _investigateRange = 8f;
[SerializeField] private float _chaseRange = 10f;
[SerializeField] private float _stunDuration = 1f;
[SerializeField] private bool _debugMode = false;
private EnemyState _currentState = EnemyState.Patrol;
private Vector3 _patrolTarget;
private Vector3 _investigatePosition;
private float _stunTimer = 0f;
private float _patrolDirection = 1f;
private static readonly Player _player = null;
public EnemyState CurrentState => _currentState;
protected override void SetClimbingAnimation(bool isClimbing)
{
// Implement climbing animation if needed
}
protected override void SetWalkingAnimation(bool isWalking)
{
_animator.SetBool("Walk", isWalking);
}
private void Start()
{
_patrolTarget = transform.position;
SetState(EnemyState.Patrol);
}
private void Update()
{
// Update stun timer
if (_currentState == EnemyState.Stunned)
{
_stunTimer -= Time.deltaTime;
if (_stunTimer <= 0f)
{
SetState(EnemyState.Patrol);
}
return;
}
// Get player position if available
var player = Player.Instance;
if (player == null)
{
HandlePatrol();
return;
}
float distanceToPlayer = Vector3.Distance(transform.position, player.transform.position);
// State transitions
switch (_currentState)
{
case EnemyState.Patrol:
if (distanceToPlayer < _investigateRange)
{
SetState(EnemyState.Chase);
}
else
{
HandlePatrol();
}
break;
case EnemyState.Investigate:
if (distanceToPlayer < _chaseRange)
{
SetState(EnemyState.Chase);
}
else if (Vector3.Distance(transform.position, _investigatePosition) < 0.5f)
{
SetState(EnemyState.Patrol);
}
else
{
HandleInvestigate();
}
break;
case EnemyState.Chase:
if (distanceToPlayer > _chaseRange)
{
SetState(EnemyState.Patrol);
}
else
{
HandleChase(player.transform.position);
}
break;
}
}
private void SetState(EnemyState newState)
{
if (_currentState == newState)
return;
if (_debugMode)
Debug.Log($"[EnemyAI] State changed: {_currentState} -> {newState}");
_currentState = newState;
}
private void HandlePatrol()
{
// Simple back-and-forth patrol
if (Vector3.Distance(transform.position, _patrolTarget) < 0.3f)
{
_patrolDirection *= -1f;
_patrolTarget = transform.position + Vector3.right * _patrolRange * _patrolDirection;
}
float direction = _patrolTarget.x > transform.position.x ? 1f : -1f;
MoveTo(direction * _patrolSpeed, 0f);
}
private void HandleInvestigate()
{
float direction = _investigatePosition.x > transform.position.x ? 1f : -1f;
MoveTo(direction * _patrolSpeed, 0f);
}
private void HandleChase(Vector3 playerPosition)
{
float direction = playerPosition.x > transform.position.x ? 1f : -1f;
MoveTo(direction * _patrolSpeed, 0f);
}
public void OnNoise(Vector3 noisePosition)
{
if (_currentState == EnemyState.Stunned)
return;
if (_currentState != EnemyState.Chase)
{
_investigatePosition = noisePosition;
SetState(EnemyState.Investigate);
if (_debugMode)
Debug.Log($"[EnemyAI] Investigating noise at {noisePosition}");
}
}
public void OnHitByHammer(float stunDuration)
{
_stunTimer = stunDuration;
SetState(EnemyState.Stunned);
if (_debugMode)
Debug.Log($"[EnemyAI] Stunned for {stunDuration} seconds");
}
}
@@ -3,7 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CharacterSpawner : MonoBehaviour
public class EnemySpawner : MonoBehaviour
{
[SerializeField]
private GameObject _prefab;
+147 -32
View File
@@ -1,56 +1,171 @@
using UnityEngine;
using System;
public enum EnemyState
{
Patrol,
Investigate,
Chase,
Stunned
}
public class EnemyAI : Character
{
[SerializeField] private float _patrolSpeed = 1f;
[SerializeField] private float _patrolRange = 5f;
[SerializeField] private float _investigateRange = 8f;
[SerializeField] private float _chaseRange = 10f;
[SerializeField] private float _stunDuration = 1f;
[SerializeField] private bool _debugMode = false;
private EnemyState _currentState = EnemyState.Patrol;
private Vector3 _patrolTarget;
private Vector3 _investigatePosition;
private float _stunTimer = 0f;
private float _patrolDirection = 1f;
private static readonly Player _player = null;
public EnemyState CurrentState => _currentState;
protected override void SetClimbingAnimation(bool isClimbing)
{
// Implement climbing animation if needed
}
protected override void SetWalkingAnimation(bool isWalking)
{
_animator.SetBool("Walk",isWalking);
_animator.SetBool("Walk", isWalking);
}
private void Start()
{
_patrolTarget = transform.position;
SetState(EnemyState.Patrol);
}
private void Update()
{
// float horizontal = 0;
// float vertical = 0;
// Update stun timer
if (_currentState == EnemyState.Stunned)
{
_stunTimer -= Time.deltaTime;
if (_stunTimer <= 0f)
{
SetState(EnemyState.Patrol);
}
return;
}
// Vector2 directionToPlayer = Player.Instance.transform.position - transform.position;
// directionToPlayer.Normalize();
// Get player position if available
var player = Player.Instance;
if (player == null)
{
HandlePatrol();
return;
}
// float verticalDistance = Player.Instance.transform.position.y - transform.position.y;
float distanceToPlayer = Vector3.Distance(transform.position, player.transform.position);
// if (Mathf.Abs(verticalDistance) > 0.1f && (isCanClimbUp || isCanGoDown))
// {
// vertical = Mathf.Sign(verticalDistance);
// }
// else
// {
// if (Mathf.Abs(Player.Instance.transform.position.x - transform.position.x) < 0.1f)
// {
// horizontal = 0;
// }
// else if (directionToPlayer.x < 0)
// { horizontal = -1; }
// else if (directionToPlayer.x > 0)
// { horizontal = 1; }
// }
// State transitions
switch (_currentState)
{
case EnemyState.Patrol:
if (distanceToPlayer < _investigateRange)
{
SetState(EnemyState.Chase);
}
else
{
HandlePatrol();
}
break;
// if (Input.GetKey(KeyCode.T))
// { vertical = 1; }
// if (Input.GetKey(KeyCode.G))
// { vertical = -1; }
case EnemyState.Investigate:
if (distanceToPlayer < _chaseRange)
{
SetState(EnemyState.Chase);
}
else if (Vector3.Distance(transform.position, _investigatePosition) < 0.5f)
{
SetState(EnemyState.Patrol);
}
else
{
HandleInvestigate();
}
break;
// Debug.Log($"Enemy Position: {transform.position}, Player Position: {Player.Instance.transform.position}");
// Debug.Log($"Vertical Distance: {verticalDistance}, Vertical Movement: {vertical}");
// base.MoveTo(horizontal, vertical);
case EnemyState.Chase:
if (distanceToPlayer > _chaseRange)
{
SetState(EnemyState.Patrol);
}
else
{
HandleChase(player.transform.position);
}
break;
}
}
private float VerticalMove(float verticalDistance)
private void SetState(EnemyState newState)
{
float verticalDirection = Mathf.Sign(verticalDistance);
return verticalDirection;
if (_currentState == newState)
return;
if (_debugMode)
Debug.Log($"[EnemyAI] State changed: {_currentState} -> {newState}");
_currentState = newState;
}
private void HandlePatrol()
{
// Simple back-and-forth patrol
if (Vector3.Distance(transform.position, _patrolTarget) < 0.3f)
{
_patrolDirection *= -1f;
_patrolTarget = transform.position + Vector3.right * _patrolRange * _patrolDirection;
}
float direction = _patrolTarget.x > transform.position.x ? 1f : -1f;
MoveTo(direction * _patrolSpeed, 0f);
}
private void HandleInvestigate()
{
float direction = _investigatePosition.x > transform.position.x ? 1f : -1f;
MoveTo(direction * _patrolSpeed, 0f);
}
private void HandleChase(Vector3 playerPosition)
{
float direction = playerPosition.x > transform.position.x ? 1f : -1f;
MoveTo(direction * _patrolSpeed, 0f);
}
public void OnNoise(Vector3 noisePosition)
{
if (_currentState == EnemyState.Stunned)
return;
if (_currentState != EnemyState.Chase)
{
_investigatePosition = noisePosition;
SetState(EnemyState.Investigate);
if (_debugMode)
Debug.Log($"[EnemyAI] Investigating noise at {noisePosition}");
}
}
public void OnHitByHammer(float stunDuration)
{
_stunTimer = stunDuration;
SetState(EnemyState.Stunned);
if (_debugMode)
Debug.Log($"[EnemyAI] Stunned for {stunDuration} seconds");
}
}
@@ -0,0 +1,82 @@
using UnityEngine;
public class BreakableWall : MapElement
{
[SerializeField] private float _noiseRadius = 10f;
[SerializeField] private bool _emitNoiseOnBreak = true;
private float _respawnElementTimer;
private int _respawnTimeout = 4;
private bool _needRespawn = false;
private bool _characterInRange = false;
private BoxCollider2D _boxCollider;
private SpriteRenderer _spriteRenderer;
[SerializeField]
private GameObject _hitParticles;
private void Start()
{
_respawnElementTimer = _respawnTimeout;
_boxCollider = GetComponent<BoxCollider2D>();
_spriteRenderer = GetComponentInChildren<SpriteRenderer>();
}
public override void Hit()
{
IsEnabled = false;
_boxCollider.isTrigger = true;
_spriteRenderer.enabled = IsEnabled;
Instantiate(_hitParticles, transform.position, Quaternion.identity);
// Emit noise when wall breaks
if (_emitNoiseOnBreak && NoiseSystem.Instance != null)
{
NoiseSystem.Instance.Emit(transform.position, _noiseRadius);
}
_respawnElementTimer = _respawnTimeout;
_needRespawn = true;
}
private void Update()
{
if (_needRespawn)
{
_respawnElementTimer -= Time.deltaTime;
if (_respawnElementTimer <= 0)
{
_respawnElementTimer = _respawnTimeout;
if (_characterInRange)
{
print("Character is dead");
}
IsEnabled = true;
_boxCollider.isTrigger = false;
_spriteRenderer.enabled = IsEnabled;
_needRespawn = false;
}
}
}
private void OnTriggerEnter2D(Collider2D collider)
{
var character = collider.GetComponent<Character>();
if (character)
{
_characterInRange = true;
}
}
private void OnTriggerExit2D(Collider2D collider)
{
var character = collider.GetComponent<Character>();
if (character)
{
_characterInRange = false;
}
}
}
+52
View File
@@ -0,0 +1,52 @@
using UnityEngine;
public class Chest : MonoBehaviour
{
[SerializeField]
private Animator animator;
[SerializeField]
private TreasureSO _treasureSO;
private Transform _treasureObject;
private bool _isOpen = false;
private void Awake()
{
_treasureObject = transform.GetChild(1);
var spriteRenderer = _treasureObject.GetComponent<SpriteRenderer>();
spriteRenderer.sprite = _treasureSO.Image;
}
private void OnTriggerEnter2D(Collider2D collider)
{
var playerState = collider.GetComponent<PlayerState>();
if (playerState != null && !_isOpen)
{
_isOpen = true;
animator.SetTrigger("OpenChest");
switch (_treasureSO.Treasure)
{
case TreasureType.Coin:
playerState.AddCoin();
if (GameManager.Instance != null)
{
GameManager.Instance.AddTreasure(1);
}
break;
case TreasureType.Key:
playerState.SetKey();
if (GameManager.Instance != null)
{
GameManager.Instance.SetKeyState(true);
}
if (LevelManager.Instance != null)
{
LevelManager.Instance.NotifyKeyCollected();
}
break;
}
}
}
}
+63
View File
@@ -0,0 +1,63 @@
using Assets.Scripts;
using System;
using UnityEngine;
public class Door : MonoBehaviour, IDoor
{
[SerializeField]
private Sprite _openDoor;
[SerializeField]
private bool _debugMode = false;
private SpriteRenderer _spriteRenderer;
private BoxCollider2D _boxCollider;
private bool _isLocked = true;
public bool IsLocked => _isLocked;
public event EventHandler OnDoorOpened;
private void Awake()
{
_spriteRenderer = GetComponentInChildren<SpriteRenderer>();
_boxCollider = gameObject.GetComponent<BoxCollider2D>();
}
public void OpenDoor()
{
if (!_isLocked)
return;
_isLocked = false;
// Update visuals
if (_spriteRenderer != null && _openDoor != null)
{
_spriteRenderer.sprite = _openDoor;
}
// Disable collision
if (_boxCollider != null)
{
_boxCollider.enabled = false;
}
if (_debugMode)
Debug.Log("[Door] Door opened!");
OnDoorOpened?.Invoke(this, EventArgs.Empty);
}
public void LockDoor()
{
_isLocked = true;
if (_boxCollider != null)
{
_boxCollider.enabled = true;
}
if (_debugMode)
Debug.Log("[Door] Door locked!");
}
}
@@ -0,0 +1,45 @@
using Assets.Scripts;
using UnityEngine;
public class DoorInteract : MonoBehaviour
{
[SerializeField] private GameObject _doorGameObject;
[SerializeField] private bool _debugMode = false;
private IDoor _door;
private bool _hasTriggered = false;
private void Awake()
{
_door = _doorGameObject.GetComponent<IDoor>();
}
private void OnTriggerEnter2D(Collider2D collider)
{
if (_hasTriggered)
return;
var playerState = collider.GetComponent<PlayerState>();
if (playerState != null)
{
// Check if player has key through GameManager
if (GameManager.Instance != null && GameManager.Instance.HasKey)
{
_hasTriggered = true;
if (_debugMode)
Debug.Log("[DoorInteract] Player exiting with key!");
// Notify LevelManager that level is complete
if (LevelManager.Instance != null)
{
LevelManager.Instance.NotifyLevelComplete();
}
}
else if (_debugMode)
{
Debug.Log("[DoorInteract] Player reached door but does not have key!");
}
}
}
}
+37
View File
@@ -0,0 +1,37 @@
using UnityEngine;
public class KeyChest : MonoBehaviour
{
private bool _isOpened = false;
private void OnTriggerEnter2D(Collider2D collider)
{
if (_isOpened)
return;
var playerState = collider.GetComponent<PlayerState>();
if (playerState != null)
{
_isOpened = true;
// Update player state
playerState.SetKey();
// Notify GameManager of key collection
if (GameManager.Instance != null)
{
GameManager.Instance.SetKeyState(true);
}
// Notify LevelManager of key collection
if (LevelManager.Instance != null)
{
LevelManager.Instance.NotifyKeyCollected();
}
// Destroy the chest
Destroy(gameObject);
}
}
}
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 86cce1993173eb04daddb1edd164da94
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -2,6 +2,9 @@
public class BreakableWall : MapElement
{
[SerializeField] private float _noiseRadius = 10f;
[SerializeField] private bool _emitNoiseOnBreak = true;
private float _respawnElementTimer;
private int _respawnTimeout = 4;
private bool _needRespawn = false;
@@ -16,18 +19,24 @@ public class BreakableWall : MapElement
private void Start()
{
_respawnElementTimer = _respawnTimeout;
_boxCollider =GetComponent<BoxCollider2D>();
_spriteRenderer= GetComponentInChildren<SpriteRenderer>();
_boxCollider = GetComponent<BoxCollider2D>();
_spriteRenderer = GetComponentInChildren<SpriteRenderer>();
}
public override void Hit()
{
IsEnabled = false;
_boxCollider.isTrigger=true;
_boxCollider.isTrigger = true;
_spriteRenderer.enabled = IsEnabled;
Instantiate(_hitParticles, transform.position, Quaternion.identity);
// Emit noise when wall breaks
if (_emitNoiseOnBreak && NoiseSystem.Instance != null)
{
NoiseSystem.Instance.Emit(transform.position, _noiseRadius);
}
_respawnElementTimer = _respawnTimeout;
_needRespawn = true;
}
@@ -41,7 +50,7 @@ public class BreakableWall : MapElement
{
_respawnElementTimer = _respawnTimeout;
if(_characterInRange)
if (_characterInRange)
{
print("Character is dead");
}
@@ -70,6 +79,4 @@ public class BreakableWall : MapElement
_characterInRange = false;
}
}
}
}
+17 -5
View File
@@ -8,31 +8,43 @@ public class Chest : MonoBehaviour
private TreasureSO _treasureSO;
private Transform _treasureObject;
private bool _isOpen=false;
private bool _isOpen = false;
private void Awake()
{
_treasureObject=transform.GetChild(1);
var spriteRenderer=_treasureObject.GetComponent<SpriteRenderer>();
_treasureObject = transform.GetChild(1);
var spriteRenderer = _treasureObject.GetComponent<SpriteRenderer>();
spriteRenderer.sprite = _treasureSO.Image;
}
private void OnTriggerEnter2D(Collider2D collider)
{
var playerState=collider.GetComponent<PlayerState>();
var playerState = collider.GetComponent<PlayerState>();
if (playerState != null && !_isOpen)
{
_isOpen = true;
animator.SetTrigger("OpenChest");
switch (_treasureSO.Treasure)
{
case TreasureType.Coin:
playerState.AddCoin();
if (GameManager.Instance != null)
{
GameManager.Instance.AddTreasure(1);
}
break;
case TreasureType.Key:
playerState.SetKey();
if (GameManager.Instance != null)
{
GameManager.Instance.SetKeyState(true);
}
if (LevelManager.Instance != null)
{
LevelManager.Instance.NotifyKeyCollected();
}
break;
}
}
+51 -5
View File
@@ -1,17 +1,63 @@
using Assets.Scripts;
using System;
using UnityEngine;
public class Door : MonoBehaviour,IDoor
public class Door : MonoBehaviour, IDoor
{
[SerializeField]
private Sprite _openDoor;
[SerializeField]
private bool _debugMode = false;
private SpriteRenderer _spriteRenderer;
private BoxCollider2D _boxCollider;
private bool _isLocked = true;
public bool IsLocked => _isLocked;
public event EventHandler OnDoorOpened;
private void Awake()
{
_spriteRenderer = GetComponentInChildren<SpriteRenderer>();
_boxCollider = gameObject.GetComponent<BoxCollider2D>();
}
public void OpenDoor()
{
//PlayOpenAnimation
GetComponentInChildren<SpriteRenderer>().sprite= _openDoor;
//Disable box collider
gameObject.GetComponent<BoxCollider2D>().enabled=false;
if (!_isLocked)
return;
_isLocked = false;
// Update visuals
if (_spriteRenderer != null && _openDoor != null)
{
_spriteRenderer.sprite = _openDoor;
}
// Disable collision
if (_boxCollider != null)
{
_boxCollider.enabled = false;
}
if (_debugMode)
Debug.Log("[Door] Door opened!");
OnDoorOpened?.Invoke(this, EventArgs.Empty);
}
public void LockDoor()
{
_isLocked = true;
if (_boxCollider != null)
{
_boxCollider.enabled = true;
}
if (_debugMode)
Debug.Log("[Door] Door locked!");
}
}
@@ -4,21 +4,41 @@ using UnityEngine;
public class DoorInteract : MonoBehaviour
{
[SerializeField] private GameObject _doorGameObject;
[SerializeField] private bool _debugMode = false;
private IDoor _door;
private bool _hasTriggered = false;
private void Awake()
{
_door =_doorGameObject.GetComponent<IDoor>();
_door = _doorGameObject.GetComponent<IDoor>();
}
private void OnTriggerEnter2D(Collider2D collider)
{
if (_hasTriggered)
return;
var playerState = collider.GetComponent<PlayerState>();
if (playerState!=null)
if (playerState != null)
{
if (playerState.HasKey)
// Check if player has key through GameManager
if (GameManager.Instance != null && GameManager.Instance.HasKey)
{
_door.OpenDoor();
_hasTriggered = true;
if (_debugMode)
Debug.Log("[DoorInteract] Player exiting with key!");
// Notify LevelManager that level is complete
if (LevelManager.Instance != null)
{
LevelManager.Instance.NotifyLevelComplete();
}
}
else if (_debugMode)
{
Debug.Log("[DoorInteract] Player reached door but does not have key!");
}
}
}
+80 -3
View File
@@ -2,19 +2,96 @@ using UnityEngine;
public class Hammer : MonoBehaviour
{
private float _life = 3;
[SerializeField] private float _lifespan = 5f;
[SerializeField] private float _stunDuration = 1f;
[SerializeField] private float _impactNoiseRadius = 10f;
[SerializeField] private bool _emitNoiseOnImpact = true;
private float _lifeTimer;
private Vector2 _velocity;
private bool _facingRight;
private Rigidbody2D _rigidbody;
private bool _hasCollided = false;
private void Awake()
{
Destroy(gameObject, _life);
_rigidbody = GetComponent<Rigidbody2D>();
_lifeTimer = _lifespan;
}
public void Initialize(bool facingRight, float speed)
{
_facingRight = facingRight;
_velocity = new Vector2(facingRight ? speed : -speed, 0);
if (_rigidbody != null)
{
_rigidbody.linearVelocity = _velocity;
}
}
private void Update()
{
// Self-destruct after lifespan expires
_lifeTimer -= Time.deltaTime;
if (_lifeTimer <= 0)
{
Destroy(gameObject);
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
if (_hasCollided)
return;
_hasCollided = true;
// Check for enemy collision (stun)
var enemy = collision.gameObject.GetComponent<Character>();
if (enemy != null)
{
HandleEnemyCollision(enemy, collision.relativeVelocity);
EmitImpactNoise(collision.GetContact(0).point);
Destroy(gameObject);
return;
}
// Check for breakable wall collision
var mapElement = collision.collider.GetComponent<MapElement>();
if (mapElement != null)
if (mapElement != null && mapElement is BreakableWall)
{
mapElement.Hit();
EmitImpactNoise(collision.GetContact(0).point);
Destroy(gameObject);
return;
}
// Fallback: destroy on any collision
EmitImpactNoise(collision.GetContact(0).point);
Destroy(gameObject);
}
private void HandleEnemyCollision(Character enemy, Vector2 impactVelocity)
{
// Apply stun to enemy
var enemyAI = enemy as EnemyAI;
if (enemyAI != null)
{
enemyAI.OnHitByHammer(_stunDuration);
}
}
private void EmitImpactNoise(Vector2 position)
{
if (!_emitNoiseOnImpact)
return;
// Check if NoiseSystem exists and emit noise
var noiseSystem = NoiseSystem.Instance;
if (noiseSystem != null)
{
noiseSystem.Emit(position, _impactNoiseRadius);
}
}
}
+31 -14
View File
@@ -6,43 +6,60 @@ public class HammerThrower : MonoBehaviour
[SerializeField] private Transform _spawnPoint;
[SerializeField] private GameObject _hammerPrefab;
[SerializeField] private float _throwSpeed = 5f;
[SerializeField] private float _throwCooldown = 1.5f;
private GameObject _currentHammer;
private bool _hasHammer = true;
private bool _facingRight = true;
private float _cooldownTimer = 0f;
public bool HasHammer => _hasHammer;
public bool CanThrow => _hasHammer && _cooldownTimer <= 0f;
public float CooldownRemaining => Mathf.Max(0f, _cooldownTimer);
public void SetFacingDirection(bool facingRight)
{
_facingRight = facingRight;
}
public void ThrowHammer()
public bool TryThrowHammer()
{
if (!_hasHammer)
return;
if (!CanThrow)
return false;
ThrowHammer();
return true;
}
private void ThrowHammer()
{
_hasHammer = false;
_cooldownTimer = _throwCooldown;
_currentHammer = Instantiate(_hammerPrefab, _spawnPoint.position, _spawnPoint.rotation);
//float direction = _facingRight ? 1f : -1f;
// Initialize hammer with direction and speed
var hammerComponent = _currentHammer.GetComponent<Hammer>();
if (hammerComponent != null)
{
hammerComponent.Initialize(_facingRight, _throwSpeed);
}
//var rb = _currentHammer.GetComponent<Rigidbody2D>();
//rb.linearVelocity = new Vector2(direction * _throwSpeed, 0);
//// Flip hammer visually
//var scale = _currentHammer.transform.localScale;
//scale.x = Mathf.Abs(scale.x) * direction;
//_currentHammer.transform.localScale = scale;
_currentHammer.transform.localScale = new Vector2(_currentHammer.transform.localScale.x * (_facingRight ? 1 : -1), _currentHammer.transform.localScale.y);
_currentHammer.GetComponent<Rigidbody2D>().linearVelocity = new Vector2(gameObject.transform.localScale.x * _throwSpeed, 0);
// Flip hammer visual based on direction
_currentHammer.transform.localScale = new Vector2(
_currentHammer.transform.localScale.x * (_facingRight ? 1 : -1),
_currentHammer.transform.localScale.y
);
}
private void Update()
{
// Update cooldown timer
if (_cooldownTimer > 0f)
{
_cooldownTimer -= Time.deltaTime;
}
// Hammer destroyed → hammer returned
if (!_hasHammer && _currentHammer == null)
{
+22 -1
View File
@@ -1,14 +1,35 @@
using System.Security;
using UnityEngine;
public class KeyChest : MonoBehaviour
{
private bool _isOpened = false;
private void OnTriggerEnter2D(Collider2D collider)
{
if (_isOpened)
return;
var playerState = collider.GetComponent<PlayerState>();
if (playerState != null)
{
_isOpened = true;
// Update player state
playerState.SetKey();
// Notify GameManager of key collection
if (GameManager.Instance != null)
{
GameManager.Instance.SetKeyState(true);
}
// Notify LevelManager of key collection
if (LevelManager.Instance != null)
{
LevelManager.Instance.NotifyKeyCollected();
}
// Destroy the chest
Destroy(gameObject);
}
}
+70
View File
@@ -0,0 +1,70 @@
using System;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
[SerializeField] private bool _debugMode = false;
private bool _hasKey = false;
private int _treasureCount = 0;
public bool HasKey => _hasKey;
public int TreasureCount => _treasureCount;
public event EventHandler<bool> OnKeyStateChanged;
public event EventHandler<int> OnTreasureCountChanged;
public event EventHandler OnLevelComplete;
private void Awake()
{
// Singleton pattern
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
public void SetKeyState(bool hasKey)
{
if (_hasKey == hasKey)
return;
_hasKey = hasKey;
OnKeyStateChanged?.Invoke(this, _hasKey);
if (_debugMode)
Debug.Log($"[GameManager] Key state changed: {_hasKey}");
}
public void AddTreasure(int amount = 1)
{
_treasureCount += amount;
OnTreasureCountChanged?.Invoke(this, _treasureCount);
if (_debugMode)
Debug.Log($"[GameManager] Treasure count: {_treasureCount}");
}
public void CompletLevel()
{
if (_debugMode)
Debug.Log("[GameManager] Level completed!");
OnLevelComplete?.Invoke(this, EventArgs.Empty);
}
public void Reset()
{
_hasKey = false;
_treasureCount = 0;
if (_debugMode)
Debug.Log("[GameManager] Reset");
}
}
+115
View File
@@ -0,0 +1,115 @@
using System;
using UnityEngine;
public class LevelManager : MonoBehaviour
{
public static LevelManager Instance { get; private set; }
[SerializeField] private bool _debugMode = false;
[SerializeField] private Door _doorReference;
private bool _keyCollected = false;
private bool _levelComplete = false;
public bool KeyCollected => _keyCollected;
public bool LevelComplete => _levelComplete;
public event EventHandler OnKeyCollected;
public event EventHandler OnDoorUnlocked;
public event EventHandler OnLevelComplete;
private void Awake()
{
// Singleton pattern
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
}
private void Start()
{
// Subscribe to GameManager key state changes
if (GameManager.Instance != null)
{
GameManager.Instance.OnKeyStateChanged += HandleKeyStateChanged;
}
}
public void NotifyKeyCollected()
{
if (_keyCollected)
return;
_keyCollected = true;
if (_debugMode)
Debug.Log("[LevelManager] Key collected!");
OnKeyCollected?.Invoke(this, EventArgs.Empty);
UnlockDoor();
}
public void UnlockDoor()
{
if (_doorReference == null)
{
Debug.LogWarning("[LevelManager] Door reference not assigned!");
return;
}
_doorReference.OpenDoor();
if (_debugMode)
Debug.Log("[LevelManager] Door unlocked!");
OnDoorUnlocked?.Invoke(this, EventArgs.Empty);
}
public void NotifyLevelComplete()
{
if (_levelComplete)
return;
_levelComplete = true;
if (_debugMode)
Debug.Log("[LevelManager] Level complete!");
OnLevelComplete?.Invoke(this, EventArgs.Empty);
// Also notify GameManager
if (GameManager.Instance != null)
{
GameManager.Instance.CompletLevel();
}
}
public void Reset()
{
_keyCollected = false;
_levelComplete = false;
if (_debugMode)
Debug.Log("[LevelManager] Reset");
}
private void HandleKeyStateChanged(object sender, bool hasKey)
{
if (hasKey && !_keyCollected)
{
NotifyKeyCollected();
}
}
private void OnDestroy()
{
if (GameManager.Instance != null)
{
GameManager.Instance.OnKeyStateChanged -= HandleKeyStateChanged;
}
}
}
+63
View File
@@ -0,0 +1,63 @@
using System;
using UnityEngine;
public class NoiseSystem : MonoBehaviour
{
public static NoiseSystem Instance { get; private set; }
[SerializeField] private bool _debugMode = false;
[SerializeField] private LayerMask _enemyLayer = LayerMask.GetMask("Enemy");
public event EventHandler<NoiseEventArgs> OnNoiseEmitted;
private void Awake()
{
// Singleton pattern
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
}
public void Emit(Vector3 position, float radius)
{
if (_debugMode)
Debug.Log($"[NoiseSystem] Noise emitted at {position} with radius {radius}");
OnNoiseEmitted?.Invoke(this, new NoiseEventArgs { Position = position, Radius = radius });
// Find all enemies in range and notify them
Collider2D[] hitColliders = Physics2D.OverlapCircleAll(position, radius, _enemyLayer);
foreach (Collider2D collider in hitColliders)
{
var enemy = collider.GetComponent<EnemyAI>();
if (enemy != null)
{
enemy.OnNoise(position);
if (_debugMode)
Debug.Log($"[NoiseSystem] Notified enemy at {collider.transform.position}");
}
}
}
private void OnDrawGizmosSelected()
{
// Debug visualization in editor
if (!_debugMode)
return;
Gizmos.color = Color.yellow;
// This would need to be called from a debug script to visualize noise spheres
}
}
public class NoiseEventArgs : EventArgs
{
public Vector3 Position { get; set; }
public float Radius { get; set; }
}
+1 -1
View File
@@ -1,7 +1,7 @@
using TMPro;
using UnityEngine;
public class UiManager : MonoBehaviour
public class UIManager : MonoBehaviour
{
[SerializeField]
private TextMeshProUGUI _totalCoins;
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: f7c871dd492d04149b39713e47b77dc6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+69
View File
@@ -0,0 +1,69 @@
using System;
using UnityEngine;
public class HammerThrower : MonoBehaviour
{
[SerializeField] private Transform _spawnPoint;
[SerializeField] private GameObject _hammerPrefab;
[SerializeField] private float _throwSpeed = 5f;
[SerializeField] private float _throwCooldown = 1.5f;
private GameObject _currentHammer;
private bool _hasHammer = true;
private bool _facingRight = true;
private float _cooldownTimer = 0f;
public bool HasHammer => _hasHammer;
public bool CanThrow => _hasHammer && _cooldownTimer <= 0f;
public float CooldownRemaining => Mathf.Max(0f, _cooldownTimer);
public void SetFacingDirection(bool facingRight)
{
_facingRight = facingRight;
}
public bool TryThrowHammer()
{
if (!CanThrow)
return false;
ThrowHammer();
return true;
}
private void ThrowHammer()
{
_hasHammer = false;
_cooldownTimer = _throwCooldown;
_currentHammer = Instantiate(_hammerPrefab, _spawnPoint.position, _spawnPoint.rotation);
// Initialize hammer with direction and speed
var hammerComponent = _currentHammer.GetComponent<Hammer>();
if (hammerComponent != null)
{
hammerComponent.Initialize(_facingRight, _throwSpeed);
}
// Flip hammer visual based on direction
_currentHammer.transform.localScale = new Vector2(
_currentHammer.transform.localScale.x * (_facingRight ? 1 : -1),
_currentHammer.transform.localScale.y
);
}
private void Update()
{
// Update cooldown timer
if (_cooldownTimer > 0f)
{
_cooldownTimer -= Time.deltaTime;
}
// Hammer destroyed → hammer returned
if (!_hasHammer && _currentHammer == null)
{
_hasHammer = true;
}
}
}
+102
View File
@@ -0,0 +1,102 @@
using System;
using UnityEngine;
public class PlayerController : Character
{
[SerializeField]
private Sprite _regularSprite;
[SerializeField]
private Sprite _noHammerSprite;
private GameObject _hammer;
private bool _isHoldingHammer = true;
public event EventHandler<TreasureType> OnPlayerTakeItem;
private InputManager _inputManager;
private PlayerState _playerState;
private HammerThrower _hammerThrower;
private void Awake()
{
_inputManager = GetComponent<InputManager>();
_playerState = GetComponent<PlayerState>();
_hammerThrower = GetComponent<HammerThrower>();
_inputManager.OnFire += OnFireButtonPressed;
}
private void OnEnable()
{
_inputManager.OnEnable();
}
private void OnDisable()
{
_inputManager.OnDisable();
}
private void Update()
{
if (_hammer == null && !_isHoldingHammer)
{
_spriteRenderer.sprite = _regularSprite;
_isHoldingHammer = true;
}
Vector2 move = _inputManager.Movement;
MoveTo(move.x, isAllowVertical ? move.y : 0);
_hammerThrower.SetFacingDirection(_facingRight);
}
private void OnFireButtonPressed()
{
if (_hammerThrower.CanThrow)
{
_animator.SetTrigger("Body_ThrowHammer");
}
}
// Animation event
public void ThrowHammerObject()
{
_hammerThrower.TryThrowHammer();
UpdatePlayerSprite();
}
private void UpdatePlayerSprite()
{
_spriteRenderer.sprite = _hammerThrower.HasHammer
? _regularSprite
: _noHammerSprite;
}
protected override void SetWalkingAnimation(bool isWalking)
{
_bonesBack.SetActive(false);
_bonesSide.SetActive(true);
_animator.SetBool("Legs_Walk", isWalking);
_animator.SetBool("Body_Walk", isWalking);
}
protected override void SetClimbingAnimation(bool isClimbing)
{
if (isClimbing)
{
_bonesBack.SetActive(true);
_bonesSide.SetActive(false);
}
_animator.SetBool("Climb", isClimbing);
}
protected void OnDeath()
{
_playerState.Lives--;
if (_playerState.Lives == 0)
{
Debug.Log("Game over");
}
}
}