diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..77fa0eb4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,329 @@ +# Colosseum - Unity Game Project + +## Project Overview + +Multiplayer arena game built with **Unity 6000.3.10f1** and **Unity Netcode for GameObjects**. + +- **Language**: C# 9.0 +- **Target Framework**: .NET Standard 2.1 +- **Root Namespace**: `Colosseum` +- **Assembly Definition**: `Colosseum.Game` (Assets/Scripts/Colosseum.Game.asmdef) + +## Game Design Documentation + +Design docs are maintained in Obsidian Vault: `C:\Users\dal4s\OneDrive\문서\Obsidian Vault\Colosseum` + +### Game Concept +- **Genre**: Online multiplayer co-op action RPG (3rd person) +- **Theme**: Gladiator survival boss raid in Colosseum +- **Roles**: Multi-role system - players can hybridize (e.g., Tank 0.5 + DPS 0.5) instead of fixed Tank/DPS/Healer + +### Stats System + +| Stat | Abbr | Description | Derived Formula | +|------|------|-------------|-----------------| +| Strength | STR | Weapon damage | Physical Damage = STR × 2 | +| Dexterity | DEX | Ranged aim/damage, melee speed | Ranged Damage = DEX × 2 | +| Intelligence | INT | Magic damage | Magic Damage = INT × 2 | +| Vitality | VIT | Max health | Max HP = VIT × 10 | +| Wisdom | WIS | Healing power | Heal Power = WIS × 1.5 | +| Spirit | SPI | Max mana | Max MP = SPI × 5 | + +### Damage Calculation + +``` +Final Damage = baseDamage + (statDamage × statScaling) +``` + +| DamageType | Base Stat | Description | +|------------|-----------|-------------| +| Physical | STR | Melee weapon damage | +| Magical | INT | Spell damage | +| Ranged | DEX | Bow/ranged damage | +| True | None | Fixed damage, no stat scaling | + +### Stat Modifier System + +Modifiers are applied in order: +1. **Flat**: Add fixed value +2. **PercentAdd**: Sum percentages, then multiply +3. **PercentMult**: Multiply individually + +``` +Final = (Base + FlatSum) × (1 + PercentAddSum) × PercentMult1 × PercentMult2... +``` + +### Skill System +- **Active Skills**: 6 slots (L-click, R-click, 1, 2, 3, 4) +- **Passive Skills**: Tree-based progression from center +- **Effects**: Triggered via animation events (`OnEffect(index)`) +- **Animation**: Start clip + optional end clip + +## Build/Run Commands + +This is a Unity project. Use Unity Editor for building and testing. + +```bash +# Open in Unity Editor (requires Unity Hub) +# Build via: File > Build Settings > Build + +# Run tests in Unity Editor +# Window > General > Test Runner > EditMode / PlayMode +``` + +### Build from Command Line (Windows) + +```bash +# Build Windows standalone (adjust paths as needed) +"C:\Program Files\Unity\Hub\Editor\6000.3.10f1\Editor\Unity.exe" -batchmode -projectPath . -buildWindows64Player ./Builds/Windows/Colosseum.exe -quit +``` + +## Project Structure + +``` +Assets/ + Scripts/ + Abnormalities/ # Buff/debuff system + Editor/ # Unity editor extensions + Network/ # Network management + Player/ # Player controllers + Skills/ # Skill system + Effects/ # Skill effects (damage, heal, etc.) + Stats/ # Character statistics + UI/ # User interface +``` + +## Code Style Guidelines + +### Namespaces + +Follow `Colosseum.{Subnamespace}` pattern: + +```csharp +namespace Colosseum.Player { } +namespace Colosseum.Skills { } +namespace Colosseum.Skills.Effects { } +namespace Colosseum.Stats { } +namespace Colosseum.Network { } +namespace Colosseum.Abnormalities { } +``` + +### Using Statements Order + +Organize imports in this order, separated by blank lines: + +1. System namespaces +2. UnityEngine namespaces +3. Unity.Netcode / Unity packages +4. Colosseum namespaces + +```csharp +using System; +using System.Collections.Generic; + +using UnityEngine; + +using Unity.Netcode; + +using Colosseum.Stats; +using Colosseum.Player; +``` + +### Naming Conventions + +| Element | Convention | Example | +|---------|------------|---------| +| Classes | PascalCase | `PlayerNetworkController` | +| Interfaces | IPascalCase | `IDamageable` | +| Methods | PascalCase | `TakeDamageRpc()` | +| Public Properties | PascalCase | `MaxHealth`, `IsStunned` | +| Private Fields | camelCase | `currentHealth`, `characterStats` | +| Constants | PascalCase or SCREAMING_SNAKE | `SKILL_STATE_NAME`, `MaxValue` | +| Enum values | PascalCase | `DamageType.Physical` | + +### Serialization & Inspector + +Use `[SerializeField]` with `[Header]` and `[Tooltip]` for organization: + +```csharp +[Header("References")] +[Tooltip("CharacterStats component (auto-searched if null)")] +[SerializeField] private CharacterStats characterStats; + +[Header("Settings")] +[Min(0f)] [SerializeField] private float baseDamage = 10f; +[SerializeField] private DamageType damageType = DamageType.Physical; +``` + +### Documentation + +Use XML documentation comments in **Korean**: + +```csharp +/// +/// 플레이어 네트워크 상태 관리 (HP, MP 등) +/// +public class PlayerNetworkController : NetworkBehaviour +{ + /// + /// 대미지 적용 (서버에서만 실행) + /// + [Rpc(SendTo.Server)] + public void TakeDamageRpc(float damage) + { + // ... + } +} +``` + +### Network Code Patterns + +Use Unity Netcode patterns: + +```csharp +// Network variables for synchronized state +private NetworkVariable currentHealth = new NetworkVariable(100f); + +// Server RPCs for client-to-server calls +[Rpc(SendTo.Server)] +public void TakeDamageRpc(float damage) +{ + currentHealth.Value = Mathf.Max(0f, currentHealth.Value - damage); +} + +// Check authority before modifying +if (IsServer) +{ + currentHealth.Value = MaxHealth; +} +``` + +### Expression Body Members + +Use for simple properties and methods: + +```csharp +public float MaxHealth => vitality.FinalValue * 10f; +public bool IsStunned => stunCount > 0; +public bool CanAct => !IsStunned; +``` + +### Switch Expressions + +Prefer switch expressions for concise mapping: + +```csharp +public CharacterStat GetStat(StatType statType) +{ + return statType switch + { + StatType.Strength => strength, + StatType.Dexterity => dexterity, + StatType.Intelligence => intelligence, + _ => null, + }; +} +``` + +### ScriptableObjects for Data + +Use ScriptableObject for configuration data: + +```csharp +[CreateAssetMenu(fileName = "NewSkill", menuName = "Colosseum/Skill")] +public class SkillData : ScriptableObject +{ + [SerializeField] private string skillName; + [SerializeField] private List effects; + + public string SkillName => skillName; + public IReadOnlyList Effects => effects; +} +``` + +### Error Handling + +- Use `Debug.LogWarning()` for recoverable issues +- Use `Debug.LogError()` for critical failures +- Null-check parameters in public methods + +```csharp +public void ApplyAbnormality(AbnormalityData data, GameObject source) +{ + if (data == null) + { + Debug.LogWarning("[Abnormality] ApplyAbnormality called with null data"); + return; + } + // ... +} +``` + +### Events + +Use C# events with `Action` or custom delegates: + +```csharp +public event Action OnAbnormalityAdded; +public event Action OnAbnormalityRemoved; +public event Action OnAbnormalitiesChanged; + +// Invoke with null-conditional +OnAbnormalityAdded?.Invoke(newAbnormality); +``` + +## Key Dependencies + +| Package | Purpose | +|---------|---------| +| Unity.Netcode.Runtime | Multiplayer networking | +| Unity.InputSystem | New input system | +| Unity.TextMeshPro | Text rendering | +| Unity.Networking.Transport | Low-level networking | + +## Common Patterns + +### MonoBehaviour Components + +```csharp +public class ExampleComponent : MonoBehaviour +{ + [Header("References")] + [SerializeField] private Animator animator; + + public Animator Animator => animator; + + private void Awake() + { + if (animator == null) + animator = GetComponentInChildren(); + } +} +``` + +### NetworkBehaviour Components + +```csharp +public class NetworkedComponent : NetworkBehaviour +{ + private NetworkVariable value = new NetworkVariable(); + + public override void OnNetworkSpawn() + { + // Initialize networked state + } + + public override void OnNetworkDespawn() + { + // Cleanup + } +} +``` + +## Notes + +- All code comments and documentation should be in Korean +- Use `[Min()]` attribute for numeric minimums in Inspector +- Use `[TextArea]` for multi-line string fields +- Private fields should use `camelCase` (no `m_` or `_` prefix) +- Prefer `IReadOnlyList` for exposing collections