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:
@@ -0,0 +1,309 @@
|
|||||||
|
# Architecture Migration - Completion Summary
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
All 10 tasks from the ArchitectureMigration.md plan have been successfully completed. The Gnome's Bounty project now has cleaner architecture, improved separation of concerns, and new core gameplay systems.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changes Made by Task
|
||||||
|
|
||||||
|
### Task 1 ✓ - Normalize UI Manager Naming
|
||||||
|
**Files Changed:**
|
||||||
|
- `UiManager.cs` → `UIManager.cs` (file and class renamed)
|
||||||
|
|
||||||
|
**What Changed:**
|
||||||
|
- Renamed file and class to follow proper C# naming convention (UIManager instead of UiManager)
|
||||||
|
- No breaking changes - internal logic untouched
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2 ✓ - Separate Hammer Throw Logic and Hammer Projectile Logic
|
||||||
|
**Files Changed:**
|
||||||
|
- `HammerThrower.cs` (refactored)
|
||||||
|
- `Hammer.cs` (refactored)
|
||||||
|
|
||||||
|
**What Changed:**
|
||||||
|
- **HammerThrower.cs**:
|
||||||
|
- Added cooldown system (`_throwCooldown`, `_cooldownTimer`)
|
||||||
|
- Added `CanThrow` property to check if ready
|
||||||
|
- New `TryThrowHammer()` method that respects cooldown
|
||||||
|
- Calls `Hammer.Initialize()` to set direction and speed
|
||||||
|
- Default cooldown: 1.5 seconds
|
||||||
|
|
||||||
|
- **Hammer.cs**:
|
||||||
|
- Added `Initialize(bool facingRight, float speed)` method
|
||||||
|
- Improved collision detection with different object types
|
||||||
|
- Stuns enemies on collision (`OnHitByHammer`)
|
||||||
|
- Breaks BreakableWall objects
|
||||||
|
- Emits noise on impact (integrates with NoiseSystem)
|
||||||
|
- Self-destructs on any collision
|
||||||
|
- Default lifespan: 5 seconds
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3 ✓ - Add GameManager
|
||||||
|
**File Created:**
|
||||||
|
- `Managers/GameManager.cs`
|
||||||
|
|
||||||
|
**Functionality:**
|
||||||
|
- Singleton managing global game state
|
||||||
|
- Tracks key ownership: `HasKey`, `SetKeyState(bool)`
|
||||||
|
- Tracks treasure count: `TreasureCount`, `AddTreasure(int)`
|
||||||
|
- Events: `OnKeyStateChanged`, `OnTreasureCountChanged`, `OnLevelComplete`
|
||||||
|
- `CompletLevel()` method for level completion
|
||||||
|
- `Reset()` for resetting game state
|
||||||
|
- Debug mode for logging
|
||||||
|
|
||||||
|
**Setup Required:**
|
||||||
|
- Add empty GameObject with GameManager script to scene
|
||||||
|
- Component will auto-persist with `DontDestroyOnLoad`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4 ✓ - Add LevelManager
|
||||||
|
**File Created:**
|
||||||
|
- `Managers/LevelManager.cs`
|
||||||
|
|
||||||
|
**Functionality:**
|
||||||
|
- Singleton managing level progression
|
||||||
|
- Tracks key collection and door unlock
|
||||||
|
- Subscribes to GameManager key state changes
|
||||||
|
- `NotifyKeyCollected()` - triggered when player gets key
|
||||||
|
- `NotifyLevelComplete()` - triggered when player exits with key
|
||||||
|
- Events: `OnKeyCollected`, `OnDoorUnlocked`, `OnLevelComplete`
|
||||||
|
- `Reset()` for restarting level
|
||||||
|
- Debug mode for logging
|
||||||
|
|
||||||
|
**Setup Required:**
|
||||||
|
- Add empty GameObject with LevelManager script to scene
|
||||||
|
- In Inspector: Assign Door reference to the `_doorReference` field
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5 ✓ - Add NoiseSystem
|
||||||
|
**Files Created/Modified:**
|
||||||
|
- `Managers/NoiseSystem.cs` (new)
|
||||||
|
- `EnvironmentObjects/BreakableWall.cs` (updated)
|
||||||
|
- `Hammer.cs` (already includes noise emission)
|
||||||
|
|
||||||
|
**Functionality:**
|
||||||
|
- Singleton noise emission system
|
||||||
|
- `Emit(Vector3 position, float radius)` - emits noise and alerts nearby enemies
|
||||||
|
- Automatically detects enemies on "Enemy" layer
|
||||||
|
- Calls `OnNoise(Vector3 position)` on all nearby enemies
|
||||||
|
- Event: `OnNoiseEmitted` for external systems
|
||||||
|
|
||||||
|
**Setup Required:**
|
||||||
|
- Add empty GameObject with NoiseSystem script to scene
|
||||||
|
- Ensure all enemy GameObject have "Enemy" layer assigned
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6 ✓ - Add Enemy States to EnemyAI
|
||||||
|
**File Modified:**
|
||||||
|
- `EnemyAI.cs` (completely refactored)
|
||||||
|
|
||||||
|
**Functionality:**
|
||||||
|
- State machine with 4 states: `Patrol`, `Investigate`, `Chase`, `Stunned`
|
||||||
|
- Automatic state transitions based on distance to player
|
||||||
|
- `OnNoise(Vector3 position)` - enters Investigate state
|
||||||
|
- `OnHitByHammer(float stunDuration)` - enters Stunned state and recovers after duration
|
||||||
|
- Configurable ranges and speeds via Inspector
|
||||||
|
- Inspector fields:
|
||||||
|
- `_patrolSpeed`: Speed during patrol
|
||||||
|
- `_patrolRange`: How far to patrol
|
||||||
|
- `_investigateRange`: Range to trigger investigate
|
||||||
|
- `_chaseRange`: Range to trigger chase
|
||||||
|
- `_stunDuration`: How long to stay stunned (default: 1 second)
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
- Patrols back and forth when at peace
|
||||||
|
- Investigates noise sources when hearing sound
|
||||||
|
- Chases player when within range
|
||||||
|
- Recovers from stun and resumes normal behavior
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 7 ✓ - Finalize Hammer Cooldown and Tactical Rules
|
||||||
|
**Files Modified:**
|
||||||
|
- `PlayerController.cs` (updated to use new hammer API)
|
||||||
|
- `HammerThrower.cs` (already includes cooldown)
|
||||||
|
- `Hammer.cs` (already includes tactical rules)
|
||||||
|
|
||||||
|
**What Changed:**
|
||||||
|
- PlayerController now checks `_hammerThrower.CanThrow` before playing throw animation
|
||||||
|
- Calls `TryThrowHammer()` instead of `ThrowHammer()`
|
||||||
|
- Hammer impacts emit noise automatically (configurable)
|
||||||
|
- Hammer stuns enemies without killing them
|
||||||
|
- Hammer breaks only BreakableWall objects
|
||||||
|
- Hammer disappears on impact
|
||||||
|
|
||||||
|
**Tunable Values (Inspector):**
|
||||||
|
- HammerThrower: `_throwCooldown`, `_throwSpeed`
|
||||||
|
- Hammer: `_lifespan`, `_stunDuration`, `_impactNoiseRadius`, `_emitNoiseOnImpact`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 8 ✓ - Integrate KeyChest with Game State
|
||||||
|
**Files Modified:**
|
||||||
|
- `KeyChest.cs` (updated)
|
||||||
|
- `Chest.cs` (updated)
|
||||||
|
|
||||||
|
**What Changed:**
|
||||||
|
- KeyChest now notifies GameManager when key collected
|
||||||
|
- KeyChest triggers LevelManager to unlock door
|
||||||
|
- Chest properly handles coin and key treasures
|
||||||
|
- Both systems integrate with GameManager for centralized state tracking
|
||||||
|
- Events flow: Chest/KeyChest → GameManager → LevelManager → Door
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 9 ✓ - Improve Door Flow
|
||||||
|
**Files Modified:**
|
||||||
|
- `Door.cs` (refactored)
|
||||||
|
- `DoorInteract.cs` (refactored)
|
||||||
|
|
||||||
|
**What Changed:**
|
||||||
|
- **Door.cs**:
|
||||||
|
- Added explicit `IsLocked` state property
|
||||||
|
- `OpenDoor()` now updates lock state, visuals, and collision
|
||||||
|
- `LockDoor()` can re-lock the door if needed
|
||||||
|
- Event: `OnDoorOpened`
|
||||||
|
|
||||||
|
- **DoorInteract.cs**:
|
||||||
|
- Checks GameManager.HasKey instead of PlayerState
|
||||||
|
- Triggers LevelManager.NotifyLevelComplete() when player exits with key
|
||||||
|
- Prevents multiple completions with `_hasTriggered`
|
||||||
|
- Debug logging for testing
|
||||||
|
|
||||||
|
**Progression Flow:**
|
||||||
|
1. Player collects key → KeyChest → GameManager.SetKeyState(true)
|
||||||
|
2. GameManager triggers → LevelManager.NotifyKeyCollected()
|
||||||
|
3. LevelManager unlocks → Door.OpenDoor()
|
||||||
|
4. Player reaches exit → DoorInteract triggers
|
||||||
|
5. DoorInteract → LevelManager.NotifyLevelComplete()
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 10 ✓ - Folder Cleanup
|
||||||
|
**Folders Created:**
|
||||||
|
- `Assets/Scripts/Player/` - Player-related scripts
|
||||||
|
- `Assets/Scripts/Combat/` - Combat/projectile scripts
|
||||||
|
- `Assets/Scripts/Enemies/` - Enemy AI and spawning
|
||||||
|
- `Assets/Scripts/Environment/` - Environmental objects
|
||||||
|
- `Assets/Scripts/Utilities/` - Helper and utility classes
|
||||||
|
|
||||||
|
**Files Moved:**
|
||||||
|
- **Player/**: PlayerController, HammerThrower, PlayerState
|
||||||
|
- **Combat/**: Hammer
|
||||||
|
- **Enemies/**: EnemyAI, EnemySpawner (renamed from CharacterSpawner)
|
||||||
|
- **Environment/**: BreakableWall, Chest, Door, DoorInteract, KeyChest, MapElements/
|
||||||
|
- **Managers/**: GameManager, LevelManager, NoiseSystem, UIManager, InputManager (unchanged location)
|
||||||
|
- **ScriptableObject/**: TreasureSO, MapElementSO (unchanged location)
|
||||||
|
- **Utilities/**: Character, CameraFollow, IDoor, Enums, InputActions, MainMenu
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scene Setup Checklist
|
||||||
|
|
||||||
|
To get the project running after these changes:
|
||||||
|
|
||||||
|
- [ ] **Add GameManager to Scene**
|
||||||
|
- Create empty GameObject
|
||||||
|
- Add `GameManager` component
|
||||||
|
- Leave Debug Mode off for release
|
||||||
|
|
||||||
|
- [ ] **Add LevelManager to Scene**
|
||||||
|
- Create empty GameObject
|
||||||
|
- Add `LevelManager` component
|
||||||
|
- **In Inspector:** Assign the Door GameObject to `_doorReference` field
|
||||||
|
- Leave Debug Mode off for release
|
||||||
|
|
||||||
|
- [ ] **Add NoiseSystem to Scene**
|
||||||
|
- Create empty GameObject
|
||||||
|
- Add `NoiseSystem` component
|
||||||
|
- Ensure all enemy GameObjects have "Enemy" layer assigned
|
||||||
|
- Leave Debug Mode off for release
|
||||||
|
|
||||||
|
- [ ] **Update Enemy Layer**
|
||||||
|
- Select all enemy GameObjects
|
||||||
|
- Set Layer to "Enemy" (create if doesn't exist)
|
||||||
|
- This is critical for NoiseSystem to detect enemies
|
||||||
|
|
||||||
|
- [ ] **Update Scene References**
|
||||||
|
- Check for any scripts that directly reference moved classes
|
||||||
|
- Most will auto-resolve if using GetComponent<>()
|
||||||
|
- Update Inspector if scripts are assigned as references
|
||||||
|
|
||||||
|
- [ ] **Test the Flow:**
|
||||||
|
1. Player throws hammer (with cooldown)
|
||||||
|
2. Hammer hits enemy - enemy should stun and nearby enemies should hear noise
|
||||||
|
3. Player collects key - GameManager updates, door should unlock
|
||||||
|
4. Player exits through door - level completion triggered
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Architectural Improvements
|
||||||
|
|
||||||
|
1. **Clear Separation of Concerns**
|
||||||
|
- Hammer throwing vs. hammer behavior
|
||||||
|
- Player controller vs. player state vs. hammer equipment
|
||||||
|
- Enemy AI vs. enemy spawning
|
||||||
|
|
||||||
|
2. **Centralized Game State**
|
||||||
|
- GameManager handles all global state
|
||||||
|
- LevelManager orchestrates progression
|
||||||
|
- No scattered state management
|
||||||
|
|
||||||
|
3. **Event-Driven Architecture**
|
||||||
|
- Systems communicate via events
|
||||||
|
- Loose coupling between systems
|
||||||
|
- Easy to add new features
|
||||||
|
|
||||||
|
4. **Extensible Gameplay Systems**
|
||||||
|
- Noise system can be expanded for more sounds
|
||||||
|
- Enemy states make new behaviors easy to add
|
||||||
|
- Hammer can be enhanced with additional effects
|
||||||
|
|
||||||
|
5. **Inspector-Friendly**
|
||||||
|
- Tunable parameters for game feel
|
||||||
|
- Debug modes for testing
|
||||||
|
- Clear assignment fields
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files Summary
|
||||||
|
|
||||||
|
**New Files (3):**
|
||||||
|
- `Managers/GameManager.cs`
|
||||||
|
- `Managers/LevelManager.cs`
|
||||||
|
- `Managers/NoiseSystem.cs`
|
||||||
|
|
||||||
|
**Modified Files (9):**
|
||||||
|
- `Managers/UIManager.cs` (renamed from UiManager)
|
||||||
|
- `Player/HammerThrower.cs` (refactored)
|
||||||
|
- `Combat/Hammer.cs` (refactored)
|
||||||
|
- `Enemies/EnemyAI.cs` (refactored)
|
||||||
|
- `Environment/BreakableWall.cs` (updated)
|
||||||
|
- `Environment/Chest.cs` (updated)
|
||||||
|
- `Environment/KeyChest.cs` (updated)
|
||||||
|
- `Environment/Door.cs` (refactored)
|
||||||
|
- `Environment/DoorInteract.cs` (refactored)
|
||||||
|
|
||||||
|
**Renamed:**
|
||||||
|
- `CharacterSpawner.cs` → `EnemySpawner.cs`
|
||||||
|
|
||||||
|
**Moved (folder reorganization only, no content changes):**
|
||||||
|
- Multiple scripts distributed into logical folders
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- All existing gameplay is preserved
|
||||||
|
- The changes follow Unity best practices
|
||||||
|
- Systems are designed to be maintainable and extensible
|
||||||
|
- Debug modes can be disabled before release
|
||||||
|
- No major breaking changes - mostly additive improvements
|
||||||
|
|
||||||
|
**Status:** ✅ **All tasks complete and ready for testing**
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
# Quick Setup Guide - Post Migration
|
||||||
|
|
||||||
|
## Immediate Next Steps
|
||||||
|
|
||||||
|
### 1. Refresh Unity Project
|
||||||
|
- Open the Gnome's Bounty project in Unity
|
||||||
|
- Wait for asset import to complete
|
||||||
|
- Check Console for any errors related to moved files
|
||||||
|
|
||||||
|
### 2. Create Required GameObjects in Scene
|
||||||
|
|
||||||
|
**Add these to your main gameplay scene:**
|
||||||
|
|
||||||
|
#### GameManager
|
||||||
|
1. Right-click in Hierarchy → Create Empty
|
||||||
|
2. Name it "GameManager"
|
||||||
|
3. Drag GameManager script from `Assets/Scripts/Managers/GameManager.cs` onto it
|
||||||
|
4. Leave settings at defaults (debug mode optional)
|
||||||
|
|
||||||
|
#### LevelManager
|
||||||
|
1. Right-click in Hierarchy → Create Empty
|
||||||
|
2. Name it "LevelManager"
|
||||||
|
3. Drag LevelManager script from `Assets/Scripts/Managers/LevelManager.cs` onto it
|
||||||
|
4. **IMPORTANT:** In Inspector, drag your Door GameObject to the `_doorReference` field
|
||||||
|
5. Leave settings at defaults (debug mode optional)
|
||||||
|
|
||||||
|
#### NoiseSystem
|
||||||
|
1. Right-click in Hierarchy → Create Empty
|
||||||
|
2. Name it "NoiseSystem"
|
||||||
|
3. Drag NoiseSystem script from `Assets/Scripts/Managers/NoiseSystem.cs` onto it
|
||||||
|
4. Enemy Layer should be set to "Enemy" automatically
|
||||||
|
5. Leave settings at defaults (debug mode optional)
|
||||||
|
|
||||||
|
### 3. Configure Layers
|
||||||
|
|
||||||
|
1. In Hierarchy, select all enemy GameObjects
|
||||||
|
2. In Inspector, change Layer to "Enemy"
|
||||||
|
3. Create the layer if it doesn't exist (Layer dropdown → Add Layer → "Enemy")
|
||||||
|
|
||||||
|
### 4. Testing Checklist
|
||||||
|
|
||||||
|
After setup, test these features:
|
||||||
|
|
||||||
|
- [ ] **Hammer Throw**: Throw hammer and verify cooldown works (should delay next throw by ~1.5 sec)
|
||||||
|
- [ ] **Enemy Stun**: Hit an enemy with hammer, it should freeze for 1 second
|
||||||
|
- [ ] **Wall Break**: Break a breakable wall with hammer
|
||||||
|
- [ ] **Noise System**: Break a wall or hit enemy near another enemy - second enemy should investigate
|
||||||
|
- [ ] **Key Collection**: Pick up key, GameManager should show `HasKey = true`
|
||||||
|
- [ ] **Door Unlock**: After picking up key, door should open automatically
|
||||||
|
- [ ] **Level Complete**: Exit through open door with key - should trigger level completion
|
||||||
|
|
||||||
|
### 5. If Compilation Errors Occur
|
||||||
|
|
||||||
|
**Most likely causes:**
|
||||||
|
|
||||||
|
1. **"Cannot find type 'GameManager'"** - Make sure GameManager component is added to scene
|
||||||
|
2. **"Missing script references"** - Delete and re-add the script component
|
||||||
|
3. **"Cannot find EnemyAI"** - Verify EnemyAI.cs moved to `Enemies/` folder correctly
|
||||||
|
|
||||||
|
**Quick fix:**
|
||||||
|
- In Unity, go to Assets → Reimport All
|
||||||
|
- Wait for compilation to complete
|
||||||
|
- Check Console for remaining errors
|
||||||
|
|
||||||
|
### 6. Debug Features
|
||||||
|
|
||||||
|
To enable debug logging in managers:
|
||||||
|
|
||||||
|
1. Select GameManager in Hierarchy
|
||||||
|
2. In Inspector, check `Debug Mode`
|
||||||
|
3. Repeat for LevelManager and NoiseSystem
|
||||||
|
4. Play game - console will show state changes
|
||||||
|
5. **Remember to disable before final build**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What If You Encounter Errors?
|
||||||
|
|
||||||
|
### Scenes Won't Load
|
||||||
|
- Unity may need to reload scenes
|
||||||
|
- Close the scene, delete from Recent, and re-open
|
||||||
|
|
||||||
|
### "CharacterSpawner" not found
|
||||||
|
- This class was renamed to `EnemySpawner`
|
||||||
|
- Update any scene prefabs or scripts that reference it
|
||||||
|
- Check `Enemies/EnemySpawner.cs`
|
||||||
|
|
||||||
|
### PlayerController Errors
|
||||||
|
- Check that `GetComponent<HammerThrower>()` can still find it
|
||||||
|
- If not, re-add PlayerController script to Player GameObject
|
||||||
|
|
||||||
|
### Missing References in Inspector
|
||||||
|
- Right-click the field → "Try Find Component"
|
||||||
|
- Or manually drag the object from scene/hierarchy
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Notes
|
||||||
|
|
||||||
|
The new systems have minimal performance impact:
|
||||||
|
|
||||||
|
- **GameManager**: O(1) operations, just stores state
|
||||||
|
- **LevelManager**: Minimal, just tracks key/completion
|
||||||
|
- **NoiseSystem**: O(n) where n = enemies in range (typically small)
|
||||||
|
- **EnemyAI States**: Simple state switching, no AI overhead
|
||||||
|
|
||||||
|
No performance concerns with default settings.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Development Steps
|
||||||
|
|
||||||
|
With the architecture in place, you can now easily:
|
||||||
|
|
||||||
|
1. **Add new hammer effects** - Modify Hammer.cs
|
||||||
|
2. **Add new enemy behaviors** - Add states to EnemyAI.cs
|
||||||
|
3. **Add puzzles** - Create new interaction systems using GameManager
|
||||||
|
4. **Add levels** - Use GameManager/LevelManager for progression
|
||||||
|
5. **Add UI** - Hook into GameManager events for HUD updates
|
||||||
|
|
||||||
|
All systems use events for loose coupling, making extensions clean and safe.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Location Reference
|
||||||
|
|
||||||
|
| What | Location |
|
||||||
|
|------|----------|
|
||||||
|
| Player Controller | `Assets/Scripts/Player/PlayerController.cs` |
|
||||||
|
| Hammer Mechanics | `Assets/Scripts/Combat/Hammer.cs` |
|
||||||
|
| Hammer Throwing | `Assets/Scripts/Player/HammerThrower.cs` |
|
||||||
|
| Enemy AI | `Assets/Scripts/Enemies/EnemyAI.cs` |
|
||||||
|
| Enemy Spawner | `Assets/Scripts/Enemies/EnemySpawner.cs` |
|
||||||
|
| Game State | `Assets/Scripts/Managers/GameManager.cs` |
|
||||||
|
| Level Control | `Assets/Scripts/Managers/LevelManager.cs` |
|
||||||
|
| Noise System | `Assets/Scripts/Managers/NoiseSystem.cs` |
|
||||||
|
| UI Manager | `Assets/Scripts/Managers/UIManager.cs` |
|
||||||
|
| Environment | `Assets/Scripts/Environment/*.cs` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Status:** Ready to import into Unity and test! 🎮
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
private void OnFireButtonPressed()
|
||||||
{
|
{
|
||||||
if (_hammer == null)
|
if (_hammerThrower.CanThrow)
|
||||||
{
|
{
|
||||||
_animator.SetTrigger("Body_ThrowHammer");
|
_animator.SetTrigger("Body_ThrowHammer");
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ public class PlayerController : Character
|
|||||||
// Animation event
|
// Animation event
|
||||||
public void ThrowHammerObject()
|
public void ThrowHammerObject()
|
||||||
{
|
{
|
||||||
_hammerThrower.ThrowHammer();
|
_hammerThrower.TryThrowHammer();
|
||||||
UpdatePlayerSprite();
|
UpdatePlayerSprite();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
public class CharacterSpawner : MonoBehaviour
|
public class EnemySpawner : MonoBehaviour
|
||||||
{
|
{
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private GameObject _prefab;
|
private GameObject _prefab;
|
||||||
+152
-37
@@ -1,56 +1,171 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
public enum EnemyState
|
||||||
|
{
|
||||||
|
Patrol,
|
||||||
|
Investigate,
|
||||||
|
Chase,
|
||||||
|
Stunned
|
||||||
|
}
|
||||||
|
|
||||||
public class EnemyAI : Character
|
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)
|
protected override void SetClimbingAnimation(bool isClimbing)
|
||||||
{
|
{
|
||||||
|
// Implement climbing animation if needed
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void SetWalkingAnimation(bool isWalking)
|
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()
|
private void Update()
|
||||||
{
|
{
|
||||||
// float horizontal = 0;
|
// Update stun timer
|
||||||
// float vertical = 0;
|
if (_currentState == EnemyState.Stunned)
|
||||||
|
{
|
||||||
// Vector2 directionToPlayer = Player.Instance.transform.position - transform.position;
|
_stunTimer -= Time.deltaTime;
|
||||||
// directionToPlayer.Normalize();
|
if (_stunTimer <= 0f)
|
||||||
|
{
|
||||||
// float verticalDistance = Player.Instance.transform.position.y - transform.position.y;
|
SetState(EnemyState.Patrol);
|
||||||
|
}
|
||||||
// if (Mathf.Abs(verticalDistance) > 0.1f && (isCanClimbUp || isCanGoDown))
|
return;
|
||||||
// {
|
|
||||||
// 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; }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (Input.GetKey(KeyCode.T))
|
|
||||||
// { vertical = 1; }
|
|
||||||
// if (Input.GetKey(KeyCode.G))
|
|
||||||
// { vertical = -1; }
|
|
||||||
|
|
||||||
// Debug.Log($"Enemy Position: {transform.position}, Player Position: {Player.Instance.transform.position}");
|
|
||||||
// Debug.Log($"Vertical Distance: {verticalDistance}, Vertical Movement: {vertical}");
|
|
||||||
|
|
||||||
// base.MoveTo(horizontal, vertical);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private float VerticalMove(float verticalDistance)
|
// Get player position if available
|
||||||
|
var player = Player.Instance;
|
||||||
|
if (player == null)
|
||||||
{
|
{
|
||||||
float verticalDirection = Mathf.Sign(verticalDistance);
|
HandlePatrol();
|
||||||
return verticalDirection;
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 86cce1993173eb04daddb1edd164da94
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
public class BreakableWall : MapElement
|
public class BreakableWall : MapElement
|
||||||
{
|
{
|
||||||
|
[SerializeField] private float _noiseRadius = 10f;
|
||||||
|
[SerializeField] private bool _emitNoiseOnBreak = true;
|
||||||
|
|
||||||
private float _respawnElementTimer;
|
private float _respawnElementTimer;
|
||||||
private int _respawnTimeout = 4;
|
private int _respawnTimeout = 4;
|
||||||
private bool _needRespawn = false;
|
private bool _needRespawn = false;
|
||||||
@@ -16,18 +19,24 @@ public class BreakableWall : MapElement
|
|||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
_respawnElementTimer = _respawnTimeout;
|
_respawnElementTimer = _respawnTimeout;
|
||||||
_boxCollider =GetComponent<BoxCollider2D>();
|
_boxCollider = GetComponent<BoxCollider2D>();
|
||||||
_spriteRenderer= GetComponentInChildren<SpriteRenderer>();
|
_spriteRenderer = GetComponentInChildren<SpriteRenderer>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Hit()
|
public override void Hit()
|
||||||
{
|
{
|
||||||
IsEnabled = false;
|
IsEnabled = false;
|
||||||
_boxCollider.isTrigger=true;
|
_boxCollider.isTrigger = true;
|
||||||
_spriteRenderer.enabled = IsEnabled;
|
_spriteRenderer.enabled = IsEnabled;
|
||||||
|
|
||||||
Instantiate(_hitParticles, transform.position, Quaternion.identity);
|
Instantiate(_hitParticles, transform.position, Quaternion.identity);
|
||||||
|
|
||||||
|
// Emit noise when wall breaks
|
||||||
|
if (_emitNoiseOnBreak && NoiseSystem.Instance != null)
|
||||||
|
{
|
||||||
|
NoiseSystem.Instance.Emit(transform.position, _noiseRadius);
|
||||||
|
}
|
||||||
|
|
||||||
_respawnElementTimer = _respawnTimeout;
|
_respawnElementTimer = _respawnTimeout;
|
||||||
_needRespawn = true;
|
_needRespawn = true;
|
||||||
}
|
}
|
||||||
@@ -41,7 +50,7 @@ public class BreakableWall : MapElement
|
|||||||
{
|
{
|
||||||
_respawnElementTimer = _respawnTimeout;
|
_respawnElementTimer = _respawnTimeout;
|
||||||
|
|
||||||
if(_characterInRange)
|
if (_characterInRange)
|
||||||
{
|
{
|
||||||
print("Character is dead");
|
print("Character is dead");
|
||||||
}
|
}
|
||||||
@@ -70,6 +79,4 @@ public class BreakableWall : MapElement
|
|||||||
_characterInRange = false;
|
_characterInRange = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -8,31 +8,43 @@ public class Chest : MonoBehaviour
|
|||||||
private TreasureSO _treasureSO;
|
private TreasureSO _treasureSO;
|
||||||
|
|
||||||
private Transform _treasureObject;
|
private Transform _treasureObject;
|
||||||
private bool _isOpen=false;
|
private bool _isOpen = false;
|
||||||
|
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
_treasureObject=transform.GetChild(1);
|
_treasureObject = transform.GetChild(1);
|
||||||
var spriteRenderer=_treasureObject.GetComponent<SpriteRenderer>();
|
var spriteRenderer = _treasureObject.GetComponent<SpriteRenderer>();
|
||||||
|
|
||||||
spriteRenderer.sprite = _treasureSO.Image;
|
spriteRenderer.sprite = _treasureSO.Image;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTriggerEnter2D(Collider2D collider)
|
private void OnTriggerEnter2D(Collider2D collider)
|
||||||
{
|
{
|
||||||
var playerState=collider.GetComponent<PlayerState>();
|
var playerState = collider.GetComponent<PlayerState>();
|
||||||
if (playerState != null && !_isOpen)
|
if (playerState != null && !_isOpen)
|
||||||
{
|
{
|
||||||
_isOpen = true;
|
_isOpen = true;
|
||||||
animator.SetTrigger("OpenChest");
|
animator.SetTrigger("OpenChest");
|
||||||
|
|
||||||
switch (_treasureSO.Treasure)
|
switch (_treasureSO.Treasure)
|
||||||
{
|
{
|
||||||
case TreasureType.Coin:
|
case TreasureType.Coin:
|
||||||
playerState.AddCoin();
|
playerState.AddCoin();
|
||||||
|
if (GameManager.Instance != null)
|
||||||
|
{
|
||||||
|
GameManager.Instance.AddTreasure(1);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case TreasureType.Key:
|
case TreasureType.Key:
|
||||||
playerState.SetKey();
|
playerState.SetKey();
|
||||||
|
if (GameManager.Instance != null)
|
||||||
|
{
|
||||||
|
GameManager.Instance.SetKeyState(true);
|
||||||
|
}
|
||||||
|
if (LevelManager.Instance != null)
|
||||||
|
{
|
||||||
|
LevelManager.Instance.NotifyKeyCollected();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,63 @@
|
|||||||
using Assets.Scripts;
|
using Assets.Scripts;
|
||||||
|
using System;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
public class Door : MonoBehaviour,IDoor
|
public class Door : MonoBehaviour, IDoor
|
||||||
{
|
{
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private Sprite _openDoor;
|
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()
|
public void OpenDoor()
|
||||||
{
|
{
|
||||||
//PlayOpenAnimation
|
if (!_isLocked)
|
||||||
GetComponentInChildren<SpriteRenderer>().sprite= _openDoor;
|
return;
|
||||||
//Disable box collider
|
|
||||||
gameObject.GetComponent<BoxCollider2D>().enabled=false;
|
_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
|
public class DoorInteract : MonoBehaviour
|
||||||
{
|
{
|
||||||
[SerializeField] private GameObject _doorGameObject;
|
[SerializeField] private GameObject _doorGameObject;
|
||||||
|
[SerializeField] private bool _debugMode = false;
|
||||||
|
|
||||||
private IDoor _door;
|
private IDoor _door;
|
||||||
|
private bool _hasTriggered = false;
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
_door =_doorGameObject.GetComponent<IDoor>();
|
_door = _doorGameObject.GetComponent<IDoor>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTriggerEnter2D(Collider2D collider)
|
private void OnTriggerEnter2D(Collider2D collider)
|
||||||
{
|
{
|
||||||
|
if (_hasTriggered)
|
||||||
|
return;
|
||||||
|
|
||||||
var playerState = collider.GetComponent<PlayerState>();
|
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!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,96 @@ using UnityEngine;
|
|||||||
|
|
||||||
public class Hammer : MonoBehaviour
|
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()
|
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)
|
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>();
|
var mapElement = collision.collider.GetComponent<MapElement>();
|
||||||
if (mapElement != null)
|
if (mapElement != null && mapElement is BreakableWall)
|
||||||
{
|
{
|
||||||
mapElement.Hit();
|
mapElement.Hit();
|
||||||
|
EmitImpactNoise(collision.GetContact(0).point);
|
||||||
Destroy(gameObject);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,43 +6,60 @@ public class HammerThrower : MonoBehaviour
|
|||||||
[SerializeField] private Transform _spawnPoint;
|
[SerializeField] private Transform _spawnPoint;
|
||||||
[SerializeField] private GameObject _hammerPrefab;
|
[SerializeField] private GameObject _hammerPrefab;
|
||||||
[SerializeField] private float _throwSpeed = 5f;
|
[SerializeField] private float _throwSpeed = 5f;
|
||||||
|
[SerializeField] private float _throwCooldown = 1.5f;
|
||||||
|
|
||||||
private GameObject _currentHammer;
|
private GameObject _currentHammer;
|
||||||
private bool _hasHammer = true;
|
private bool _hasHammer = true;
|
||||||
private bool _facingRight = true;
|
private bool _facingRight = true;
|
||||||
|
private float _cooldownTimer = 0f;
|
||||||
|
|
||||||
public bool HasHammer => _hasHammer;
|
public bool HasHammer => _hasHammer;
|
||||||
|
public bool CanThrow => _hasHammer && _cooldownTimer <= 0f;
|
||||||
|
public float CooldownRemaining => Mathf.Max(0f, _cooldownTimer);
|
||||||
|
|
||||||
public void SetFacingDirection(bool facingRight)
|
public void SetFacingDirection(bool facingRight)
|
||||||
{
|
{
|
||||||
_facingRight = facingRight;
|
_facingRight = facingRight;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ThrowHammer()
|
public bool TryThrowHammer()
|
||||||
{
|
{
|
||||||
if (!_hasHammer)
|
if (!CanThrow)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
|
ThrowHammer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ThrowHammer()
|
||||||
|
{
|
||||||
_hasHammer = false;
|
_hasHammer = false;
|
||||||
|
_cooldownTimer = _throwCooldown;
|
||||||
|
|
||||||
_currentHammer = Instantiate(_hammerPrefab, _spawnPoint.position, _spawnPoint.rotation);
|
_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>();
|
// Flip hammer visual based on direction
|
||||||
//rb.linearVelocity = new Vector2(direction * _throwSpeed, 0);
|
_currentHammer.transform.localScale = new Vector2(
|
||||||
|
_currentHammer.transform.localScale.x * (_facingRight ? 1 : -1),
|
||||||
//// Flip hammer visually
|
_currentHammer.transform.localScale.y
|
||||||
//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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
|
// Update cooldown timer
|
||||||
|
if (_cooldownTimer > 0f)
|
||||||
|
{
|
||||||
|
_cooldownTimer -= Time.deltaTime;
|
||||||
|
}
|
||||||
|
|
||||||
// Hammer destroyed → hammer returned
|
// Hammer destroyed → hammer returned
|
||||||
if (!_hasHammer && _currentHammer == null)
|
if (!_hasHammer && _currentHammer == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,14 +1,35 @@
|
|||||||
using System.Security;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
public class KeyChest : MonoBehaviour
|
public class KeyChest : MonoBehaviour
|
||||||
{
|
{
|
||||||
|
private bool _isOpened = false;
|
||||||
|
|
||||||
private void OnTriggerEnter2D(Collider2D collider)
|
private void OnTriggerEnter2D(Collider2D collider)
|
||||||
{
|
{
|
||||||
|
if (_isOpened)
|
||||||
|
return;
|
||||||
|
|
||||||
var playerState = collider.GetComponent<PlayerState>();
|
var playerState = collider.GetComponent<PlayerState>();
|
||||||
if (playerState != null)
|
if (playerState != null)
|
||||||
{
|
{
|
||||||
|
_isOpened = true;
|
||||||
|
|
||||||
|
// Update player state
|
||||||
playerState.SetKey();
|
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);
|
Destroy(gameObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,7 +1,7 @@
|
|||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
public class UiManager : MonoBehaviour
|
public class UIManager : MonoBehaviour
|
||||||
{
|
{
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private TextMeshProUGUI _totalCoins;
|
private TextMeshProUGUI _totalCoins;
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: f7c871dd492d04149b39713e47b77dc6
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user