네트워크 멀티플레이 환경 문제 수정
관련 문제가 다시 발생하면 이 커밋으로 돌아올 것
This commit is contained in:
@@ -1,120 +0,0 @@
|
|||||||
# Automated Prefab Generation Pipeline
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
This system automates the creation of monster prefabs from CSV data. No manual prefab setup required!
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
1. **CSV Data** → **ScriptableObject (SO)** → **Prefab** (automatically generated)
|
|
||||||
2. Template prefabs define required components and defaults
|
|
||||||
3. CSV importer creates complete prefabs with mesh, components, and SO references
|
|
||||||
|
|
||||||
## Setup Instructions
|
|
||||||
|
|
||||||
### Step 1: Create Template Prefab
|
|
||||||
|
|
||||||
1. In Unity, go to `Tools > Data > Create Monster Template`
|
|
||||||
2. This creates `Assets/Data/Templates/MonsterTemplate.prefab`
|
|
||||||
3. The template includes all required components:
|
|
||||||
- NetworkObject
|
|
||||||
- EnemyUnit
|
|
||||||
- MonsterDataComponent (links to SO)
|
|
||||||
- NavMeshAgent
|
|
||||||
- EnemyAIController
|
|
||||||
- CapsuleCollider
|
|
||||||
- MeshFilter & MeshRenderer
|
|
||||||
|
|
||||||
4. **Customize the template if needed:**
|
|
||||||
- Adjust default AI behavior
|
|
||||||
- Set default collider size
|
|
||||||
- Add visual effects
|
|
||||||
- Configure NavMeshAgent settings
|
|
||||||
|
|
||||||
### Step 2: Update CSV with Mesh Path
|
|
||||||
|
|
||||||
Add `meshPath` column to your `Monster.csv`:
|
|
||||||
|
|
||||||
```csv
|
|
||||||
id,name,memo,moveSpeed,maxHp,atkRange,atkDamage,atkIntervalSec,meshPath,cost,weight
|
|
||||||
101,Grunt,Basic Monster,2.6,30,1,3,1.2,Assets/Models/Monsters/Monster101.fbx,1,1.0
|
|
||||||
102,Fast,Fast/Weak,3.4,18,1,2,1,Assets/Models/Monsters/Monster102.fbx,2,0.5
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Import Data
|
|
||||||
|
|
||||||
1. In Unity, go to `Tools > Data > Import All CSV`
|
|
||||||
2. The importer will:
|
|
||||||
- Create/update SO files in `Assets/Data/ScriptableObjects/Monster/`
|
|
||||||
- Create/update prefabs in `Assets/Prefabs/Monster/`
|
|
||||||
- Apply mesh from `meshPath` column
|
|
||||||
- Link SO reference to prefab's MonsterDataComponent
|
|
||||||
|
|
||||||
### Step 4: Use Prefabs
|
|
||||||
|
|
||||||
Your generated prefabs are ready to use! Example with EnemyPortal:
|
|
||||||
- Open EnemyPortal prefab
|
|
||||||
- Click "Load Monster Data" button
|
|
||||||
- All monster prefabs are automatically loaded
|
|
||||||
|
|
||||||
## File Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
Assets/
|
|
||||||
├── Data/
|
|
||||||
│ ├── ScriptableObjects/
|
|
||||||
│ │ └── Monster/ # SO files (generated from CSV)
|
|
||||||
│ └── Templates/ # Template prefabs (created once)
|
|
||||||
│ └── MonsterTemplate.prefab
|
|
||||||
├── Prefabs/
|
|
||||||
│ └── Monster/ # Generated monster prefabs
|
|
||||||
│ ├── Monster101.prefab
|
|
||||||
│ ├── Monster102.prefab
|
|
||||||
│ └── ...
|
|
||||||
└── GameData/
|
|
||||||
└── Monster.csv # Source data (editable)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Benefits
|
|
||||||
|
|
||||||
- ✅ **One source of truth**: Edit CSV, everything updates
|
|
||||||
- ✅ **No manual setup**: Prefabs generated automatically
|
|
||||||
- ✅ **Designer-friendly**: Templates visual, CSV simple
|
|
||||||
- ✅ **Error-proof**: All components guaranteed to exist
|
|
||||||
- ✅ **Easy customization**: Edit template once, applies to all
|
|
||||||
|
|
||||||
## Customization
|
|
||||||
|
|
||||||
### Adding New Components
|
|
||||||
1. Open `MonsterTemplate.prefab`
|
|
||||||
2. Add the component
|
|
||||||
3. Configure defaults
|
|
||||||
4. Save template
|
|
||||||
5. All future imports will include it
|
|
||||||
|
|
||||||
### Modifying Existing Monsters
|
|
||||||
- Edit the prefab directly (changes persist on next import)
|
|
||||||
- OR modify CSV and re-import (will update SO link and mesh)
|
|
||||||
|
|
||||||
### Creating Other Data Types
|
|
||||||
Use the same pattern:
|
|
||||||
1. Create template: `Tools > Data > Create Tower Template`
|
|
||||||
2. Add meshPath column to Tower.csv
|
|
||||||
3. Import: `Tools > Data > Import All CSV`
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
**Prefab not created?**
|
|
||||||
- Check if template exists in `Assets/Data/Templates/`
|
|
||||||
- Verify `meshPath` column in CSV
|
|
||||||
|
|
||||||
**Mesh not showing?**
|
|
||||||
- Verify `meshPath` is correct in CSV
|
|
||||||
- Check that mesh file exists at specified path
|
|
||||||
|
|
||||||
**Components not configured?**
|
|
||||||
- Edit template prefab
|
|
||||||
- Re-import CSV to apply template changes to new prefabs
|
|
||||||
|
|
||||||
**Want to keep manual prefab edits?**
|
|
||||||
- Prefabs created by importer are NOT overwritten
|
|
||||||
- Edits persist on next import (SO and mesh only update)
|
|
||||||
@@ -69,6 +69,7 @@
|
|||||||
<Compile Include="Assets\Scripts\BuildingDamageTest.cs" />
|
<Compile Include="Assets\Scripts\BuildingDamageTest.cs" />
|
||||||
<Compile Include="Assets\Scripts\EnemyAIController.cs" />
|
<Compile Include="Assets\Scripts\EnemyAIController.cs" />
|
||||||
<Compile Include="Assets\Scripts\EquipmentSocket.cs" />
|
<Compile Include="Assets\Scripts\EquipmentSocket.cs" />
|
||||||
|
<Compile Include="Assets\Scripts\DebugLogUI.cs" />
|
||||||
<Compile Include="Assets\Data\Scripts\DataClasses\PlayerData.cs" />
|
<Compile Include="Assets\Data\Scripts\DataClasses\PlayerData.cs" />
|
||||||
<Compile Include="Assets\Scripts\TeamManager.cs" />
|
<Compile Include="Assets\Scripts\TeamManager.cs" />
|
||||||
<Compile Include="Assets\Scripts\TeamGate.cs" />
|
<Compile Include="Assets\Scripts\TeamGate.cs" />
|
||||||
@@ -82,6 +83,7 @@
|
|||||||
<Compile Include="Assets\Scripts\BuildingFoundation.cs" />
|
<Compile Include="Assets\Scripts\BuildingFoundation.cs" />
|
||||||
<Compile Include="Assets\Scripts\AttackAction.cs" />
|
<Compile Include="Assets\Scripts\AttackAction.cs" />
|
||||||
<Compile Include="Assets\Scripts\IInteractable.cs" />
|
<Compile Include="Assets\Scripts\IInteractable.cs" />
|
||||||
|
<Compile Include="Assets\Scripts\ServerResourceManager.cs" />
|
||||||
<Compile Include="Assets\FlatKit\Demos\Common\Scripts\UvScroller.cs" />
|
<Compile Include="Assets\FlatKit\Demos\Common\Scripts\UvScroller.cs" />
|
||||||
<Compile Include="Assets\Scripts\GlobalTimer.cs" />
|
<Compile Include="Assets\Scripts\GlobalTimer.cs" />
|
||||||
<Compile Include="Assets\InputSystem_Actions.cs" />
|
<Compile Include="Assets\InputSystem_Actions.cs" />
|
||||||
@@ -103,13 +105,16 @@
|
|||||||
<Compile Include="Assets\Scripts\NetworkSpawnManager.cs" />
|
<Compile Include="Assets\Scripts\NetworkSpawnManager.cs" />
|
||||||
<Compile Include="Assets\Scripts\FogOfWarSystem.cs" />
|
<Compile Include="Assets\Scripts\FogOfWarSystem.cs" />
|
||||||
<Compile Include="Assets\Data\Scripts\DataClasses\MonsterData.cs" />
|
<Compile Include="Assets\Data\Scripts\DataClasses\MonsterData.cs" />
|
||||||
|
<Compile Include="Assets\Scripts\NetworkEquipmentSocket.cs" />
|
||||||
<Compile Include="Assets\Data\Scripts\DataClasses\TowerData.cs" />
|
<Compile Include="Assets\Data\Scripts\DataClasses\TowerData.cs" />
|
||||||
<Compile Include="Assets\Scripts\IAction.cs" />
|
<Compile Include="Assets\Scripts\IAction.cs" />
|
||||||
<Compile Include="Assets\Scripts\NetworkPlayerController.cs" />
|
<Compile Include="Assets\Scripts\NetworkPlayerController.cs" />
|
||||||
<Compile Include="Assets\Scripts\PlayerInteraction.cs" />
|
<Compile Include="Assets\Scripts\PlayerInteraction.cs" />
|
||||||
<Compile Include="Assets\Scripts\FogOfWarVisibility.cs" />
|
<Compile Include="Assets\Scripts\FogOfWarVisibility.cs" />
|
||||||
|
<Compile Include="Assets\Scripts\NetworkDebug.cs" />
|
||||||
<Compile Include="Assets\Data\Scripts\DataClasses\DefaultSettingsData.cs" />
|
<Compile Include="Assets\Data\Scripts\DataClasses\DefaultSettingsData.cs" />
|
||||||
<Compile Include="Assets\Scripts\EnemyAIState.cs" />
|
<Compile Include="Assets\Scripts\EnemyAIState.cs" />
|
||||||
|
<Compile Include="Assets\Scripts\ShortcutNetworkStarter.cs" />
|
||||||
<Compile Include="Assets\Scripts\EquipmentData.cs" />
|
<Compile Include="Assets\Scripts\EquipmentData.cs" />
|
||||||
<Compile Include="Assets\Scripts\NetworkConnectionHandler.cs" />
|
<Compile Include="Assets\Scripts\NetworkConnectionHandler.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -132,6 +137,7 @@
|
|||||||
<None Include="Assets\FlatKit\Shaders\StylizedSurface\LibraryUrp\Lighting_DR.hlsl" />
|
<None Include="Assets\FlatKit\Shaders\StylizedSurface\LibraryUrp\Lighting_DR.hlsl" />
|
||||||
<None Include="Assets\External\Kaykit Adventurers Character\URP\URP Conversion - Read me.txt" />
|
<None Include="Assets\External\Kaykit Adventurers Character\URP\URP Conversion - Read me.txt" />
|
||||||
<None Include="Assets\FlatKit\Demos\[Demo] Desert\Shaders\DesertCloud.hlsl" />
|
<None Include="Assets\FlatKit\Demos\[Demo] Desert\Shaders\DesertCloud.hlsl" />
|
||||||
|
<None Include="Assets\Scripts\ForceRecompile.txt" />
|
||||||
<None Include="Assets\FlatKit\Shaders\StylizedSurface\StylizedSurfaceOutline.shader" />
|
<None Include="Assets\FlatKit\Shaders\StylizedSurface\StylizedSurfaceOutline.shader" />
|
||||||
<None Include="Assets\TextMesh Pro\Shaders\TMP_SDF-Surface.shader" />
|
<None Include="Assets\TextMesh Pro\Shaders\TMP_SDF-Surface.shader" />
|
||||||
<None Include="Assets\TextMesh Pro\Sprites\EmojiOne Attribution.txt" />
|
<None Include="Assets\TextMesh Pro\Sprites\EmojiOne Attribution.txt" />
|
||||||
|
|||||||
@@ -104,3 +104,13 @@ MonoBehaviour:
|
|||||||
SourcePrefabToOverride: {fileID: 0}
|
SourcePrefabToOverride: {fileID: 0}
|
||||||
SourceHashToOverride: 0
|
SourceHashToOverride: 0
|
||||||
OverridingTargetPrefab: {fileID: 0}
|
OverridingTargetPrefab: {fileID: 0}
|
||||||
|
- Override: 0
|
||||||
|
Prefab: {fileID: 7603736553692962085, guid: f03e71a1b147c77498145a41db9d5c6e, type: 3}
|
||||||
|
SourcePrefabToOverride: {fileID: 0}
|
||||||
|
SourceHashToOverride: 0
|
||||||
|
OverridingTargetPrefab: {fileID: 0}
|
||||||
|
- Override: 0
|
||||||
|
Prefab: {fileID: 8294708185945415980, guid: eda89876457aa6143b1bef3330e8f7fb, type: 3}
|
||||||
|
SourcePrefabToOverride: {fileID: 0}
|
||||||
|
SourceHashToOverride: 0
|
||||||
|
OverridingTargetPrefab: {fileID: 0}
|
||||||
|
|||||||
@@ -280,6 +280,24 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
|
|||||||
""processors"": """",
|
""processors"": """",
|
||||||
""interactions"": """",
|
""interactions"": """",
|
||||||
""initialStateCheck"": false
|
""initialStateCheck"": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
""name"": ""StartAsClient"",
|
||||||
|
""type"": ""Button"",
|
||||||
|
""id"": ""3cdb7b19-b155-4d50-bd36-d132b9290400"",
|
||||||
|
""expectedControlType"": """",
|
||||||
|
""processors"": """",
|
||||||
|
""interactions"": """",
|
||||||
|
""initialStateCheck"": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
""name"": ""DebugPanel"",
|
||||||
|
""type"": ""Button"",
|
||||||
|
""id"": ""2d93558e-a811-4563-9591-6ef734155d8d"",
|
||||||
|
""expectedControlType"": """",
|
||||||
|
""processors"": """",
|
||||||
|
""interactions"": """",
|
||||||
|
""initialStateCheck"": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
""bindings"": [
|
""bindings"": [
|
||||||
@@ -799,6 +817,28 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
|
|||||||
""action"": ""Cancel"",
|
""action"": ""Cancel"",
|
||||||
""isComposite"": false,
|
""isComposite"": false,
|
||||||
""isPartOfComposite"": false
|
""isPartOfComposite"": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
""name"": """",
|
||||||
|
""id"": ""492d997c-1c69-463e-a63f-dd327d7881b8"",
|
||||||
|
""path"": ""<Keyboard>/c"",
|
||||||
|
""interactions"": """",
|
||||||
|
""processors"": """",
|
||||||
|
""groups"": "";Keyboard&Mouse"",
|
||||||
|
""action"": ""StartAsClient"",
|
||||||
|
""isComposite"": false,
|
||||||
|
""isPartOfComposite"": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
""name"": """",
|
||||||
|
""id"": ""c286789c-b46b-4160-8724-23412c73cb67"",
|
||||||
|
""path"": ""<Keyboard>/backquote"",
|
||||||
|
""interactions"": """",
|
||||||
|
""processors"": """",
|
||||||
|
""groups"": "";Keyboard&Mouse"",
|
||||||
|
""action"": ""DebugPanel"",
|
||||||
|
""isComposite"": false,
|
||||||
|
""isPartOfComposite"": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -1405,6 +1445,8 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
|
|||||||
m_Player_QuickSlot7 = m_Player.FindAction("QuickSlot7", throwIfNotFound: true);
|
m_Player_QuickSlot7 = m_Player.FindAction("QuickSlot7", throwIfNotFound: true);
|
||||||
m_Player_QuickSlot8 = m_Player.FindAction("QuickSlot8", throwIfNotFound: true);
|
m_Player_QuickSlot8 = m_Player.FindAction("QuickSlot8", throwIfNotFound: true);
|
||||||
m_Player_Cancel = m_Player.FindAction("Cancel", throwIfNotFound: true);
|
m_Player_Cancel = m_Player.FindAction("Cancel", throwIfNotFound: true);
|
||||||
|
m_Player_StartAsClient = m_Player.FindAction("StartAsClient", throwIfNotFound: true);
|
||||||
|
m_Player_DebugPanel = m_Player.FindAction("DebugPanel", throwIfNotFound: true);
|
||||||
// UI
|
// UI
|
||||||
m_UI = asset.FindActionMap("UI", throwIfNotFound: true);
|
m_UI = asset.FindActionMap("UI", throwIfNotFound: true);
|
||||||
m_UI_Navigate = m_UI.FindAction("Navigate", throwIfNotFound: true);
|
m_UI_Navigate = m_UI.FindAction("Navigate", throwIfNotFound: true);
|
||||||
@@ -1519,6 +1561,8 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
|
|||||||
private readonly InputAction m_Player_QuickSlot7;
|
private readonly InputAction m_Player_QuickSlot7;
|
||||||
private readonly InputAction m_Player_QuickSlot8;
|
private readonly InputAction m_Player_QuickSlot8;
|
||||||
private readonly InputAction m_Player_Cancel;
|
private readonly InputAction m_Player_Cancel;
|
||||||
|
private readonly InputAction m_Player_StartAsClient;
|
||||||
|
private readonly InputAction m_Player_DebugPanel;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides access to input actions defined in input action map "Player".
|
/// Provides access to input actions defined in input action map "Player".
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1615,6 +1659,14 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public InputAction @Cancel => m_Wrapper.m_Player_Cancel;
|
public InputAction @Cancel => m_Wrapper.m_Player_Cancel;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// Provides access to the underlying input action "Player/StartAsClient".
|
||||||
|
/// </summary>
|
||||||
|
public InputAction @StartAsClient => m_Wrapper.m_Player_StartAsClient;
|
||||||
|
/// <summary>
|
||||||
|
/// Provides access to the underlying input action "Player/DebugPanel".
|
||||||
|
/// </summary>
|
||||||
|
public InputAction @DebugPanel => m_Wrapper.m_Player_DebugPanel;
|
||||||
|
/// <summary>
|
||||||
/// Provides access to the underlying input action map instance.
|
/// Provides access to the underlying input action map instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public InputActionMap Get() { return m_Wrapper.m_Player; }
|
public InputActionMap Get() { return m_Wrapper.m_Player; }
|
||||||
@@ -1703,6 +1755,12 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
|
|||||||
@Cancel.started += instance.OnCancel;
|
@Cancel.started += instance.OnCancel;
|
||||||
@Cancel.performed += instance.OnCancel;
|
@Cancel.performed += instance.OnCancel;
|
||||||
@Cancel.canceled += instance.OnCancel;
|
@Cancel.canceled += instance.OnCancel;
|
||||||
|
@StartAsClient.started += instance.OnStartAsClient;
|
||||||
|
@StartAsClient.performed += instance.OnStartAsClient;
|
||||||
|
@StartAsClient.canceled += instance.OnStartAsClient;
|
||||||
|
@DebugPanel.started += instance.OnDebugPanel;
|
||||||
|
@DebugPanel.performed += instance.OnDebugPanel;
|
||||||
|
@DebugPanel.canceled += instance.OnDebugPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1777,6 +1835,12 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
|
|||||||
@Cancel.started -= instance.OnCancel;
|
@Cancel.started -= instance.OnCancel;
|
||||||
@Cancel.performed -= instance.OnCancel;
|
@Cancel.performed -= instance.OnCancel;
|
||||||
@Cancel.canceled -= instance.OnCancel;
|
@Cancel.canceled -= instance.OnCancel;
|
||||||
|
@StartAsClient.started -= instance.OnStartAsClient;
|
||||||
|
@StartAsClient.performed -= instance.OnStartAsClient;
|
||||||
|
@StartAsClient.canceled -= instance.OnStartAsClient;
|
||||||
|
@DebugPanel.started -= instance.OnDebugPanel;
|
||||||
|
@DebugPanel.performed -= instance.OnDebugPanel;
|
||||||
|
@DebugPanel.canceled -= instance.OnDebugPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -2224,6 +2288,20 @@ public partial class @PlayerInputActions: IInputActionCollection2, IDisposable
|
|||||||
/// <seealso cref="UnityEngine.InputSystem.InputAction.performed" />
|
/// <seealso cref="UnityEngine.InputSystem.InputAction.performed" />
|
||||||
/// <seealso cref="UnityEngine.InputSystem.InputAction.canceled" />
|
/// <seealso cref="UnityEngine.InputSystem.InputAction.canceled" />
|
||||||
void OnCancel(InputAction.CallbackContext context);
|
void OnCancel(InputAction.CallbackContext context);
|
||||||
|
/// <summary>
|
||||||
|
/// Method invoked when associated input action "StartAsClient" is either <see cref="UnityEngine.InputSystem.InputAction.started" />, <see cref="UnityEngine.InputSystem.InputAction.performed" /> or <see cref="UnityEngine.InputSystem.InputAction.canceled" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="UnityEngine.InputSystem.InputAction.started" />
|
||||||
|
/// <seealso cref="UnityEngine.InputSystem.InputAction.performed" />
|
||||||
|
/// <seealso cref="UnityEngine.InputSystem.InputAction.canceled" />
|
||||||
|
void OnStartAsClient(InputAction.CallbackContext context);
|
||||||
|
/// <summary>
|
||||||
|
/// Method invoked when associated input action "DebugPanel" is either <see cref="UnityEngine.InputSystem.InputAction.started" />, <see cref="UnityEngine.InputSystem.InputAction.performed" /> or <see cref="UnityEngine.InputSystem.InputAction.canceled" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="UnityEngine.InputSystem.InputAction.started" />
|
||||||
|
/// <seealso cref="UnityEngine.InputSystem.InputAction.performed" />
|
||||||
|
/// <seealso cref="UnityEngine.InputSystem.InputAction.canceled" />
|
||||||
|
void OnDebugPanel(InputAction.CallbackContext context);
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface to implement callback methods for all input action callbacks associated with input actions defined by "UI" which allows adding and removing callbacks.
|
/// Interface to implement callback methods for all input action callbacks associated with input actions defined by "UI" which allows adding and removing callbacks.
|
||||||
|
|||||||
@@ -194,6 +194,24 @@
|
|||||||
"processors": "",
|
"processors": "",
|
||||||
"interactions": "",
|
"interactions": "",
|
||||||
"initialStateCheck": false
|
"initialStateCheck": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "StartAsClient",
|
||||||
|
"type": "Button",
|
||||||
|
"id": "3cdb7b19-b155-4d50-bd36-d132b9290400",
|
||||||
|
"expectedControlType": "",
|
||||||
|
"processors": "",
|
||||||
|
"interactions": "",
|
||||||
|
"initialStateCheck": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DebugPanel",
|
||||||
|
"type": "Button",
|
||||||
|
"id": "2d93558e-a811-4563-9591-6ef734155d8d",
|
||||||
|
"expectedControlType": "",
|
||||||
|
"processors": "",
|
||||||
|
"interactions": "",
|
||||||
|
"initialStateCheck": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"bindings": [
|
"bindings": [
|
||||||
@@ -713,6 +731,28 @@
|
|||||||
"action": "Cancel",
|
"action": "Cancel",
|
||||||
"isComposite": false,
|
"isComposite": false,
|
||||||
"isPartOfComposite": false
|
"isPartOfComposite": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"id": "492d997c-1c69-463e-a63f-dd327d7881b8",
|
||||||
|
"path": "<Keyboard>/c",
|
||||||
|
"interactions": "",
|
||||||
|
"processors": "",
|
||||||
|
"groups": ";Keyboard&Mouse",
|
||||||
|
"action": "StartAsClient",
|
||||||
|
"isComposite": false,
|
||||||
|
"isPartOfComposite": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"id": "c286789c-b46b-4160-8724-23412c73cb67",
|
||||||
|
"path": "<Keyboard>/backquote",
|
||||||
|
"interactions": "",
|
||||||
|
"processors": "",
|
||||||
|
"groups": ";Keyboard&Mouse",
|
||||||
|
"action": "DebugPanel",
|
||||||
|
"isComposite": false,
|
||||||
|
"isPartOfComposite": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
122
Assets/Prefabs/Bat.prefab
Normal file
122
Assets/Prefabs/Bat.prefab
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!1 &7603736553692962085
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 1194557353959628612}
|
||||||
|
- component: {fileID: 6659565122963588399}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Bat
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!4 &1194557353959628612
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 7603736553692962085}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: -1.57958, y: 1.00001, z: -1.56753}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children:
|
||||||
|
- {fileID: 3616642548623739450}
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!114 &6659565122963588399
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 7603736553692962085}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject
|
||||||
|
GlobalObjectIdHash: 4268566319
|
||||||
|
InScenePlacedSourceGlobalObjectIdHash: 0
|
||||||
|
DeferredDespawnTick: 0
|
||||||
|
Ownership: 1
|
||||||
|
AlwaysReplicateAsRoot: 0
|
||||||
|
SynchronizeTransform: 1
|
||||||
|
ActiveSceneSynchronization: 0
|
||||||
|
SceneMigrationSynchronization: 0
|
||||||
|
SpawnWithObservers: 1
|
||||||
|
DontDestroyWithOwner: 0
|
||||||
|
AutoObjectParentSync: 1
|
||||||
|
SyncOwnerTransformWhenParented: 1
|
||||||
|
AllowOwnerToParent: 0
|
||||||
|
--- !u!1001 &3871508264071294417
|
||||||
|
PrefabInstance:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Modification:
|
||||||
|
serializedVersion: 3
|
||||||
|
m_TransformParent: {fileID: 1194557353959628612}
|
||||||
|
m_Modifications:
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 1261145a64d4f3e43bee728a02c1b5e3, type: 3}
|
||||||
|
propertyPath: m_LocalPosition.x
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 1261145a64d4f3e43bee728a02c1b5e3, type: 3}
|
||||||
|
propertyPath: m_LocalPosition.y
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 1261145a64d4f3e43bee728a02c1b5e3, type: 3}
|
||||||
|
propertyPath: m_LocalPosition.z
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 1261145a64d4f3e43bee728a02c1b5e3, type: 3}
|
||||||
|
propertyPath: m_LocalRotation.w
|
||||||
|
value: 1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 1261145a64d4f3e43bee728a02c1b5e3, type: 3}
|
||||||
|
propertyPath: m_LocalRotation.x
|
||||||
|
value: 0.000000021855694
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 1261145a64d4f3e43bee728a02c1b5e3, type: 3}
|
||||||
|
propertyPath: m_LocalRotation.y
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 1261145a64d4f3e43bee728a02c1b5e3, type: 3}
|
||||||
|
propertyPath: m_LocalRotation.z
|
||||||
|
value: -0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 1261145a64d4f3e43bee728a02c1b5e3, type: 3}
|
||||||
|
propertyPath: m_LocalEulerAnglesHint.x
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 1261145a64d4f3e43bee728a02c1b5e3, type: 3}
|
||||||
|
propertyPath: m_LocalEulerAnglesHint.y
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 1261145a64d4f3e43bee728a02c1b5e3, type: 3}
|
||||||
|
propertyPath: m_LocalEulerAnglesHint.z
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 919132149155446097, guid: 1261145a64d4f3e43bee728a02c1b5e3, type: 3}
|
||||||
|
propertyPath: m_Name
|
||||||
|
value: Bat
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
m_RemovedComponents: []
|
||||||
|
m_RemovedGameObjects: []
|
||||||
|
m_AddedGameObjects: []
|
||||||
|
m_AddedComponents: []
|
||||||
|
m_SourcePrefab: {fileID: 100100000, guid: 1261145a64d4f3e43bee728a02c1b5e3, type: 3}
|
||||||
|
--- !u!4 &3616642548623739450 stripped
|
||||||
|
Transform:
|
||||||
|
m_CorrespondingSourceObject: {fileID: -8679921383154817045, guid: 1261145a64d4f3e43bee728a02c1b5e3, type: 3}
|
||||||
|
m_PrefabInstance: {fileID: 3871508264071294417}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
7
Assets/Prefabs/Bat.prefab.meta
Normal file
7
Assets/Prefabs/Bat.prefab.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f03e71a1b147c77498145a41db9d5c6e
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
122
Assets/Prefabs/Pickaxe.prefab
Normal file
122
Assets/Prefabs/Pickaxe.prefab
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!1 &8294708185945415980
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 4786869462106053255}
|
||||||
|
- component: {fileID: 7258747535635499414}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Pickaxe
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!4 &4786869462106053255
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 8294708185945415980}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: -1.57958, y: 1.00001, z: -1.56753}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children:
|
||||||
|
- {fileID: 5490866329018943490}
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!114 &7258747535635499414
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 8294708185945415980}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject
|
||||||
|
GlobalObjectIdHash: 1911485989
|
||||||
|
InScenePlacedSourceGlobalObjectIdHash: 0
|
||||||
|
DeferredDespawnTick: 0
|
||||||
|
Ownership: 1
|
||||||
|
AlwaysReplicateAsRoot: 0
|
||||||
|
SynchronizeTransform: 1
|
||||||
|
ActiveSceneSynchronization: 0
|
||||||
|
SceneMigrationSynchronization: 0
|
||||||
|
SpawnWithObservers: 1
|
||||||
|
DontDestroyWithOwner: 0
|
||||||
|
AutoObjectParentSync: 1
|
||||||
|
SyncOwnerTransformWhenParented: 1
|
||||||
|
AllowOwnerToParent: 0
|
||||||
|
--- !u!1001 &5456604255163953129
|
||||||
|
PrefabInstance:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Modification:
|
||||||
|
serializedVersion: 3
|
||||||
|
m_TransformParent: {fileID: 4786869462106053255}
|
||||||
|
m_Modifications:
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 804d477fc7f114c498aa6f95452be893, type: 3}
|
||||||
|
propertyPath: m_LocalPosition.x
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 804d477fc7f114c498aa6f95452be893, type: 3}
|
||||||
|
propertyPath: m_LocalPosition.y
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 804d477fc7f114c498aa6f95452be893, type: 3}
|
||||||
|
propertyPath: m_LocalPosition.z
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 804d477fc7f114c498aa6f95452be893, type: 3}
|
||||||
|
propertyPath: m_LocalRotation.w
|
||||||
|
value: 1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 804d477fc7f114c498aa6f95452be893, type: 3}
|
||||||
|
propertyPath: m_LocalRotation.x
|
||||||
|
value: 0.00000008146034
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 804d477fc7f114c498aa6f95452be893, type: 3}
|
||||||
|
propertyPath: m_LocalRotation.y
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 804d477fc7f114c498aa6f95452be893, type: 3}
|
||||||
|
propertyPath: m_LocalRotation.z
|
||||||
|
value: -0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 804d477fc7f114c498aa6f95452be893, type: 3}
|
||||||
|
propertyPath: m_LocalEulerAnglesHint.x
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 804d477fc7f114c498aa6f95452be893, type: 3}
|
||||||
|
propertyPath: m_LocalEulerAnglesHint.y
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: -8679921383154817045, guid: 804d477fc7f114c498aa6f95452be893, type: 3}
|
||||||
|
propertyPath: m_LocalEulerAnglesHint.z
|
||||||
|
value: 0
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 919132149155446097, guid: 804d477fc7f114c498aa6f95452be893, type: 3}
|
||||||
|
propertyPath: m_Name
|
||||||
|
value: pickaxe
|
||||||
|
objectReference: {fileID: 0}
|
||||||
|
m_RemovedComponents: []
|
||||||
|
m_RemovedGameObjects: []
|
||||||
|
m_AddedGameObjects: []
|
||||||
|
m_AddedComponents: []
|
||||||
|
m_SourcePrefab: {fileID: 100100000, guid: 804d477fc7f114c498aa6f95452be893, type: 3}
|
||||||
|
--- !u!4 &5490866329018943490 stripped
|
||||||
|
Transform:
|
||||||
|
m_CorrespondingSourceObject: {fileID: -8679921383154817045, guid: 804d477fc7f114c498aa6f95452be893, type: 3}
|
||||||
|
m_PrefabInstance: {fileID: 5456604255163953129}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
7
Assets/Prefabs/Pickaxe.prefab.meta
Normal file
7
Assets/Prefabs/Pickaxe.prefab.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: eda89876457aa6143b1bef3330e8f7fb
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -11,7 +11,6 @@ GameObject:
|
|||||||
- component: {fileID: 5887522270574905679}
|
- component: {fileID: 5887522270574905679}
|
||||||
- component: {fileID: 2636831972010436653}
|
- component: {fileID: 2636831972010436653}
|
||||||
- component: {fileID: 3792365921352178844}
|
- component: {fileID: 3792365921352178844}
|
||||||
- component: {fileID: 1698609800605343773}
|
|
||||||
- component: {fileID: 3007098678582223509}
|
- component: {fileID: 3007098678582223509}
|
||||||
- component: {fileID: 1883169379180791275}
|
- component: {fileID: 1883169379180791275}
|
||||||
- component: {fileID: 8729870597719024730}
|
- component: {fileID: 8729870597719024730}
|
||||||
@@ -20,6 +19,9 @@ GameObject:
|
|||||||
- component: {fileID: 6066313428661204362}
|
- component: {fileID: 6066313428661204362}
|
||||||
- component: {fileID: 2443072964133329520}
|
- component: {fileID: 2443072964133329520}
|
||||||
- component: {fileID: 2148255267416253297}
|
- component: {fileID: 2148255267416253297}
|
||||||
|
- component: {fileID: 1698609800605343773}
|
||||||
|
- component: {fileID: -4348726977448206869}
|
||||||
|
- component: {fileID: 7148704114816793672}
|
||||||
m_Layer: 9
|
m_Layer: 9
|
||||||
m_Name: Player
|
m_Name: Player
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
@@ -55,7 +57,7 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
|
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject
|
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject
|
||||||
GlobalObjectIdHash: 1360081626
|
GlobalObjectIdHash: 4211758632
|
||||||
InScenePlacedSourceGlobalObjectIdHash: 4211758632
|
InScenePlacedSourceGlobalObjectIdHash: 4211758632
|
||||||
DeferredDespawnTick: 0
|
DeferredDespawnTick: 0
|
||||||
Ownership: 1
|
Ownership: 1
|
||||||
@@ -88,28 +90,6 @@ MonoBehaviour:
|
|||||||
showHealthBar: 1
|
showHealthBar: 1
|
||||||
damageEffectPrefab: {fileID: 0}
|
damageEffectPrefab: {fileID: 0}
|
||||||
deathEffectPrefab: {fileID: 0}
|
deathEffectPrefab: {fileID: 0}
|
||||||
--- !u!95 &1698609800605343773
|
|
||||||
Animator:
|
|
||||||
serializedVersion: 7
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 1314983689436087486}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_Avatar: {fileID: 9000000, guid: 2632f2cc035d62d41bca411a318fbe36, type: 3}
|
|
||||||
m_Controller: {fileID: 9100000, guid: bb242bb2298e65941bcb502d7ae219cd, type: 2}
|
|
||||||
m_CullingMode: 0
|
|
||||||
m_UpdateMode: 0
|
|
||||||
m_ApplyRootMotion: 0
|
|
||||||
m_LinearVelocityBlending: 0
|
|
||||||
m_StabilizeFeet: 0
|
|
||||||
m_AnimatePhysics: 0
|
|
||||||
m_WarningMessage:
|
|
||||||
m_HasTransformHierarchy: 1
|
|
||||||
m_AllowConstantClipSamplingOptimization: 1
|
|
||||||
m_KeepAnimatorStateOnDisable: 0
|
|
||||||
m_WriteDefaultValuesOnDisable: 0
|
|
||||||
--- !u!143 &3007098678582223509
|
--- !u!143 &3007098678582223509
|
||||||
CharacterController:
|
CharacterController:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -175,6 +155,7 @@ MonoBehaviour:
|
|||||||
interactableLayer:
|
interactableLayer:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
m_Bits: 128
|
m_Bits: 128
|
||||||
|
workPower: 10
|
||||||
rayOrigin: {fileID: 0}
|
rayOrigin: {fileID: 0}
|
||||||
useForwardDirection: 1
|
useForwardDirection: 1
|
||||||
playAnimations: 1
|
playAnimations: 1
|
||||||
@@ -182,6 +163,7 @@ MonoBehaviour:
|
|||||||
blockDuringAnimation: 1
|
blockDuringAnimation: 1
|
||||||
useEquipment: 1
|
useEquipment: 1
|
||||||
showDebugRay: 1
|
showDebugRay: 1
|
||||||
|
assignedWorker: {fileID: 0}
|
||||||
--- !u!114 &5217638038410020423
|
--- !u!114 &5217638038410020423
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -243,10 +225,14 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: ac908541bf903c745a1794d409a5f048, type: 3}
|
m_Script: {fileID: 11500000, guid: ac908541bf903c745a1794d409a5f048, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier: Assembly-CSharp::Northbound.EquipmentSocket
|
m_EditorClassIdentifier: Assembly-CSharp::Northbound.EquipmentSocket
|
||||||
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
sockets:
|
sockets:
|
||||||
- socketName: handslot.r
|
- socketName: handslot.r
|
||||||
socketTransform: {fileID: 2844947653216056832}
|
socketTransform: {fileID: 2844947653216056832}
|
||||||
currentEquipment: {fileID: 0}
|
currentEquipment: {fileID: 0}
|
||||||
|
equipmentPrefabs:
|
||||||
|
- {fileID: 7603736553692962085, guid: f03e71a1b147c77498145a41db9d5c6e, type: 3}
|
||||||
|
- {fileID: 8294708185945415980, guid: eda89876457aa6143b1bef3330e8f7fb, type: 3}
|
||||||
--- !u!114 &2443072964133329520
|
--- !u!114 &2443072964133329520
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -275,6 +261,106 @@ MonoBehaviour:
|
|||||||
m_EditorClassIdentifier: Assembly-CSharp::Northbound.PlayerVisionProvider
|
m_EditorClassIdentifier: Assembly-CSharp::Northbound.PlayerVisionProvider
|
||||||
ShowTopMostFoldoutHeaderGroup: 1
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
visionRange: 20
|
visionRange: 20
|
||||||
|
--- !u!95 &1698609800605343773
|
||||||
|
Animator:
|
||||||
|
serializedVersion: 7
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1314983689436087486}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_Avatar: {fileID: 9000000, guid: 2632f2cc035d62d41bca411a318fbe36, type: 3}
|
||||||
|
m_Controller: {fileID: 9100000, guid: bb242bb2298e65941bcb502d7ae219cd, type: 2}
|
||||||
|
m_CullingMode: 0
|
||||||
|
m_UpdateMode: 0
|
||||||
|
m_ApplyRootMotion: 0
|
||||||
|
m_LinearVelocityBlending: 0
|
||||||
|
m_StabilizeFeet: 0
|
||||||
|
m_AnimatePhysics: 0
|
||||||
|
m_WarningMessage:
|
||||||
|
m_HasTransformHierarchy: 1
|
||||||
|
m_AllowConstantClipSamplingOptimization: 1
|
||||||
|
m_KeepAnimatorStateOnDisable: 0
|
||||||
|
m_WriteDefaultValuesOnDisable: 0
|
||||||
|
--- !u!114 &-4348726977448206869
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1314983689436087486}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: e8d0727d5ae3244e3b569694d3912374, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.Components.NetworkAnimator
|
||||||
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
|
NetworkAnimatorExpanded: 0
|
||||||
|
AuthorityMode: 0
|
||||||
|
m_Animator: {fileID: 1698609800605343773}
|
||||||
|
TransitionStateInfoList: []
|
||||||
|
AnimatorParameterEntries:
|
||||||
|
ParameterEntries:
|
||||||
|
- name: MoveSpeed
|
||||||
|
NameHash: 526065662
|
||||||
|
Synchronize: 1
|
||||||
|
ParameterType: 1
|
||||||
|
- name: Mining
|
||||||
|
NameHash: 577859424
|
||||||
|
Synchronize: 1
|
||||||
|
ParameterType: 9
|
||||||
|
- name: Attack
|
||||||
|
NameHash: 1080829965
|
||||||
|
Synchronize: 1
|
||||||
|
ParameterType: 9
|
||||||
|
AnimatorParametersExpanded: 0
|
||||||
|
--- !u!114 &7148704114816793672
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1314983689436087486}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: e96cb6065543e43c4a752faaa1468eb1, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.Components.NetworkTransform
|
||||||
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
|
NetworkTransformExpanded: 0
|
||||||
|
AutoOwnerAuthorityTickOffset: 1
|
||||||
|
PositionInterpolationType: 0
|
||||||
|
RotationInterpolationType: 0
|
||||||
|
ScaleInterpolationType: 0
|
||||||
|
PositionLerpSmoothing: 1
|
||||||
|
PositionMaxInterpolationTime: 0.1
|
||||||
|
RotationLerpSmoothing: 1
|
||||||
|
RotationMaxInterpolationTime: 0.1
|
||||||
|
ScaleLerpSmoothing: 1
|
||||||
|
ScaleMaxInterpolationTime: 0.1
|
||||||
|
AuthorityMode: 0
|
||||||
|
TickSyncChildren: 0
|
||||||
|
UseUnreliableDeltas: 0
|
||||||
|
SyncPositionX: 1
|
||||||
|
SyncPositionY: 1
|
||||||
|
SyncPositionZ: 1
|
||||||
|
SyncRotAngleX: 1
|
||||||
|
SyncRotAngleY: 1
|
||||||
|
SyncRotAngleZ: 1
|
||||||
|
SyncScaleX: 1
|
||||||
|
SyncScaleY: 1
|
||||||
|
SyncScaleZ: 1
|
||||||
|
PositionThreshold: 0.001
|
||||||
|
RotAngleThreshold: 0.01
|
||||||
|
ScaleThreshold: 0.01
|
||||||
|
UseQuaternionSynchronization: 0
|
||||||
|
UseQuaternionCompression: 0
|
||||||
|
UseHalfFloatPrecision: 0
|
||||||
|
InLocalSpace: 0
|
||||||
|
SwitchTransformSpaceWhenParented: 0
|
||||||
|
Interpolate: 1
|
||||||
|
SlerpPosition: 0
|
||||||
--- !u!1001 &1445453803682481668
|
--- !u!1001 &1445453803682481668
|
||||||
PrefabInstance:
|
PrefabInstance:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|||||||
8
Assets/Resources.meta
Normal file
8
Assets/Resources.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 963daf2309f087a4192afcb548328e22
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1799,7 +1799,7 @@ MonoBehaviour:
|
|||||||
EnableNetworkLogs: 1
|
EnableNetworkLogs: 1
|
||||||
NetworkTopology: 1
|
NetworkTopology: 1
|
||||||
UseCMBService: 0
|
UseCMBService: 0
|
||||||
AutoSpawnPlayerPrefabClientSide: 1
|
AutoSpawnPlayerPrefabClientSide: 0
|
||||||
NetworkMessageMetrics: 1
|
NetworkMessageMetrics: 1
|
||||||
NetworkProfilingMetrics: 1
|
NetworkProfilingMetrics: 1
|
||||||
OldPrefabList: []
|
OldPrefabList: []
|
||||||
|
|||||||
@@ -259,19 +259,14 @@ namespace Northbound
|
|||||||
if (!CanInteract(playerId))
|
if (!CanInteract(playerId))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// 플레이어 인벤토리 가져오기
|
var resourceManager = ServerResourceManager.Instance;
|
||||||
var playerObject = NetworkManager.Singleton.ConnectedClients[playerId].PlayerObject;
|
if (resourceManager == null)
|
||||||
if (playerObject == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var playerInventory = playerObject.GetComponent<PlayerResourceInventory>();
|
|
||||||
if (playerInventory == null)
|
|
||||||
{
|
{
|
||||||
Debug.LogWarning($"플레이어 {playerId}에게 PlayerResourceInventory 컴포넌트가 없습니다.");
|
Debug.LogWarning("ServerResourceManager 인스턴스를 찾을 수 없습니다.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int playerResourceAmount = playerInventory.CurrentResourceAmount;
|
int playerResourceAmount = resourceManager.GetPlayerResourceAmount(playerId);
|
||||||
if (playerResourceAmount <= 0)
|
if (playerResourceAmount <= 0)
|
||||||
{
|
{
|
||||||
Debug.Log($"플레이어 {playerId}가 건낼 자원이 없습니다.");
|
Debug.Log($"플레이어 {playerId}가 건낼 자원이 없습니다.");
|
||||||
@@ -282,16 +277,13 @@ namespace Northbound
|
|||||||
|
|
||||||
if (depositAll)
|
if (depositAll)
|
||||||
{
|
{
|
||||||
// 전부 건네기
|
|
||||||
depositAmount = playerResourceAmount;
|
depositAmount = playerResourceAmount;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 일부만 건네기
|
|
||||||
depositAmount = Mathf.Min(depositAmountPerInteraction, playerResourceAmount);
|
depositAmount = Mathf.Min(depositAmountPerInteraction, playerResourceAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 무제한 저장소가 아니면 용량 제한 확인
|
|
||||||
if (!unlimitedStorage)
|
if (!unlimitedStorage)
|
||||||
{
|
{
|
||||||
int availableSpace = maxStorageCapacity - _totalResources.Value;
|
int availableSpace = maxStorageCapacity - _totalResources.Value;
|
||||||
@@ -304,10 +296,8 @@ namespace Northbound
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 플레이어로부터 자원 차감
|
resourceManager.RemoveResource(playerId, depositAmount);
|
||||||
playerInventory.RemoveResourceServerRpc(depositAmount);
|
UpdatePlayerResourcesClientRpc(playerId);
|
||||||
|
|
||||||
// 코어에 자원 추가
|
|
||||||
_totalResources.Value += depositAmount;
|
_totalResources.Value += depositAmount;
|
||||||
|
|
||||||
Debug.Log($"<color=green>[Core] 플레이어 {playerId}가 {depositAmount} 자원을 건넸습니다. 코어 총 자원: {_totalResources.Value}" +
|
Debug.Log($"<color=green>[Core] 플레이어 {playerId}가 {depositAmount} 자원을 건넸습니다. 코어 총 자원: {_totalResources.Value}" +
|
||||||
@@ -316,6 +306,20 @@ namespace Northbound
|
|||||||
ShowDepositEffectClientRpc();
|
ShowDepositEffectClientRpc();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Rpc(SendTo.ClientsAndHost)]
|
||||||
|
private void UpdatePlayerResourcesClientRpc(ulong playerId)
|
||||||
|
{
|
||||||
|
var playerObject = NetworkManager.Singleton.ConnectedClients[playerId].PlayerObject;
|
||||||
|
if (playerObject != null)
|
||||||
|
{
|
||||||
|
var playerInventory = playerObject.GetComponent<PlayerResourceInventory>();
|
||||||
|
if (playerInventory != null)
|
||||||
|
{
|
||||||
|
playerInventory.RequestResourceUpdateServerRpc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Rpc(SendTo.ClientsAndHost)]
|
[Rpc(SendTo.ClientsAndHost)]
|
||||||
private void ShowDepositEffectClientRpc()
|
private void ShowDepositEffectClientRpc()
|
||||||
{
|
{
|
||||||
|
|||||||
160
Assets/Scripts/DebugLogUI.cs
Normal file
160
Assets/Scripts/DebugLogUI.cs
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Northbound
|
||||||
|
{
|
||||||
|
public class DebugLogUI : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("Settings")]
|
||||||
|
public int maxLogMessages = 50;
|
||||||
|
public float logDisplayDuration = 5f;
|
||||||
|
public KeyCode toggleKey = KeyCode.BackQuote;
|
||||||
|
|
||||||
|
[Header("UI References")]
|
||||||
|
public GameObject logPanel;
|
||||||
|
public Text logText;
|
||||||
|
public GameObject scrollContent;
|
||||||
|
public ScrollRect scrollRect;
|
||||||
|
|
||||||
|
private Queue<string> _logMessages = new Queue<string>();
|
||||||
|
private bool _isVisible = true;
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
// 이미 씬에 있는지 확인
|
||||||
|
if (logPanel != null)
|
||||||
|
{
|
||||||
|
logPanel = CreateLogPanel();
|
||||||
|
_isVisible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (Input.GetKeyDown(toggleKey))
|
||||||
|
{
|
||||||
|
ToggleLogPanel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
if (logPanel != null)
|
||||||
|
{
|
||||||
|
Application.logMessageReceived += HandleLog;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
if (logPanel != null)
|
||||||
|
{
|
||||||
|
Application.logMessageReceived -= HandleLog;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private GameObject CreateLogPanel()
|
||||||
|
{
|
||||||
|
GameObject panel = new GameObject("DebugLogPanel");
|
||||||
|
panel.AddComponent<Canvas>();
|
||||||
|
Canvas canvas = panel.GetComponent<Canvas>();
|
||||||
|
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||||
|
canvas.sortingOrder = 9999;
|
||||||
|
|
||||||
|
RectTransform canvasRect = canvas.GetComponent<RectTransform>();
|
||||||
|
canvasRect.anchorMin = Vector2.zero;
|
||||||
|
canvasRect.anchorMax = new Vector2(Screen.width, Screen.height);
|
||||||
|
canvasRect.sizeDelta = Vector2.zero;
|
||||||
|
canvasRect.localScale = Vector3.one;
|
||||||
|
|
||||||
|
// 배경
|
||||||
|
GameObject background = new GameObject("Background");
|
||||||
|
background.transform.SetParent(canvas.transform, false);
|
||||||
|
Image bgImage = background.AddComponent<Image>();
|
||||||
|
bgImage.color = new Color(0, 0, 0, 0.8f);
|
||||||
|
|
||||||
|
RectTransform bgRect = background.AddComponent<RectTransform>();
|
||||||
|
bgRect.anchorMin = Vector2.zero;
|
||||||
|
bgRect.anchorMax = Vector2.one;
|
||||||
|
bgRect.sizeDelta = new Vector2(600, 300);
|
||||||
|
bgRect.localPosition = new Vector2(-300, -150);
|
||||||
|
|
||||||
|
// 로그 내용
|
||||||
|
GameObject content = new GameObject("Content");
|
||||||
|
content.transform.SetParent(background.transform, false);
|
||||||
|
|
||||||
|
RectTransform contentRect = content.AddComponent<RectTransform>();
|
||||||
|
contentRect.anchorMin = Vector2.zero;
|
||||||
|
contentRect.anchorMax = Vector2.one;
|
||||||
|
contentRect.sizeDelta = new Vector2(580, 280);
|
||||||
|
contentRect.localPosition = new Vector2(-290, -140);
|
||||||
|
|
||||||
|
// 텍스트
|
||||||
|
Text logTextComponent = content.AddComponent<Text>();
|
||||||
|
logTextComponent.fontSize = 14;
|
||||||
|
logTextComponent.alignment = TextAnchor.UpperLeft;
|
||||||
|
logTextComponent.color = Color.white;
|
||||||
|
|
||||||
|
// 스크롤뷰
|
||||||
|
GameObject scrollView = new GameObject("ScrollView");
|
||||||
|
scrollView.transform.SetParent(content.transform, false);
|
||||||
|
scrollRect = scrollView.AddComponent<ScrollRect>();
|
||||||
|
scrollRect.content = contentRect;
|
||||||
|
scrollRect.viewport = new RectTransform();
|
||||||
|
scrollRect.horizontal = false;
|
||||||
|
scrollRect.vertical = true;
|
||||||
|
scrollRect.scrollSensitivity = 1;
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleLog(string logString, string stackTrace, LogType type)
|
||||||
|
{
|
||||||
|
string timestamp = System.DateTime.Now.ToString("HH:mm:ss");
|
||||||
|
string logMessage = $"[{timestamp}] {logString}";
|
||||||
|
|
||||||
|
_logMessages.Enqueue(logMessage);
|
||||||
|
if (_logMessages.Count > maxLogMessages)
|
||||||
|
{
|
||||||
|
_logMessages.Dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateLogText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateLogText()
|
||||||
|
{
|
||||||
|
if (logText == null) return;
|
||||||
|
|
||||||
|
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
||||||
|
foreach (string msg in _logMessages)
|
||||||
|
{
|
||||||
|
sb.AppendLine(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
logText.text = sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ToggleLogPanel()
|
||||||
|
{
|
||||||
|
if (logPanel != null)
|
||||||
|
{
|
||||||
|
_isVisible = !_isVisible;
|
||||||
|
logPanel.SetActive(_isVisible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Log(string message)
|
||||||
|
{
|
||||||
|
if (logPanel != null)
|
||||||
|
{
|
||||||
|
logPanel.SetActive(true);
|
||||||
|
_isVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log($"[DebugLogUI] {message}");
|
||||||
|
Debug.Log($"[DebugLogUI] 로그 패널 활성됨: {message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/DebugLogUI.cs.meta
Normal file
2
Assets/Scripts/DebugLogUI.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: dd4b979683b78954bb4d9b224ec4c61d
|
||||||
@@ -1,29 +1,34 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Unity.Netcode;
|
||||||
|
|
||||||
namespace Northbound
|
namespace Northbound
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 플레이어의 장비 소켓 관리 (손, 등, 허리 등)
|
/// 플레이어의 장비 소켓 관리 (손, 등, 허리 등)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EquipmentSocket : MonoBehaviour
|
public class EquipmentSocket : NetworkBehaviour
|
||||||
{
|
{
|
||||||
[System.Serializable]
|
[System.Serializable]
|
||||||
public class Socket
|
public class Socket
|
||||||
{
|
{
|
||||||
public string socketName; // "RightHand", "LeftHand", "Back" 등
|
public string socketName;
|
||||||
public Transform socketTransform; // 실제 본 Transform
|
public Transform socketTransform;
|
||||||
[HideInInspector] public GameObject currentEquipment; // 현재 장착된 장비
|
[HideInInspector] public GameObject currentEquipment;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Header("Available Sockets")]
|
[Header("Available Sockets")]
|
||||||
public List<Socket> sockets = new List<Socket>();
|
public List<Socket> sockets = new List<Socket>();
|
||||||
|
|
||||||
|
[Header("Equipment Prefabs")]
|
||||||
|
public GameObject[] equipmentPrefabs;
|
||||||
|
|
||||||
private Dictionary<string, Socket> _socketDict = new Dictionary<string, Socket>();
|
private Dictionary<string, Socket> _socketDict = new Dictionary<string, Socket>();
|
||||||
|
private Dictionary<string, GameObject> _prefabDict = new Dictionary<string, GameObject>();
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
// 빠른 검색을 위한 딕셔너리 생성
|
_socketDict.Clear();
|
||||||
foreach (var socket in sockets)
|
foreach (var socket in sockets)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(socket.socketName))
|
if (!string.IsNullOrEmpty(socket.socketName))
|
||||||
@@ -31,82 +36,124 @@ namespace Northbound
|
|||||||
_socketDict[socket.socketName] = socket;
|
_socketDict[socket.socketName] = socket;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
_prefabDict.Clear();
|
||||||
/// 소켓에 장비 부착
|
if (equipmentPrefabs != null)
|
||||||
/// </summary>
|
|
||||||
public GameObject AttachToSocket(string socketName, GameObject equipmentPrefab)
|
|
||||||
{
|
{
|
||||||
if (!_socketDict.TryGetValue(socketName, out Socket socket))
|
foreach (var prefab in equipmentPrefabs)
|
||||||
{
|
{
|
||||||
Debug.LogWarning($"소켓을 찾을 수 없습니다: {socketName}");
|
if (prefab != null)
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (socket.socketTransform == null)
|
|
||||||
{
|
{
|
||||||
Debug.LogWarning($"소켓 Transform이 없습니다: {socketName}");
|
_prefabDict[prefab.name] = prefab;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 기존 장비 제거
|
|
||||||
DetachFromSocket(socketName);
|
|
||||||
|
|
||||||
// 새 장비 생성
|
|
||||||
if (equipmentPrefab != null)
|
|
||||||
{
|
|
||||||
GameObject equipment = Instantiate(equipmentPrefab, socket.socketTransform);
|
|
||||||
equipment.transform.localPosition = Vector3.zero;
|
|
||||||
equipment.transform.localRotation = Quaternion.identity;
|
|
||||||
socket.currentEquipment = equipment;
|
|
||||||
|
|
||||||
return equipment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 소켓에서 장비 제거
|
|
||||||
/// </summary>
|
|
||||||
public void DetachFromSocket(string socketName)
|
|
||||||
{
|
|
||||||
if (!_socketDict.TryGetValue(socketName, out Socket socket))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (socket.currentEquipment != null)
|
|
||||||
{
|
|
||||||
Destroy(socket.currentEquipment);
|
|
||||||
socket.currentEquipment = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public override void OnNetworkDespawn()
|
||||||
/// 모든 소켓에서 장비 제거
|
|
||||||
/// </summary>
|
|
||||||
public void DetachAll()
|
|
||||||
{
|
{
|
||||||
foreach (var socket in sockets)
|
foreach (var socket in sockets)
|
||||||
{
|
{
|
||||||
if (socket.currentEquipment != null)
|
if (socket.currentEquipment != null)
|
||||||
{
|
{
|
||||||
Destroy(socket.currentEquipment);
|
Object.Destroy(socket.currentEquipment);
|
||||||
socket.currentEquipment = null;
|
socket.currentEquipment = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
base.OnNetworkDespawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameObject AttachToSocket(string socketName, GameObject equipmentPrefab)
|
||||||
|
{
|
||||||
|
if (equipmentPrefab != null)
|
||||||
|
{
|
||||||
|
AttachToSocketServerRpc(socketName, equipmentPrefab.name);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Rpc(SendTo.Server)]
|
||||||
|
private void AttachToSocketServerRpc(string socketName, string prefabName)
|
||||||
|
{
|
||||||
|
AttachToSocketClientRpc(socketName, prefabName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Rpc(SendTo.ClientsAndHost)]
|
||||||
|
private void AttachToSocketClientRpc(string socketName, string prefabName)
|
||||||
|
{
|
||||||
|
if (!_socketDict.ContainsKey(socketName))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var socket = sockets.Find(s => s.socketName == socketName);
|
||||||
|
if (socket == null || socket.socketTransform == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DetachFromSocketInternal(socketName);
|
||||||
|
|
||||||
|
GameObject prefab = FindPrefab(prefabName);
|
||||||
|
if (prefab != null)
|
||||||
|
{
|
||||||
|
GameObject equipment = Object.Instantiate(prefab, socket.socketTransform);
|
||||||
|
equipment.transform.localPosition = Vector3.zero;
|
||||||
|
equipment.transform.localRotation = Quaternion.identity;
|
||||||
|
socket.currentEquipment = equipment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DetachFromSocket(string socketName)
|
||||||
|
{
|
||||||
|
DetachFromSocketServerRpc(socketName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Rpc(SendTo.Server)]
|
||||||
|
private void DetachFromSocketServerRpc(string socketName)
|
||||||
|
{
|
||||||
|
DetachFromSocketClientRpc(socketName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Rpc(SendTo.ClientsAndHost)]
|
||||||
|
private void DetachFromSocketClientRpc(string socketName)
|
||||||
|
{
|
||||||
|
DetachFromSocketInternal(socketName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DetachFromSocketInternal(string socketName)
|
||||||
|
{
|
||||||
|
var socket = sockets.Find(s => s.socketName == socketName);
|
||||||
|
if (socket == null) return;
|
||||||
|
|
||||||
|
if (socket.currentEquipment != null)
|
||||||
|
{
|
||||||
|
Object.Destroy(socket.currentEquipment);
|
||||||
|
socket.currentEquipment = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 특정 소켓에 장비가 있는지 확인
|
|
||||||
/// </summary>
|
|
||||||
public bool HasEquipment(string socketName)
|
public bool HasEquipment(string socketName)
|
||||||
{
|
{
|
||||||
if (_socketDict.TryGetValue(socketName, out Socket socket))
|
var socket = sockets.Find(s => s.socketName == socketName);
|
||||||
|
return socket != null && socket.currentEquipment != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameObject GetEquipment(string socketName)
|
||||||
{
|
{
|
||||||
return socket.currentEquipment != null;
|
var socket = sockets.Find(s => s.socketName == socketName);
|
||||||
|
return socket != null ? socket.currentEquipment : null;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
private GameObject FindPrefab(string name)
|
||||||
|
{
|
||||||
|
if (_prefabDict.TryGetValue(name, out var prefab))
|
||||||
|
{
|
||||||
|
return prefab;
|
||||||
|
}
|
||||||
|
|
||||||
|
prefab = Resources.Load<GameObject>($"Prefabs/{name}");
|
||||||
|
if (prefab != null)
|
||||||
|
{
|
||||||
|
return prefab;
|
||||||
|
}
|
||||||
|
return Resources.Load<GameObject>(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0
Assets/Scripts/ForceRecompile.txt
Normal file
0
Assets/Scripts/ForceRecompile.txt
Normal file
7
Assets/Scripts/ForceRecompile.txt.meta
Normal file
7
Assets/Scripts/ForceRecompile.txt.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 871f9f40b5b3cb54ab7c0a76b592bc47
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -32,6 +32,7 @@ namespace Northbound
|
|||||||
{
|
{
|
||||||
NetworkManager.Singleton.ConnectionApprovalCallback = ApprovalCheck;
|
NetworkManager.Singleton.ConnectionApprovalCallback = ApprovalCheck;
|
||||||
NetworkManager.Singleton.OnServerStarted += OnServerStarted;
|
NetworkManager.Singleton.OnServerStarted += OnServerStarted;
|
||||||
|
NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnected;
|
||||||
Debug.Log("<color=cyan>[Connection] ConnectionApprovalCallback 등록됨</color>");
|
Debug.Log("<color=cyan>[Connection] ConnectionApprovalCallback 등록됨</color>");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -50,7 +51,7 @@ namespace Northbound
|
|||||||
spawnPoints.Clear();
|
spawnPoints.Clear();
|
||||||
foreach (var point in sortedPoints)
|
foreach (var point in sortedPoints)
|
||||||
{
|
{
|
||||||
if (point.isAvailable)
|
if (point != null && point.transform != null && point.isAvailable)
|
||||||
{
|
{
|
||||||
spawnPoints.Add(point.transform);
|
spawnPoints.Add(point.transform);
|
||||||
}
|
}
|
||||||
@@ -62,30 +63,92 @@ namespace Northbound
|
|||||||
private void OnServerStarted()
|
private void OnServerStarted()
|
||||||
{
|
{
|
||||||
Debug.Log("<color=green>[Connection] 서버 시작됨</color>");
|
Debug.Log("<color=green>[Connection] 서버 시작됨</color>");
|
||||||
|
|
||||||
|
if (ServerResourceManager.Instance == null)
|
||||||
|
{
|
||||||
|
GameObject resourceManagerObj = new GameObject("ServerResourceManager");
|
||||||
|
ServerResourceManager manager = resourceManagerObj.AddComponent<ServerResourceManager>();
|
||||||
|
|
||||||
|
NetworkObject networkObject = resourceManagerObj.GetComponent<NetworkObject>();
|
||||||
|
if (networkObject == null)
|
||||||
|
{
|
||||||
|
networkObject = resourceManagerObj.AddComponent<NetworkObject>();
|
||||||
|
}
|
||||||
|
|
||||||
|
networkObject.Spawn();
|
||||||
|
Debug.Log("[Connection] ServerResourceManager spawned.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NetworkManager.Singleton.IsHost)
|
||||||
|
{
|
||||||
|
SpawnPlayer(NetworkManager.Singleton.LocalClientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClientConnected(ulong clientId)
|
||||||
|
{
|
||||||
|
if (!NetworkManager.Singleton.IsServer) return;
|
||||||
|
if (clientId == NetworkManager.Singleton.LocalClientId) return;
|
||||||
|
|
||||||
|
Debug.Log($"<color=cyan>[Connection] 클라이언트 {clientId} 연결됨</color>");
|
||||||
|
|
||||||
|
if (!NetworkManager.Singleton.ConnectedClients.TryGetValue(clientId, out var client) || client.PlayerObject != null)
|
||||||
|
{
|
||||||
|
Debug.Log($"<color=yellow>[Connection] 클라이언트 {clientId}의 플레이어가 이미 존재합니다.</color>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpawnPlayer(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SpawnPlayer(ulong clientId)
|
||||||
|
{
|
||||||
|
Vector3 spawnPosition = GetSpawnPosition(clientId);
|
||||||
|
Quaternion spawnRotation = GetSpawnRotation(clientId);
|
||||||
|
|
||||||
|
GameObject playerPrefab = NetworkManager.Singleton.NetworkConfig.PlayerPrefab;
|
||||||
|
if (playerPrefab == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("[Connection] PlayerPrefab이 null입니다!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameObject playerObject = Instantiate(playerPrefab, spawnPosition, spawnRotation);
|
||||||
|
NetworkObject networkObject = playerObject.GetComponent<NetworkObject>();
|
||||||
|
if (networkObject == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("[Connection] PlayerPrefab에 NetworkObject가 없습니다!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
networkObject.SpawnAsPlayerObject(clientId);
|
||||||
|
|
||||||
|
Debug.Log($"<color=green>[Connection] 플레이어 {clientId} 스폰됨: {spawnPosition}</color>");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApprovalCheck(
|
private void ApprovalCheck(
|
||||||
NetworkManager.ConnectionApprovalRequest request,
|
NetworkManager.ConnectionApprovalRequest request,
|
||||||
NetworkManager.ConnectionApprovalResponse response)
|
NetworkManager.ConnectionApprovalResponse response)
|
||||||
{
|
{
|
||||||
// 🔍 디버깅: 스폰 포인트 상태 확인
|
spawnPoints.RemoveAll(p => p == null);
|
||||||
|
|
||||||
if (spawnPoints.Count == 0)
|
if (spawnPoints.Count == 0)
|
||||||
{
|
{
|
||||||
Debug.LogError($"<color=red>[Connection] 스폰 포인트가 없습니다! 씬에 PlayerSpawnPoint가 있는지 확인하세요.</color>");
|
Debug.LogError($"<color=red>[Connection] 스폰 포인트가 없습니다! 씬에 PlayerSpawnPoint가 있는지 확인하세요.</color>");
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Approved = true;
|
response.Approved = true;
|
||||||
response.CreatePlayerObject = true;
|
response.CreatePlayerObject = false;
|
||||||
|
response.Position = Vector3.zero;
|
||||||
|
response.Rotation = Quaternion.identity;
|
||||||
|
|
||||||
// 스폰 위치 설정
|
Debug.Log($"<color=green>[Connection] 클라이언트 {request.ClientNetworkId} 승인됨. 수동 스폰으로 대기.</color>");
|
||||||
response.Position = GetSpawnPosition(request.ClientNetworkId);
|
|
||||||
response.Rotation = GetSpawnRotation(request.ClientNetworkId);
|
|
||||||
|
|
||||||
Debug.Log($"<color=green>[Connection] 클라이언트 {request.ClientNetworkId} 승인됨. 스폰 위치: {response.Position}</color>");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Vector3 GetSpawnPosition(ulong clientId)
|
private Vector3 GetSpawnPosition(ulong clientId)
|
||||||
{
|
{
|
||||||
|
spawnPoints.RemoveAll(p => p == null);
|
||||||
|
|
||||||
if (spawnPoints.Count == 0)
|
if (spawnPoints.Count == 0)
|
||||||
{
|
{
|
||||||
Debug.LogWarning("[Connection] 스폰 포인트가 없습니다. 기본 위치 반환.");
|
Debug.LogWarning("[Connection] 스폰 포인트가 없습니다. 기본 위치 반환.");
|
||||||
@@ -106,6 +169,12 @@ namespace Northbound
|
|||||||
_nextSpawnIndex = (_nextSpawnIndex + 1) % spawnPoints.Count;
|
_nextSpawnIndex = (_nextSpawnIndex + 1) % spawnPoints.Count;
|
||||||
}
|
}
|
||||||
spawnIndex = _clientSpawnIndices[clientId];
|
spawnIndex = _clientSpawnIndices[clientId];
|
||||||
|
|
||||||
|
if (spawnIndex >= spawnPoints.Count)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"<color=yellow>[Connection] 스폰 인덱스 {spawnIndex}가 범위를 벗어났습니다. 기본값으로 조정.</color>");
|
||||||
|
spawnIndex = spawnIndex % spawnPoints.Count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.Log($"<color=yellow>[Connection] 클라이언트 {clientId}에게 스폰 인덱스 {spawnIndex} 할당</color>");
|
Debug.Log($"<color=yellow>[Connection] 클라이언트 {clientId}에게 스폰 인덱스 {spawnIndex} 할당</color>");
|
||||||
@@ -114,6 +183,8 @@ namespace Northbound
|
|||||||
|
|
||||||
private Quaternion GetSpawnRotation(ulong clientId)
|
private Quaternion GetSpawnRotation(ulong clientId)
|
||||||
{
|
{
|
||||||
|
spawnPoints.RemoveAll(p => p == null);
|
||||||
|
|
||||||
if (spawnPoints.Count == 0)
|
if (spawnPoints.Count == 0)
|
||||||
return Quaternion.identity;
|
return Quaternion.identity;
|
||||||
|
|
||||||
@@ -121,6 +192,11 @@ namespace Northbound
|
|||||||
? _clientSpawnIndices[clientId]
|
? _clientSpawnIndices[clientId]
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
|
if (spawnIndex >= spawnPoints.Count)
|
||||||
|
{
|
||||||
|
spawnIndex = spawnIndex % spawnPoints.Count;
|
||||||
|
}
|
||||||
|
|
||||||
return spawnPoints[spawnIndex].rotation;
|
return spawnPoints[spawnIndex].rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +206,7 @@ namespace Northbound
|
|||||||
{
|
{
|
||||||
NetworkManager.Singleton.ConnectionApprovalCallback = null;
|
NetworkManager.Singleton.ConnectionApprovalCallback = null;
|
||||||
NetworkManager.Singleton.OnServerStarted -= OnServerStarted;
|
NetworkManager.Singleton.OnServerStarted -= OnServerStarted;
|
||||||
|
NetworkManager.Singleton.OnClientConnectedCallback -= OnClientConnected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
86
Assets/Scripts/NetworkDebug.cs
Normal file
86
Assets/Scripts/NetworkDebug.cs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using UnityEngine;
|
||||||
|
using Unity.Netcode;
|
||||||
|
using Northbound;
|
||||||
|
|
||||||
|
public class NetworkDebug : MonoBehaviour
|
||||||
|
{
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
Debug.Log("=== NETWORK DEBUG INFO ===");
|
||||||
|
|
||||||
|
// 1. NetworkManager 상태
|
||||||
|
if (NetworkManager.Singleton != null)
|
||||||
|
{
|
||||||
|
Debug.Log($"NetworkManager: IsServer={NetworkManager.Singleton.IsServer}, IsClient={NetworkManager.Singleton.IsClient}, IsHost={NetworkManager.Singleton.IsHost}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 스폰 포인트 검색
|
||||||
|
PlayerSpawnPoint[] spawnPoints = FindObjectsByType<PlayerSpawnPoint>(FindObjectsSortMode.None);
|
||||||
|
Debug.Log($"PlayerSpawnPoints: Found {spawnPoints.Length} objects");
|
||||||
|
foreach (var sp in spawnPoints)
|
||||||
|
{
|
||||||
|
Debug.Log($" - {sp.name} at {sp.transform.position}, isAvailable={sp.isAvailable}, spawnIndex={sp.spawnIndex}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. NetworkConnectionHandler 확인
|
||||||
|
var connectionHandler = FindObjectOfType<NetworkConnectionHandler>();
|
||||||
|
if (connectionHandler != null)
|
||||||
|
{
|
||||||
|
Debug.Log($"NetworkConnectionHandler: spawnPoints.Count={connectionHandler.spawnPoints.Count}, findAuto={connectionHandler.findSpawnPointsAutomatically}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. NetworkSpawnManager 확인
|
||||||
|
var spawnManager = FindObjectOfType<Northbound.NetworkSpawnManager>();
|
||||||
|
if (spawnManager != null)
|
||||||
|
{
|
||||||
|
Debug.Log($"NetworkSpawnManager: spawnPoints.Count={spawnManager.spawnPoints.Count}, findAuto={spawnManager.findSpawnPointsAutomatically}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 플레이어가 스폰될 때까지 대기
|
||||||
|
StartCoroutine(CheckPlayerAfterSpawn());
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator CheckPlayerAfterSpawn()
|
||||||
|
{
|
||||||
|
// 플레이어가 스폰될 때까지 최대 5초 대기
|
||||||
|
for (int i = 0; i < 50; i++)
|
||||||
|
{
|
||||||
|
yield return new WaitForSeconds(0.1f);
|
||||||
|
|
||||||
|
var players = GameObject.FindObjectsByType<NetworkPlayerController>(FindObjectsSortMode.None);
|
||||||
|
if (players != null && players.Length > 0)
|
||||||
|
{
|
||||||
|
Debug.Log($"Found {players.Length} player(s) - checking equipment...");
|
||||||
|
|
||||||
|
foreach (var playerCtrl in players)
|
||||||
|
{
|
||||||
|
Debug.Log($"Player found: {playerCtrl.name}, IsOwner={playerCtrl.IsOwner}");
|
||||||
|
|
||||||
|
var equipmentSocketOnPlayer = playerCtrl.GetComponent<Northbound.EquipmentSocket>();
|
||||||
|
var networkEquipSocketOnPlayer = playerCtrl.GetComponent<Northbound.NetworkEquipmentSocket>();
|
||||||
|
|
||||||
|
if (equipmentSocketOnPlayer != null)
|
||||||
|
Debug.Log($"Player has EquipmentSocket: YES");
|
||||||
|
else
|
||||||
|
Debug.LogWarning($"Player has EquipmentSocket: NO");
|
||||||
|
|
||||||
|
if (networkEquipSocketOnPlayer != null)
|
||||||
|
Debug.Log($"Player has NetworkEquipmentSocket: YES");
|
||||||
|
else
|
||||||
|
Debug.LogWarning($"Player has NetworkEquipmentSocket: NO");
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log("=== DEBUG INFO END ===");
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5초 후에도 플레이어가 없는지 확인
|
||||||
|
var finalCheck = GameObject.FindObjectsByType<NetworkPlayerController>(FindObjectsSortMode.None);
|
||||||
|
if (finalCheck == null || finalCheck.Length == 0)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"Player did not spawn within 5 seconds!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/NetworkDebug.cs.meta
Normal file
2
Assets/Scripts/NetworkDebug.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c4d562c43da292a4082bb22cb0d3e361
|
||||||
162
Assets/Scripts/NetworkEquipmentSocket.cs
Normal file
162
Assets/Scripts/NetworkEquipmentSocket.cs
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Unity.Netcode;
|
||||||
|
|
||||||
|
namespace Northbound
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 네트워크 환경에서 작동하는 장비 소켓 관리
|
||||||
|
/// </summary>
|
||||||
|
public class NetworkEquipmentSocket : NetworkBehaviour
|
||||||
|
{
|
||||||
|
[System.Serializable]
|
||||||
|
public class Socket
|
||||||
|
{
|
||||||
|
public string socketName; // "RightHand", "LeftHand", "Back" 등
|
||||||
|
public Transform socketTransform; // 실제 본 Transform
|
||||||
|
[HideInInspector] public NetworkObject currentEquipmentNetworkObj; // 현재 장착된 장비 NetworkObject
|
||||||
|
[HideInInspector] public GameObject currentEquipment; // 현재 장착된 장비 GameObject
|
||||||
|
}
|
||||||
|
|
||||||
|
[Header("Available Sockets")]
|
||||||
|
public List<Socket> sockets = new List<Socket>();
|
||||||
|
|
||||||
|
private Dictionary<string, Socket> _socketDict = new Dictionary<string, Socket>();
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
// 빠른 검색을 위한 딕셔너리 생성
|
||||||
|
foreach (var socket in sockets)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(socket.socketName))
|
||||||
|
{
|
||||||
|
_socketDict[socket.socketName] = socket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 소켓에 장비 부착 (네트워크 스폰)
|
||||||
|
/// </summary>
|
||||||
|
public GameObject AttachToSocket(string socketName, GameObject equipmentPrefab)
|
||||||
|
{
|
||||||
|
if (!IsServer)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[NetworkEquipmentSocket] 서버에서만 장비를 장착할 수 있습니다.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_socketDict.TryGetValue(socketName, out Socket socket))
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[NetworkEquipmentSocket] 소켓을 찾을 수 없습니다: {socketName}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (socket.socketTransform == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[NetworkEquipmentSocket] 소켓 Transform이 없습니다: {socketName}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 기존 장비 제거
|
||||||
|
DetachFromSocket(socketName);
|
||||||
|
|
||||||
|
// 새 장비 생성
|
||||||
|
if (equipmentPrefab != null)
|
||||||
|
{
|
||||||
|
GameObject equipment = Instantiate(equipmentPrefab, socket.socketTransform);
|
||||||
|
equipment.transform.localPosition = Vector3.zero;
|
||||||
|
equipment.transform.localRotation = Quaternion.identity;
|
||||||
|
|
||||||
|
// NetworkObject 확인
|
||||||
|
NetworkObject netObj = equipment.GetComponent<NetworkObject>();
|
||||||
|
if (netObj == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[NetworkEquipmentSocket] 장비 프리팹에 NetworkObject가 없습니다: {equipmentPrefab.name}");
|
||||||
|
Destroy(equipment);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 네트워크에 스폰
|
||||||
|
netObj.Spawn(true); // true = 소유자가 파괴되면 장비도 파괴
|
||||||
|
socket.currentEquipment = equipment;
|
||||||
|
socket.currentEquipmentNetworkObj = netObj;
|
||||||
|
|
||||||
|
Debug.Log($"<color=green>[NetworkEquipmentSocket] 장비 장착됨: {socketName} -> {equipmentPrefab.name}</color>");
|
||||||
|
return equipment;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 소켓에서 장비 제거 (네트워크 디스폰)
|
||||||
|
/// </summary>
|
||||||
|
public void DetachFromSocket(string socketName)
|
||||||
|
{
|
||||||
|
if (!IsServer)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[NetworkEquipmentSocket] 서버에서만 장비를 제거할 수 있습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_socketDict.TryGetValue(socketName, out Socket socket))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (socket.currentEquipment != null)
|
||||||
|
{
|
||||||
|
// 네트워크에서 디스폰
|
||||||
|
if (socket.currentEquipmentNetworkObj != null && socket.currentEquipmentNetworkObj.IsSpawned)
|
||||||
|
{
|
||||||
|
socket.currentEquipmentNetworkObj.Despawn(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 로컬 파괴
|
||||||
|
if (socket.currentEquipment != null)
|
||||||
|
{
|
||||||
|
Destroy(socket.currentEquipment);
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.currentEquipment = null;
|
||||||
|
socket.currentEquipmentNetworkObj = null;
|
||||||
|
|
||||||
|
Debug.Log($"<color=yellow>[NetworkEquipmentSocket] 장비 제거됨: {socketName}</color>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 모든 소켓에서 장비 제거
|
||||||
|
/// </summary>
|
||||||
|
public void DetachAll()
|
||||||
|
{
|
||||||
|
if (!IsServer) return;
|
||||||
|
|
||||||
|
foreach (var socket in sockets)
|
||||||
|
{
|
||||||
|
if (socket.currentEquipment != null)
|
||||||
|
{
|
||||||
|
if (socket.currentEquipmentNetworkObj != null && socket.currentEquipmentNetworkObj.IsSpawned)
|
||||||
|
{
|
||||||
|
socket.currentEquipmentNetworkObj.Despawn(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Destroy(socket.currentEquipment);
|
||||||
|
socket.currentEquipment = null;
|
||||||
|
socket.currentEquipmentNetworkObj = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 특정 소켓에 장비가 있는지 확인
|
||||||
|
/// </summary>
|
||||||
|
public bool HasEquipment(string socketName)
|
||||||
|
{
|
||||||
|
if (_socketDict.TryGetValue(socketName, out Socket socket))
|
||||||
|
{
|
||||||
|
return socket.currentEquipment != null;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/NetworkEquipmentSocket.cs.meta
Normal file
2
Assets/Scripts/NetworkEquipmentSocket.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 58c239d2e984593429db90ada66cbdcf
|
||||||
@@ -1,35 +1,30 @@
|
|||||||
using Unity.Netcode;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
|
||||||
public class NetworkManagerUI : MonoBehaviour
|
namespace Northbound
|
||||||
{
|
{
|
||||||
[Header("UI Buttons")]
|
/// <summary>
|
||||||
[SerializeField] private Button hostButton;
|
/// 키보드 단축키로 클라이언트/호스트 모드 시작
|
||||||
[SerializeField] private Button clientButton;
|
/// </summary>
|
||||||
[SerializeField] private Button serverButton;
|
public class ShortcutNetworkStarterUI : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("UI Reference")]
|
||||||
|
[SerializeField] private Text statusText;
|
||||||
|
|
||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
if (hostButton != null)
|
if (statusText != null)
|
||||||
hostButton.onClick.AddListener(() => NetworkManager.Singleton.StartHost());
|
|
||||||
|
|
||||||
if (clientButton != null)
|
|
||||||
clientButton.onClick.AddListener(() => NetworkManager.Singleton.StartClient());
|
|
||||||
|
|
||||||
if (serverButton != null)
|
|
||||||
serverButton.onClick.AddListener(() => NetworkManager.Singleton.StartServer());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDestroy()
|
|
||||||
{
|
{
|
||||||
if (hostButton != null)
|
statusText.text = "'C' 키를 누르면 클라이언트 모드 시작";
|
||||||
hostButton.onClick.RemoveAllListeners();
|
}
|
||||||
|
}
|
||||||
if (clientButton != null)
|
|
||||||
clientButton.onClick.RemoveAllListeners();
|
private void Update()
|
||||||
|
{
|
||||||
if (serverButton != null)
|
if (Input.GetKeyDown(KeyCode.BackQuote))
|
||||||
serverButton.onClick.RemoveAllListeners();
|
{
|
||||||
|
statusText.gameObject.SetActive(!statusText.gameObject.activeSelf);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,13 +194,15 @@ namespace Northbound
|
|||||||
|
|
||||||
public void OnInteractionComplete()
|
public void OnInteractionComplete()
|
||||||
{
|
{
|
||||||
|
if (!IsOwner) return;
|
||||||
|
|
||||||
if (_interactionTimeoutCoroutine != null)
|
if (_interactionTimeoutCoroutine != null)
|
||||||
{
|
{
|
||||||
StopCoroutine(_interactionTimeoutCoroutine);
|
StopCoroutine(_interactionTimeoutCoroutine);
|
||||||
_interactionTimeoutCoroutine = null;
|
_interactionTimeoutCoroutine = null;
|
||||||
}
|
}
|
||||||
_isInteracting = false;
|
_isInteracting = false;
|
||||||
Debug.Log("[PlayerInteraction] 상호작용 완료");
|
Debug.Log($"[PlayerInteraction] Owner {OwnerClientId} - 상호작용 완료");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|||||||
@@ -3,74 +3,57 @@ using UnityEngine;
|
|||||||
|
|
||||||
namespace Northbound
|
namespace Northbound
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 플레이어의 자원 인벤토리 관리
|
|
||||||
/// </summary>
|
|
||||||
public class PlayerResourceInventory : NetworkBehaviour
|
public class PlayerResourceInventory : NetworkBehaviour
|
||||||
{
|
{
|
||||||
[Header("Inventory Settings")]
|
public int maxResourceCapacity = 100;
|
||||||
public int maxResourceCapacity = 100; // 최대 자원 보유량
|
private int _displayAmount = 0;
|
||||||
|
|
||||||
private NetworkVariable<int> _currentResourceAmount = new NetworkVariable<int>(
|
public int CurrentResourceAmount => _displayAmount;
|
||||||
0,
|
|
||||||
NetworkVariableReadPermission.Everyone,
|
|
||||||
NetworkVariableWritePermission.Server
|
|
||||||
);
|
|
||||||
|
|
||||||
public int CurrentResourceAmount => _currentResourceAmount.Value;
|
|
||||||
public int MaxResourceCapacity => maxResourceCapacity;
|
public int MaxResourceCapacity => maxResourceCapacity;
|
||||||
|
|
||||||
/// <summary>
|
public override void OnNetworkSpawn()
|
||||||
/// 자원을 추가할 수 있는지 확인
|
{
|
||||||
/// </summary>
|
if (IsClient && IsOwner)
|
||||||
|
{
|
||||||
|
RequestResourceUpdateServerRpc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Rpc(SendTo.Server)]
|
||||||
|
public void RequestResourceUpdateServerRpc()
|
||||||
|
{
|
||||||
|
var resourceManager = ServerResourceManager.Instance;
|
||||||
|
if (resourceManager != null)
|
||||||
|
{
|
||||||
|
int amount = resourceManager.GetPlayerResourceAmount(OwnerClientId);
|
||||||
|
UpdateResourceAmountClientRpc(amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Rpc(SendTo.ClientsAndHost)]
|
||||||
|
private void UpdateResourceAmountClientRpc(int amount)
|
||||||
|
{
|
||||||
|
_displayAmount = amount;
|
||||||
|
}
|
||||||
|
|
||||||
public bool CanAddResource(int amount)
|
public bool CanAddResource(int amount)
|
||||||
{
|
{
|
||||||
return _currentResourceAmount.Value + amount <= maxResourceCapacity;
|
var resourceManager = ServerResourceManager.Instance;
|
||||||
|
if (resourceManager != null)
|
||||||
|
{
|
||||||
|
return resourceManager.CanAddResource(OwnerClientId, amount);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 추가 가능한 최대 자원량 계산
|
|
||||||
/// </summary>
|
|
||||||
public int GetAvailableSpace()
|
public int GetAvailableSpace()
|
||||||
{
|
{
|
||||||
return maxResourceCapacity - _currentResourceAmount.Value;
|
var resourceManager = ServerResourceManager.Instance;
|
||||||
}
|
if (resourceManager != null)
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 자원 추가 (서버에서만 호출)
|
|
||||||
/// </summary>
|
|
||||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
|
||||||
public void AddResourceServerRpc(int amount)
|
|
||||||
{
|
{
|
||||||
if (amount <= 0) return;
|
return resourceManager.GetAvailableSpace(OwnerClientId);
|
||||||
|
|
||||||
int actualAmount = Mathf.Min(amount, maxResourceCapacity - _currentResourceAmount.Value);
|
|
||||||
_currentResourceAmount.Value += actualAmount;
|
|
||||||
|
|
||||||
Debug.Log($"플레이어 {OwnerClientId} - 자원 추가: +{actualAmount}, 현재: {_currentResourceAmount.Value}/{maxResourceCapacity}");
|
|
||||||
}
|
}
|
||||||
|
return 0;
|
||||||
/// <summary>
|
|
||||||
/// 자원 제거 (서버에서만 호출)
|
|
||||||
/// </summary>
|
|
||||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
|
||||||
public void RemoveResourceServerRpc(int amount)
|
|
||||||
{
|
|
||||||
if (amount <= 0) return;
|
|
||||||
|
|
||||||
int actualAmount = Mathf.Min(amount, _currentResourceAmount.Value);
|
|
||||||
_currentResourceAmount.Value -= actualAmount;
|
|
||||||
|
|
||||||
Debug.Log($"플레이어 {OwnerClientId} - 자원 사용: -{actualAmount}, 현재: {_currentResourceAmount.Value}/{maxResourceCapacity}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 자원 설정 (서버에서만 호출)
|
|
||||||
/// </summary>
|
|
||||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
|
||||||
public void SetResourceServerRpc(int amount)
|
|
||||||
{
|
|
||||||
_currentResourceAmount.Value = Mathf.Clamp(amount, 0, maxResourceCapacity);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using Unity.Netcode;
|
using Unity.Netcode;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Northbound
|
namespace Northbound
|
||||||
{
|
{
|
||||||
@@ -112,6 +113,7 @@ namespace Northbound
|
|||||||
{
|
{
|
||||||
_currentResources.Value = maxResources;
|
_currentResources.Value = maxResources;
|
||||||
_lastRechargeTime = Time.time;
|
_lastRechargeTime = Time.time;
|
||||||
|
_lastGatheringTime = Time.time - gatheringCooldown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,29 +139,18 @@ namespace Northbound
|
|||||||
|
|
||||||
public bool CanInteract(ulong playerId)
|
public bool CanInteract(ulong playerId)
|
||||||
{
|
{
|
||||||
// 자원 노드에 자원이 없으면 상호작용 불가
|
|
||||||
if (_currentResources.Value <= 0)
|
if (_currentResources.Value <= 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// 쿨다운 확인
|
|
||||||
if (Time.time - _lastGatheringTime < gatheringCooldown)
|
if (Time.time - _lastGatheringTime < gatheringCooldown)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// 플레이어 인벤토리 확인
|
var resourceManager = ServerResourceManager.Instance;
|
||||||
if (NetworkManager.Singleton != null &&
|
if (resourceManager != null)
|
||||||
NetworkManager.Singleton.ConnectedClients.TryGetValue(playerId, out var client))
|
|
||||||
{
|
{
|
||||||
if (client.PlayerObject != null)
|
if (resourceManager.GetAvailableSpace(playerId) <= 0)
|
||||||
{
|
|
||||||
var playerInventory = client.PlayerObject.GetComponent<PlayerResourceInventory>();
|
|
||||||
if (playerInventory != null)
|
|
||||||
{
|
|
||||||
// 플레이어가 받을 수 있는 공간이 없으면 상호작용 불가
|
|
||||||
if (playerInventory.GetAvailableSpace() <= 0)
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -211,17 +202,11 @@ namespace Northbound
|
|||||||
if (!CanInteract(playerId))
|
if (!CanInteract(playerId))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var playerObject = NetworkManager.Singleton.ConnectedClients[playerId].PlayerObject;
|
var resourceManager = ServerResourceManager.Instance;
|
||||||
if (playerObject == null)
|
if (resourceManager == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var playerInventory = playerObject.GetComponent<PlayerResourceInventory>();
|
int playerAvailableSpace = resourceManager.GetAvailableSpace(playerId);
|
||||||
if (playerInventory == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int playerAvailableSpace = playerInventory.GetAvailableSpace();
|
|
||||||
|
|
||||||
int gatheredAmount = Mathf.Min(
|
int gatheredAmount = Mathf.Min(
|
||||||
resourcesPerGathering,
|
resourcesPerGathering,
|
||||||
@@ -237,12 +222,27 @@ namespace Northbound
|
|||||||
_currentResources.Value -= gatheredAmount;
|
_currentResources.Value -= gatheredAmount;
|
||||||
_lastGatheringTime = Time.time;
|
_lastGatheringTime = Time.time;
|
||||||
|
|
||||||
playerInventory.AddResourceServerRpc(gatheredAmount);
|
resourceManager.AddResource(playerId, gatheredAmount);
|
||||||
|
UpdatePlayerResourcesClientRpc(playerId);
|
||||||
|
|
||||||
ShowGatheringEffectClientRpc();
|
ShowGatheringEffectClientRpc();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Rpc(SendTo.ClientsAndHost)]
|
[Rpc(SendTo.ClientsAndHost)]
|
||||||
|
private void UpdatePlayerResourcesClientRpc(ulong playerId)
|
||||||
|
{
|
||||||
|
var playerObject = NetworkManager.Singleton.ConnectedClients[playerId].PlayerObject;
|
||||||
|
if (playerObject != null)
|
||||||
|
{
|
||||||
|
var playerInventory = playerObject.GetComponent<PlayerResourceInventory>();
|
||||||
|
if (playerInventory != null)
|
||||||
|
{
|
||||||
|
playerInventory.RequestResourceUpdateServerRpc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Rpc(SendTo.NotServer)]
|
||||||
private void ShowGatheringEffectClientRpc()
|
private void ShowGatheringEffectClientRpc()
|
||||||
{
|
{
|
||||||
if (gatheringEffectPrefab != null && effectSpawnPoint != null)
|
if (gatheringEffectPrefab != null && effectSpawnPoint != null)
|
||||||
|
|||||||
@@ -32,25 +32,15 @@ namespace Northbound
|
|||||||
|
|
||||||
public bool CanInteract(ulong playerId)
|
public bool CanInteract(ulong playerId)
|
||||||
{
|
{
|
||||||
// 이미 수집됨
|
|
||||||
if (_isCollected)
|
if (_isCollected)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// 플레이어 인벤토리 확인
|
var resourceManager = ServerResourceManager.Instance;
|
||||||
if (NetworkManager.Singleton != null &&
|
if (resourceManager != null)
|
||||||
NetworkManager.Singleton.ConnectedClients.TryGetValue(playerId, out var client))
|
|
||||||
{
|
{
|
||||||
if (client.PlayerObject != null)
|
if (resourceManager.GetAvailableSpace(playerId) <= 0)
|
||||||
{
|
|
||||||
var playerInventory = client.PlayerObject.GetComponent<PlayerResourceInventory>();
|
|
||||||
if (playerInventory != null)
|
|
||||||
{
|
|
||||||
// 플레이어가 받을 수 있는 공간이 없으면 상호작용 불가
|
|
||||||
if (playerInventory.GetAvailableSpace() <= 0)
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -69,49 +59,52 @@ namespace Northbound
|
|||||||
if (!CanInteract(playerId))
|
if (!CanInteract(playerId))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// 중복 수집 방지
|
|
||||||
if (_isCollected)
|
if (_isCollected)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_isCollected = true;
|
_isCollected = true;
|
||||||
|
|
||||||
// 플레이어의 인벤토리 확인
|
var resourceManager = ServerResourceManager.Instance;
|
||||||
var playerObject = NetworkManager.Singleton.ConnectedClients[playerId].PlayerObject;
|
if (resourceManager == null)
|
||||||
if (playerObject == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var playerInventory = playerObject.GetComponent<PlayerResourceInventory>();
|
|
||||||
if (playerInventory == null)
|
|
||||||
{
|
{
|
||||||
Debug.LogWarning($"플레이어 {playerId}에게 PlayerResourceInventory 컴포넌트가 없습니다.");
|
Debug.LogWarning("ServerResourceManager 인스턴스를 찾을 수 없습니다.");
|
||||||
|
_isCollected = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 플레이어가 받을 수 있는 최대량 계산
|
int playerAvailableSpace = resourceManager.GetAvailableSpace(playerId);
|
||||||
int playerAvailableSpace = playerInventory.GetAvailableSpace();
|
|
||||||
|
|
||||||
// 실제 지급할 양 계산
|
|
||||||
int collectedAmount = Mathf.Min(resourceAmount, playerAvailableSpace);
|
int collectedAmount = Mathf.Min(resourceAmount, playerAvailableSpace);
|
||||||
|
|
||||||
if (collectedAmount <= 0)
|
if (collectedAmount <= 0)
|
||||||
{
|
{
|
||||||
Debug.Log($"플레이어 {playerId}의 인벤토리가 가득 찼습니다.");
|
Debug.Log($"플레이어 {playerId}의 인벤토리가 가득 찼습니다.");
|
||||||
_isCollected = false; // 수집 실패 시 다시 시도 가능하도록
|
_isCollected = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 플레이어에게 자원 추가
|
resourceManager.AddResource(playerId, collectedAmount);
|
||||||
playerInventory.AddResourceServerRpc(collectedAmount);
|
UpdatePlayerResourcesClientRpc(playerId);
|
||||||
|
|
||||||
Debug.Log($"플레이어 {playerId}가 {collectedAmount} {resourceName}을(를) 획득했습니다.");
|
Debug.Log($"플레이어 {playerId}가 {collectedAmount} {resourceName}을(를) 획득했습니다.");
|
||||||
|
|
||||||
// 이펙트 표시 및 오브젝트 제거
|
|
||||||
ShowPickupEffectClientRpc();
|
ShowPickupEffectClientRpc();
|
||||||
|
|
||||||
// 짧은 딜레이 후 제거 (이펙트를 위해)
|
|
||||||
Invoke(nameof(DestroyPickup), 0.1f);
|
Invoke(nameof(DestroyPickup), 0.1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Rpc(SendTo.ClientsAndHost)]
|
||||||
|
private void UpdatePlayerResourcesClientRpc(ulong playerId)
|
||||||
|
{
|
||||||
|
var playerObject = NetworkManager.Singleton.ConnectedClients[playerId].PlayerObject;
|
||||||
|
if (playerObject != null)
|
||||||
|
{
|
||||||
|
var playerInventory = playerObject.GetComponent<PlayerResourceInventory>();
|
||||||
|
if (playerInventory != null)
|
||||||
|
{
|
||||||
|
playerInventory.RequestResourceUpdateServerRpc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Rpc(SendTo.ClientsAndHost)]
|
[Rpc(SendTo.ClientsAndHost)]
|
||||||
private void ShowPickupEffectClientRpc()
|
private void ShowPickupEffectClientRpc()
|
||||||
{
|
{
|
||||||
|
|||||||
101
Assets/Scripts/ServerResourceManager.cs
Normal file
101
Assets/Scripts/ServerResourceManager.cs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
using Unity.Netcode;
|
||||||
|
using UnityEngine;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Northbound
|
||||||
|
{
|
||||||
|
public class ServerResourceManager : NetworkBehaviour
|
||||||
|
{
|
||||||
|
public static ServerResourceManager Instance { get; private set; }
|
||||||
|
|
||||||
|
private Dictionary<ulong, int> _playerResources = new Dictionary<ulong, int>();
|
||||||
|
private NetworkVariable<int> _resourcesData = new NetworkVariable<int>();
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
if (Instance != null && Instance != this)
|
||||||
|
{
|
||||||
|
Destroy(gameObject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNetworkSpawn()
|
||||||
|
{
|
||||||
|
if (IsServer)
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnected;
|
||||||
|
NetworkManager.Singleton.OnClientDisconnectCallback += OnClientDisconnected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNetworkDespawn()
|
||||||
|
{
|
||||||
|
if (IsServer)
|
||||||
|
{
|
||||||
|
if (NetworkManager.Singleton != null)
|
||||||
|
{
|
||||||
|
NetworkManager.Singleton.OnClientConnectedCallback -= OnClientConnected;
|
||||||
|
NetworkManager.Singleton.OnClientDisconnectCallback -= OnClientDisconnected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClientConnected(ulong clientId)
|
||||||
|
{
|
||||||
|
_playerResources[clientId] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClientDisconnected(ulong clientId)
|
||||||
|
{
|
||||||
|
_playerResources.Remove(clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetPlayerResourceAmount(ulong clientId)
|
||||||
|
{
|
||||||
|
if (_playerResources.TryGetValue(clientId, out var resource))
|
||||||
|
{
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanAddResource(ulong clientId, int amount)
|
||||||
|
{
|
||||||
|
if (_playerResources.TryGetValue(clientId, out var resource))
|
||||||
|
{
|
||||||
|
return resource + amount <= 100;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetAvailableSpace(ulong clientId)
|
||||||
|
{
|
||||||
|
if (_playerResources.TryGetValue(clientId, out var resource))
|
||||||
|
{
|
||||||
|
return 100 - resource;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddResource(ulong clientId, int amount)
|
||||||
|
{
|
||||||
|
if (_playerResources.TryGetValue(clientId, out var resource))
|
||||||
|
{
|
||||||
|
int actualAmount = Mathf.Min(amount, 100 - resource);
|
||||||
|
_playerResources[clientId] = resource + actualAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveResource(ulong clientId, int amount)
|
||||||
|
{
|
||||||
|
if (_playerResources.TryGetValue(clientId, out var resource))
|
||||||
|
{
|
||||||
|
int actualAmount = Mathf.Min(amount, resource);
|
||||||
|
_playerResources[clientId] = resource - actualAmount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/ServerResourceManager.cs.meta
Normal file
2
Assets/Scripts/ServerResourceManager.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1cf781ac9fc26344a883a69b98104373
|
||||||
50
Assets/Scripts/ShortcutNetworkStarter.cs
Normal file
50
Assets/Scripts/ShortcutNetworkStarter.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.InputSystem;
|
||||||
|
using Unity.Netcode;
|
||||||
|
|
||||||
|
namespace Northbound
|
||||||
|
{
|
||||||
|
public class ShortcutNetworkStarter : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("Input Actions")]
|
||||||
|
[SerializeField] private InputActionReference startAsClientAction;
|
||||||
|
|
||||||
|
private bool _isClientStarted = false;
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
if (startAsClientAction != null && startAsClientAction.action != null)
|
||||||
|
{
|
||||||
|
startAsClientAction.action.performed += StartAsClient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
if (startAsClientAction != null && startAsClientAction.action != null)
|
||||||
|
{
|
||||||
|
startAsClientAction.action.performed -= StartAsClient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartAsClient(InputAction.CallbackContext context)
|
||||||
|
{
|
||||||
|
if (NetworkManager.Singleton == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("[ShortcutNetworkStarter] NetworkManager.Singleton이 null입니다!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NetworkManager.Singleton.IsClient)
|
||||||
|
{
|
||||||
|
Debug.Log("[ShortcutNetworkStarter] 이미 클라이언트 모드입니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isClientStarted = true;
|
||||||
|
|
||||||
|
Debug.Log("[ShortcutNetworkStarter] 클라이언트 모드로 시작합니다...");
|
||||||
|
NetworkManager.Singleton.StartClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/ShortcutNetworkStarter.cs.meta
Normal file
2
Assets/Scripts/ShortcutNetworkStarter.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c83db7812a3e91149a9626a2302f43cd
|
||||||
8
Assets/Settings/Build Profiles.meta
Normal file
8
Assets/Settings/Build Profiles.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7231ff870d55b254384c1a74dbeaf7f6
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
94
Assets/Settings/Build Profiles/Windows.asset
Normal file
94
Assets/Settings/Build Profiles/Windows.asset
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 15003, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
m_Name: Windows
|
||||||
|
m_EditorClassIdentifier: UnityEditor.dll::UnityEditor.Build.Profile.BuildProfile
|
||||||
|
m_AssetVersion: 1
|
||||||
|
m_BuildTarget: 19
|
||||||
|
m_Subtarget: 2
|
||||||
|
m_PlatformId: 4e3c793746204150860bf175a9a41a05
|
||||||
|
m_PlatformBuildProfile:
|
||||||
|
rid: 4349351379921797208
|
||||||
|
m_OverrideGlobalSceneList: 0
|
||||||
|
m_Scenes: []
|
||||||
|
m_HasScriptingDefines: 0
|
||||||
|
m_ScriptingDefines: []
|
||||||
|
m_PlayerSettingsYaml:
|
||||||
|
m_Settings: []
|
||||||
|
references:
|
||||||
|
version: 2
|
||||||
|
RefIds:
|
||||||
|
- rid: 4349351379921797208
|
||||||
|
type: {class: WindowsPlatformSettings, ns: UnityEditor.WindowsStandalone, asm: UnityEditor.WindowsStandalone.Extensions}
|
||||||
|
data:
|
||||||
|
m_Development: 0
|
||||||
|
m_ConnectProfiler: 0
|
||||||
|
m_BuildWithDeepProfilingSupport: 0
|
||||||
|
m_AllowDebugging: 0
|
||||||
|
m_WaitForManagedDebugger: 0
|
||||||
|
m_ManagedDebuggerFixedPort: 0
|
||||||
|
m_ExplicitNullChecks: 0
|
||||||
|
m_ExplicitDivideByZeroChecks: 0
|
||||||
|
m_ExplicitArrayBoundsChecks: 0
|
||||||
|
m_CompressionType: 0
|
||||||
|
m_InstallInBuildFolder: 0
|
||||||
|
m_InsightsSettingsContainer:
|
||||||
|
m_BuildProfileEngineDiagnosticsState: 2
|
||||||
|
m_AdaptivePerformanceEnabled: 0
|
||||||
|
m_WindowsBuildAndRunDeployTarget: 0
|
||||||
|
m_Architecture: 0
|
||||||
|
m_CreateSolution: 0
|
||||||
|
m_CopyPDBFiles: 0
|
||||||
|
m_WindowsDevicePortalAddress:
|
||||||
|
m_WindowsDevicePortalUsername:
|
||||||
|
--- !u!114 &5293933444619661119
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 3
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 15009, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
|
m_Name: Graphics Settings
|
||||||
|
m_EditorClassIdentifier: UnityEditor.dll::UnityEditor.Build.Profile.BuildProfileGraphicsSettings
|
||||||
|
m_LightmapStripping: 0
|
||||||
|
m_LightmapKeepPlain: 1
|
||||||
|
m_LightmapKeepDirCombined: 1
|
||||||
|
m_LightmapKeepDynamicPlain: 1
|
||||||
|
m_LightmapKeepDynamicDirCombined: 1
|
||||||
|
m_LightmapKeepShadowMask: 1
|
||||||
|
m_LightmapKeepSubtractive: 1
|
||||||
|
m_FogStripping: 0
|
||||||
|
m_FogKeepLinear: 1
|
||||||
|
m_FogKeepExp: 1
|
||||||
|
m_FogKeepExp2: 1
|
||||||
|
m_InstancingStripping: 0
|
||||||
|
m_BrgStripping: 0
|
||||||
|
m_LogWhenShaderIsCompiled: 0
|
||||||
|
m_CameraRelativeLightCulling: 0
|
||||||
|
m_CameraRelativeShadowCulling: 0
|
||||||
|
m_VideoShadersIncludeMode: 2
|
||||||
|
m_AlwaysIncludedShaders:
|
||||||
|
- {fileID: 7, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
- {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
- {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
- {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
- {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
- {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
- {fileID: 10783, guid: 0000000000000000f000000000000000, type: 0}
|
||||||
|
m_LightProbeOutsideHullStrategy: 0
|
||||||
|
m_PreloadedShaders: []
|
||||||
|
m_PreloadShadersBatchTimeLimit: -1
|
||||||
|
m_ShaderBuildSettings:
|
||||||
|
keywordDeclarationOverrides: []
|
||||||
8
Assets/Settings/Build Profiles/Windows.asset.meta
Normal file
8
Assets/Settings/Build Profiles/Windows.asset.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b278d7bcf7e5c5b46ae41f7fa05f9d49
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -15,7 +15,7 @@ MonoBehaviour:
|
|||||||
k_AssetVersion: 13
|
k_AssetVersion: 13
|
||||||
k_AssetPreviousVersion: 13
|
k_AssetPreviousVersion: 13
|
||||||
m_RendererType: 1
|
m_RendererType: 1
|
||||||
m_RendererData: {fileID: 11400000, guid: 618d298269e66c542b306de85db1faea, type: 2}
|
m_RendererData: {fileID: 0}
|
||||||
m_RendererDataList:
|
m_RendererDataList:
|
||||||
- {fileID: 11400000, guid: 569816eb300be8149adcef0c7c162f8c, type: 2}
|
- {fileID: 11400000, guid: 569816eb300be8149adcef0c7c162f8c, type: 2}
|
||||||
m_DefaultRendererIndex: 0
|
m_DefaultRendererIndex: 0
|
||||||
@@ -128,14 +128,14 @@ MonoBehaviour:
|
|||||||
m_PrefilterSoftShadowsQualityHigh: 1
|
m_PrefilterSoftShadowsQualityHigh: 1
|
||||||
m_PrefilterSoftShadows: 0
|
m_PrefilterSoftShadows: 0
|
||||||
m_PrefilterScreenCoord: 1
|
m_PrefilterScreenCoord: 1
|
||||||
m_PrefilterScreenSpaceIrradiance: 0
|
m_PrefilterScreenSpaceIrradiance: 1
|
||||||
m_PrefilterNativeRenderPass: 1
|
m_PrefilterNativeRenderPass: 1
|
||||||
m_PrefilterUseLegacyLightmaps: 0
|
m_PrefilterUseLegacyLightmaps: 0
|
||||||
m_PrefilterBicubicLightmapSampling: 1
|
m_PrefilterBicubicLightmapSampling: 1
|
||||||
m_PrefilterReflectionProbeRotation: 0
|
m_PrefilterReflectionProbeRotation: 1
|
||||||
m_PrefilterReflectionProbeBlending: 0
|
m_PrefilterReflectionProbeBlending: 1
|
||||||
m_PrefilterReflectionProbeBoxProjection: 0
|
m_PrefilterReflectionProbeBoxProjection: 1
|
||||||
m_PrefilterReflectionProbeAtlas: 0
|
m_PrefilterReflectionProbeAtlas: 1
|
||||||
m_ShaderVariantLogLevel: 0
|
m_ShaderVariantLogLevel: 0
|
||||||
m_ShadowCascades: 2
|
m_ShadowCascades: 2
|
||||||
m_Textures:
|
m_Textures:
|
||||||
|
|||||||
@@ -66,7 +66,19 @@ MonoBehaviour:
|
|||||||
- rid: 235636870086918150
|
- rid: 235636870086918150
|
||||||
- rid: 235636870086918151
|
- rid: 235636870086918151
|
||||||
m_RuntimeSettings:
|
m_RuntimeSettings:
|
||||||
m_List: []
|
m_List:
|
||||||
|
- rid: 7752762179098771456
|
||||||
|
- rid: 7752762179098771459
|
||||||
|
- rid: 7752762179098771461
|
||||||
|
- rid: 7752762179098771462
|
||||||
|
- rid: 7752762179098771463
|
||||||
|
- rid: 7752762179098771464
|
||||||
|
- rid: 7752762179098771466
|
||||||
|
- rid: 7752762179098771468
|
||||||
|
- rid: 7752762179098771472
|
||||||
|
- rid: 7752762179098771476
|
||||||
|
- rid: 3114554777721110529
|
||||||
|
- rid: 3114554777721110530
|
||||||
m_AssetVersion: 9
|
m_AssetVersion: 9
|
||||||
m_ObsoleteDefaultVolumeProfile: {fileID: 0}
|
m_ObsoleteDefaultVolumeProfile: {fileID: 0}
|
||||||
m_RenderingLayerNames:
|
m_RenderingLayerNames:
|
||||||
|
|||||||
@@ -1,323 +0,0 @@
|
|||||||
# Building System Setup Guide
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
The building system allows players to place buildings with:
|
|
||||||
- **Grid-based snapping** for aligned placement
|
|
||||||
- **Overlap detection** to prevent buildings from colliding
|
|
||||||
- **Ground validation** to ensure buildings are placed on terrain
|
|
||||||
- **Ghost preview** with green (valid) / red (invalid) visual feedback
|
|
||||||
- **Network synchronization** across all players
|
|
||||||
|
|
||||||
## Components
|
|
||||||
|
|
||||||
### 1. BuildingData (ScriptableObject)
|
|
||||||
Defines properties for each building type:
|
|
||||||
- Building name and prefab reference
|
|
||||||
- Grid size (width, length, height)
|
|
||||||
- Placement offset and rotation settings
|
|
||||||
|
|
||||||
### 2. Building (MonoBehaviour)
|
|
||||||
Component attached to placed building instances. Tracks:
|
|
||||||
- Grid position
|
|
||||||
- Rotation (0-3, representing 0°, 90°, 180°, 270°)
|
|
||||||
- Reference to BuildingData
|
|
||||||
|
|
||||||
### 3. BuildingManager (NetworkBehaviour - Singleton)
|
|
||||||
Central manager that:
|
|
||||||
- Maintains list of all placed buildings
|
|
||||||
- Validates placement (ground check + overlap detection)
|
|
||||||
- Handles grid snapping
|
|
||||||
- Spawns buildings on network via ServerRpc
|
|
||||||
|
|
||||||
### 4. BuildingPlacement (NetworkBehaviour)
|
|
||||||
Player-side building interface:
|
|
||||||
- Uses new Input System (`PlayerInputActions`, `Keyboard.current`, `Mouse.current`)
|
|
||||||
- Handles input (B key to toggle, Q/E to rotate, click to place)
|
|
||||||
- Shows ghost preview at mouse position
|
|
||||||
- Updates preview material (green/red) based on validity
|
|
||||||
- Proper input lifecycle management (enable/disable/dispose)
|
|
||||||
|
|
||||||
## Setup Instructions
|
|
||||||
|
|
||||||
### Step 0: Setup Input Actions (Required First!)
|
|
||||||
**Before using the building system, you MUST add the required input actions.**
|
|
||||||
|
|
||||||
See **INPUT_ACTIONS_SETUP.md** for detailed instructions.
|
|
||||||
|
|
||||||
Quick summary - Add these to your Player action map in `InputSystem_Actions.inputactions`:
|
|
||||||
1. **ToggleBuildMode** (Button) → Keyboard B
|
|
||||||
2. **Rotate** (Value/Axis) → Keyboard R
|
|
||||||
3. **Build** (Button) → Mouse Left Button
|
|
||||||
|
|
||||||
After adding, save the asset to regenerate the code.
|
|
||||||
|
|
||||||
### Step 1: Create BuildingData Assets
|
|
||||||
1. Right-click in Project window → Create → Northbound → Building Data
|
|
||||||
2. Configure your building:
|
|
||||||
- Assign the building prefab
|
|
||||||
- Set grid size (width, length, height)
|
|
||||||
- Adjust placement offset if needed
|
|
||||||
- Enable/disable rotation
|
|
||||||
|
|
||||||
### Step 2: Setup Building Prefabs
|
|
||||||
For each building prefab:
|
|
||||||
1. Add `NetworkObject` component
|
|
||||||
2. Add `Building` component
|
|
||||||
3. Ensure it has a `Renderer` for preview
|
|
||||||
4. Add colliders for gameplay (player collision, physics)
|
|
||||||
5. **Important:** Colliders are NOT used for placement validation - only BuildingData grid size matters
|
|
||||||
6. Set the prefab's layer to anything EXCEPT the Ground layer (buildings will be raycast-ignored during placement)
|
|
||||||
|
|
||||||
### Step 3: Setup BuildingManager
|
|
||||||
1. Create empty GameObject in your scene named "BuildingManager"
|
|
||||||
2. Add `BuildingManager` component
|
|
||||||
3. Add `NetworkObject` component
|
|
||||||
4. Configure settings:
|
|
||||||
- **Grid Size**: Size of one grid unit (default: 1)
|
|
||||||
- **Ground Layer**: Layer mask for valid ground (e.g., "Ground")
|
|
||||||
- **Available Buildings**: Add your BuildingData assets
|
|
||||||
|
|
||||||
### Step 4: Add BuildingPlacement to Player
|
|
||||||
1. Open your Player prefab
|
|
||||||
2. Add `BuildingPlacement` component
|
|
||||||
3. Configure:
|
|
||||||
- **Ground Layer**: Same as BuildingManager
|
|
||||||
- **Max Placement Distance**: How far player can place (default: 100)
|
|
||||||
- **Valid Material**: Green transparent material (auto-created if null)
|
|
||||||
- **Invalid Material**: Red transparent material (auto-created if null)
|
|
||||||
|
|
||||||
### Step 5: Setup Layers
|
|
||||||
1. Create a layer named "Ground" (or your preferred name)
|
|
||||||
2. Assign this layer to terrain/ground objects
|
|
||||||
3. **Important:** Make sure building prefabs are on a **different** layer (not Ground)
|
|
||||||
- This allows raycasting through buildings to hit the ground
|
|
||||||
- You can place buildings even when cursor is over existing buildings
|
|
||||||
|
|
||||||
## Controls
|
|
||||||
|
|
||||||
Default bindings (customizable in Input Actions):
|
|
||||||
- **B**: Toggle build mode on/off
|
|
||||||
- **R**: Rotate building (90° increments)
|
|
||||||
- **Left Mouse Button**: Place building (if valid position)
|
|
||||||
- **Mouse Movement**: Move preview to placement location
|
|
||||||
|
|
||||||
## Input System
|
|
||||||
|
|
||||||
The building system uses Unity's **new Input System** with action callbacks:
|
|
||||||
- Actions: `ToggleBuildMode`, `Rotate`, `Build`
|
|
||||||
- Subscribe to `.performed` events in `OnNetworkSpawn`
|
|
||||||
- Unsubscribe in `OnNetworkDespawn`
|
|
||||||
- No hardcoded keys - all controls defined in Input Actions asset
|
|
||||||
- Easy to rebind without code changes
|
|
||||||
|
|
||||||
**Pattern:**
|
|
||||||
```csharp
|
|
||||||
_inputActions.Player.ToggleBuildMode.performed += OnToggleBuildMode;
|
|
||||||
_inputActions.Player.Rotate.performed += OnRotate;
|
|
||||||
_inputActions.Player.Build.performed += OnBuild;
|
|
||||||
```
|
|
||||||
|
|
||||||
This uses callback-based input, similar to the pattern shown in `InputSystem_Actions.cs` examples.
|
|
||||||
|
|
||||||
## Technical Implementation
|
|
||||||
|
|
||||||
### Modern Input System (Callback Pattern)
|
|
||||||
```csharp
|
|
||||||
// Initialization in OnNetworkSpawn
|
|
||||||
_inputActions = new PlayerInputActions();
|
|
||||||
_inputActions.Player.ToggleBuildMode.performed += OnToggleBuildMode;
|
|
||||||
_inputActions.Player.Rotate.performed += OnRotate;
|
|
||||||
_inputActions.Player.Build.performed += OnBuild;
|
|
||||||
_inputActions.Enable();
|
|
||||||
|
|
||||||
// Callback handlers
|
|
||||||
private void OnToggleBuildMode(InputAction.CallbackContext context)
|
|
||||||
{
|
|
||||||
ToggleBuildMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRotate(InputAction.CallbackContext context)
|
|
||||||
{
|
|
||||||
float rotateValue = context.ReadValue<float>();
|
|
||||||
// Positive = rotate right, Negative = rotate left
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnBuild(InputAction.CallbackContext context)
|
|
||||||
{
|
|
||||||
TryPlaceBuilding();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup in OnNetworkDespawn
|
|
||||||
_inputActions.Player.ToggleBuildMode.performed -= OnToggleBuildMode;
|
|
||||||
_inputActions.Player.Rotate.performed -= OnRotate;
|
|
||||||
_inputActions.Player.Build.performed -= OnBuild;
|
|
||||||
_inputActions.Disable();
|
|
||||||
_inputActions.Dispose();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Modern Netcode RPC Pattern
|
|
||||||
```csharp
|
|
||||||
[ServerRpc(RequireOwnership = false)]
|
|
||||||
public void PlaceBuildingServerRpc(int buildingIndex, Vector3 position, int rotation, ServerRpcParams serverRpcParams = default)
|
|
||||||
{
|
|
||||||
// Validation
|
|
||||||
if (!IsValidPlacement(...)) return;
|
|
||||||
|
|
||||||
// Server-side spawn
|
|
||||||
GameObject obj = Instantiate(...);
|
|
||||||
NetworkObject netObj = obj.GetComponent<NetworkObject>();
|
|
||||||
netObj.Spawn(); // Automatically syncs to all clients
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
### Build Mode Flow
|
|
||||||
1. Player presses **B** → enters build mode
|
|
||||||
2. Preview appears at mouse cursor position
|
|
||||||
3. System raycasts to find ground (two-stage):
|
|
||||||
- **Method 1:** Direct raycast to ground, ignoring building colliders
|
|
||||||
- **Method 2:** If ground not visible (buildings blocking), raycast to anything, then cast downward from that point to find ground below
|
|
||||||
- This allows placement even in completely surrounded cells
|
|
||||||
4. System checks placement validity:
|
|
||||||
- Is there ground below?
|
|
||||||
- Does grid bounds overlap with existing buildings?
|
|
||||||
5. Preview shows **green** if valid, **red** if invalid
|
|
||||||
6. Player rotates with **R** if desired
|
|
||||||
7. Player clicks to confirm placement
|
|
||||||
8. **ServerRpc** sent to server to spawn building
|
|
||||||
9. Building synchronized to all clients
|
|
||||||
|
|
||||||
### Validation System (Grid-Based)
|
|
||||||
```
|
|
||||||
IsValidPlacement() checks:
|
|
||||||
├── Ground Check: Raycast down to find ground
|
|
||||||
└── Overlap Check: Compare GRID BOUNDS (not colliders!)
|
|
||||||
├── Uses BuildingData width/length/height
|
|
||||||
├── NOT the actual collider size
|
|
||||||
└── Rotates grid size automatically (90°/180°/270°)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Important:** Collision detection uses the **grid size** from `BuildingData`, not the physical colliders on the building prefab. This means:
|
|
||||||
- A building with `width=2, length=3, height=2` occupies a 2x3 grid area
|
|
||||||
- Even if the visual model is smaller/larger, the grid size determines overlap
|
|
||||||
- This allows consistent, predictable building placement
|
|
||||||
|
|
||||||
### Grid Snapping
|
|
||||||
- World position → rounded to nearest grid size
|
|
||||||
- Example: gridSize=2, position (3.7, 0, 5.2) → snaps to (4, 0, 6)
|
|
||||||
|
|
||||||
### Rotation System
|
|
||||||
- Rotation values: 0, 1, 2, 3 → 0°, 90°, 180°, 270°
|
|
||||||
- Building size auto-swaps width/length when rotated 90° or 270°
|
|
||||||
|
|
||||||
## Example Building Setup
|
|
||||||
|
|
||||||
### Small House (2x2)
|
|
||||||
```
|
|
||||||
Name: Small House
|
|
||||||
Prefab: SmallHousePrefab
|
|
||||||
Width: 2
|
|
||||||
Length: 2
|
|
||||||
Height: 3
|
|
||||||
Allow Rotation: true
|
|
||||||
```
|
|
||||||
|
|
||||||
### Wall Segment (1x3)
|
|
||||||
```
|
|
||||||
Name: Wall
|
|
||||||
Prefab: WallPrefab
|
|
||||||
Width: 1
|
|
||||||
Length: 3
|
|
||||||
Height: 2
|
|
||||||
Allow Rotation: true
|
|
||||||
```
|
|
||||||
|
|
||||||
## Debug Visualization
|
|
||||||
|
|
||||||
The system includes visual debugging to help you understand grid placement:
|
|
||||||
|
|
||||||
**In Scene View (when build mode is active):**
|
|
||||||
- **Green/Red Wire Cube**: Shows the grid bounds being checked
|
|
||||||
- Green = valid placement
|
|
||||||
- Red = invalid (overlapping or no ground)
|
|
||||||
- **Yellow Grid Cells**: Shows individual grid cells occupied
|
|
||||||
- **Yellow Sphere**: Grid origin point (snapped position)
|
|
||||||
|
|
||||||
**On Placed Buildings:**
|
|
||||||
- **Cyan Wire Cube**: Shows the grid bounds of placed buildings
|
|
||||||
- **Yellow Wire Cube** (when selected): Highlights the selected building's grid
|
|
||||||
- **Magenta Sphere**: Grid position origin
|
|
||||||
|
|
||||||
Enable/disable with:
|
|
||||||
- BuildingPlacement → Show Grid Bounds
|
|
||||||
- Building → Show Grid Bounds
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
**Preview doesn't appear:**
|
|
||||||
- Check if BuildingManager.Instance exists
|
|
||||||
- Verify availableBuildings list has BuildingData
|
|
||||||
- Ensure Ground layer is set correctly
|
|
||||||
|
|
||||||
**Can't place buildings:**
|
|
||||||
- Check ground layer mask matches terrain
|
|
||||||
- Verify building prefabs have NetworkObject
|
|
||||||
- Ensure BuildingManager has NetworkObject and is spawned
|
|
||||||
|
|
||||||
**Buildings overlap despite visual gap:**
|
|
||||||
- Remember: Collision uses **grid size** from BuildingData, not visual model size
|
|
||||||
- Check the grid bounds in Scene view (cyan wire cubes)
|
|
||||||
- Adjust width/length/height in BuildingData to match desired grid footprint
|
|
||||||
|
|
||||||
**Can't place buildings when cursor is near existing buildings:**
|
|
||||||
- FIXED: System now uses RaycastAll and ignores building colliders
|
|
||||||
- Raycasts pass through buildings to hit the ground
|
|
||||||
- You can place buildings even when cursor is hovering over old ones
|
|
||||||
- Just make sure buildings are NOT on the Ground layer
|
|
||||||
|
|
||||||
**Can't place in center cell when surrounded by buildings:**
|
|
||||||
- FIXED: Two-stage raycast system
|
|
||||||
- If direct ground raycast fails, system raycasts to any object then down to find ground
|
|
||||||
- Works even when all adjacent cells are built and blocking the ground view
|
|
||||||
- You can fill in gaps surrounded by buildings
|
|
||||||
|
|
||||||
**Network issues:**
|
|
||||||
- BuildingManager must be spawned on network
|
|
||||||
- Ensure player has BuildingPlacement component with IsOwner
|
|
||||||
|
|
||||||
## Extending the System
|
|
||||||
|
|
||||||
### Add Building Categories
|
|
||||||
Edit `BuildingData.cs` and add:
|
|
||||||
```csharp
|
|
||||||
public enum BuildingCategory { Housing, Production, Defense }
|
|
||||||
public BuildingCategory category;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Add Resource Costs
|
|
||||||
```csharp
|
|
||||||
[Header("Cost")]
|
|
||||||
public int woodCost;
|
|
||||||
public int stoneCost;
|
|
||||||
```
|
|
||||||
|
|
||||||
Then check resources in `BuildingManager.PlaceBuildingServerRpc()`
|
|
||||||
|
|
||||||
### Add Build Range Limit
|
|
||||||
In `BuildingPlacement.UpdatePreviewPosition()`:
|
|
||||||
```csharp
|
|
||||||
float distanceToPlayer = Vector3.Distance(transform.position, snappedPosition);
|
|
||||||
if (distanceToPlayer > maxBuildRange)
|
|
||||||
isValid = false;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Multiple Building Selection
|
|
||||||
Add UI buttons that change `selectedBuildingIndex` in BuildingPlacement
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- Buildings are spawned on the **server** via ServerRpc
|
|
||||||
- Preview is **local-only** (each player sees their own preview)
|
|
||||||
- Ground layer should include all walkable/buildable surfaces
|
|
||||||
- Grid size should match your game's scale (larger buildings = larger grid)
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
# Git Discord Notification Hook
|
|
||||||
|
|
||||||
Send commit notifications to Discord with Korean text support.
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
- **Node.js** (required for Korean text support)
|
|
||||||
- Download from: https://nodejs.org/
|
|
||||||
- Install the LTS version
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
1. **Open your project folder**
|
|
||||||
|
|
||||||
2. **Run the setup script:**
|
|
||||||
```powershell
|
|
||||||
.\setup-hooks.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Configure your Discord webhook URL:**
|
|
||||||
- Open `git-hooks/send-discord.js` in a text editor (Notepad, VS Code, etc.)
|
|
||||||
- Find line with `webhookUrl =`
|
|
||||||
- Replace the URL with your Discord webhook URL
|
|
||||||
- Save the file
|
|
||||||
- Run `.\setup-hooks.ps1` again to apply changes
|
|
||||||
|
|
||||||
**That's it!** Now every commit will send a notification to Discord.
|
|
||||||
|
|
||||||
## How to Get Discord Webhook URL
|
|
||||||
|
|
||||||
1. Open your Discord server settings
|
|
||||||
2. Go to **Integrations** → **Webhooks**
|
|
||||||
3. Click **New Webhook**
|
|
||||||
4. Copy the **Webhook URL**
|
|
||||||
5. Paste it into `git-hooks/send-discord.js`
|
|
||||||
|
|
||||||
## What Gets Sent to Discord
|
|
||||||
|
|
||||||
- Commit hash
|
|
||||||
- Author name
|
|
||||||
- Commit message (supports Korean/other languages)
|
|
||||||
- List of changed files
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
**Hook not running?**
|
|
||||||
- Make sure you ran `.\setup-hooks.ps1` after cloning the repo
|
|
||||||
- Check that `.git/hooks/post-commit` file exists
|
|
||||||
|
|
||||||
**Korean text showing as `???`?**
|
|
||||||
- Make sure Node.js is installed
|
|
||||||
- Run `node --version` in terminal to check
|
|
||||||
|
|
||||||
**Hook sending errors?**
|
|
||||||
- Verify your Discord webhook URL is correct
|
|
||||||
- Check that the webhook has permission to send messages
|
|
||||||
|
|
||||||
## Uninstalling
|
|
||||||
|
|
||||||
Run this to remove the hooks:
|
|
||||||
```bash
|
|
||||||
rm .git/hooks/post-commit .git/hooks/send-discord.js
|
|
||||||
```
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
# Generic CSV Importer System
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The CSV importer now works with **any data type**, not just monsters! Each data type has its own prefab setup logic.
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
```
|
|
||||||
CSV File (any type)
|
|
||||||
↓ Importer
|
|
||||||
ScriptableObject (data)
|
|
||||||
↓ Prefab Setup
|
|
||||||
Prefab (with components, models, etc.)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### 1. IPrefabSetup Interface
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public interface IPrefabSetup
|
|
||||||
{
|
|
||||||
string GetTemplateName(); // Which template to use
|
|
||||||
void SetupPrefab(GameObject prefab, ScriptableObject data); // How to setup
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Prefab Setup Handlers
|
|
||||||
|
|
||||||
Each data type has its own setup class:
|
|
||||||
|
|
||||||
**MonsterPrefabSetup.cs**
|
|
||||||
- Handles MonsterData
|
|
||||||
- Applies FBX models
|
|
||||||
- Sets Animator controllers
|
|
||||||
- Updates MonsterDataComponent
|
|
||||||
|
|
||||||
**To add new types:**
|
|
||||||
1. Create `NewTypePrefabSetup.cs` implementing `IPrefabSetup`
|
|
||||||
2. Register in `CSVToSOImporter.RegisterPrefabSetups()`
|
|
||||||
3. Done!
|
|
||||||
|
|
||||||
### 3. Templates
|
|
||||||
|
|
||||||
Each data type has its own template:
|
|
||||||
```
|
|
||||||
Assets/Data/Templates/
|
|
||||||
├── MonsterTemplate.prefab → For monsters
|
|
||||||
├── TowerTemplate.prefab → For towers (to create)
|
|
||||||
└── PlayerTemplate.prefab → For players (to create)
|
|
||||||
```
|
|
||||||
|
|
||||||
Templates define:
|
|
||||||
- Required components
|
|
||||||
- Component defaults
|
|
||||||
- Layer settings
|
|
||||||
- etc.
|
|
||||||
|
|
||||||
## Adding New Data Types
|
|
||||||
|
|
||||||
### Step 1: Create Template
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// TemplateCreator.cs
|
|
||||||
Tools > Data > Create [TypeName] Template
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Create Setup Handler
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// TowerPrefabSetup.cs
|
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace Northbound.Editor
|
|
||||||
{
|
|
||||||
public class TowerPrefabSetup : IPrefabSetup
|
|
||||||
{
|
|
||||||
public string GetTemplateName()
|
|
||||||
{
|
|
||||||
return "TowerTemplate";
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetupPrefab(GameObject prefab, ScriptableObject data)
|
|
||||||
{
|
|
||||||
// Your custom logic here
|
|
||||||
// e.g., add models, apply stats, etc.
|
|
||||||
Debug.Log($"[TowerPrefabSetup] Setting up {data.name}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Register Handler
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// CSVToSOImporter.cs - RegisterPrefabSetups()
|
|
||||||
prefabSetups["Tower"] = new TowerPrefabSetup();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Create CSV
|
|
||||||
|
|
||||||
```
|
|
||||||
GameData/Tower.csv
|
|
||||||
id,name,cost,...
|
|
||||||
101,ArrowTower,50,...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5: Import
|
|
||||||
|
|
||||||
```
|
|
||||||
Tools > Data > Import All CSV
|
|
||||||
```
|
|
||||||
|
|
||||||
Done! Prefabs auto-generated for Towers.
|
|
||||||
|
|
||||||
## Benefits
|
|
||||||
|
|
||||||
✅ **Generic**: Works for ANY data type
|
|
||||||
✅ **Extensible**: Easy to add new types
|
|
||||||
✅ **Type-Safe**: Each type has its own logic
|
|
||||||
✅ **Maintainable**: No monster-specific code in importer
|
|
||||||
✅ **Automated**: CSV → SO → Prefab in one click
|
|
||||||
|
|
||||||
## Example: Complete Flow for Monster
|
|
||||||
|
|
||||||
1. **Designer edits CSV**: `GameData/Monster.csv`
|
|
||||||
2. **Imports data**: `Tools > Data > Import All CSV`
|
|
||||||
3. **Importer**:
|
|
||||||
- Reads CSV rows
|
|
||||||
- Creates Monster101.asset (SO)
|
|
||||||
- Clones MonsterTemplate.prefab
|
|
||||||
- Calls MonsterPrefabSetup.SetupPrefab()
|
|
||||||
- Saves as Monster101.prefab
|
|
||||||
4. **MonsterPrefabSetup**:
|
|
||||||
- Links SO to MonsterDataComponent
|
|
||||||
- Applies FBX model
|
|
||||||
- Sets Animator controller
|
|
||||||
5. **Result**: Complete prefab ready to use!
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
**"No prefab setup found for [Type]"**
|
|
||||||
- Create `TypePrefabSetup.cs` implementing `IPrefabSetup`
|
|
||||||
- Register it in `RegisterPrefabSetups()`
|
|
||||||
|
|
||||||
**"No template found for [Type]"**
|
|
||||||
- Create `[Type]Template.prefab` using `Tools > Data > Create Template`
|
|
||||||
- Ensure name matches `GetTemplateName()` return value
|
|
||||||
|
|
||||||
**Prefabs not updating**
|
|
||||||
- Check that your setup logic is in `SetupPrefab()`
|
|
||||||
- Verify SO fields are being read correctly
|
|
||||||
@@ -1,283 +0,0 @@
|
|||||||
# Grid-Based Collision System
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
The building system uses **grid-based collision detection**, NOT the actual colliders on building prefabs.
|
|
||||||
|
|
||||||
### Grid Bounds vs Collider Bounds
|
|
||||||
|
|
||||||
```
|
|
||||||
❌ OLD WAY (Collider-based):
|
|
||||||
Building A has BoxCollider (2.5 x 3.7 x 4.2)
|
|
||||||
Building B has MeshCollider (complex shape)
|
|
||||||
→ Hard to predict, inconsistent placement
|
|
||||||
|
|
||||||
✅ NEW WAY (Grid-based):
|
|
||||||
Building A has width=2, length=3, height=2 in BuildingData
|
|
||||||
Building B has width=1, length=4, height=3 in BuildingData
|
|
||||||
→ Predictable, consistent grid placement
|
|
||||||
```
|
|
||||||
|
|
||||||
## Why Grid-Based?
|
|
||||||
|
|
||||||
### Advantages
|
|
||||||
1. **Predictable**: Buildings snap to grid, easy to understand
|
|
||||||
2. **Consistent**: Same rules for all buildings, regardless of visual model
|
|
||||||
3. **Flexible**: Visual model can be any size/shape
|
|
||||||
4. **Network-Friendly**: Simple data to sync (just grid position + rotation)
|
|
||||||
5. **Performance**: Fast AABB (Axis-Aligned Bounding Box) checks
|
|
||||||
|
|
||||||
### Comparison
|
|
||||||
|
|
||||||
| Aspect | Collider-Based | Grid-Based |
|
|
||||||
|--------|---------------|------------|
|
|
||||||
| Placement | Visual, complex shapes | Predictable grid cells |
|
|
||||||
| Performance | Slower (physics queries) | Faster (simple bounds check) |
|
|
||||||
| Network Sync | Complex mesh data | Simple int coordinates |
|
|
||||||
| Rotation | Complex recalculation | Swap width/length |
|
|
||||||
| Debugging | Hard to visualize | Easy (grid gizmos) |
|
|
||||||
|
|
||||||
## How Grid Size Works
|
|
||||||
|
|
||||||
### BuildingData Settings
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
[Header("Grid Size")]
|
|
||||||
public int width = 2; // X-axis grid cells
|
|
||||||
public int length = 3; // Z-axis grid cells
|
|
||||||
public float height = 2f; // Y-axis size (can be float)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Rotation Effect
|
|
||||||
|
|
||||||
```
|
|
||||||
Building: width=2, length=3
|
|
||||||
|
|
||||||
Rotation 0° (0): Rotation 90° (1):
|
|
||||||
┌─────┐ ┌───────┐
|
|
||||||
│ 2 │ │ 3 │
|
|
||||||
│ x │ │ x │
|
|
||||||
│ 3 │ └───────┘
|
|
||||||
└─────┘ 2
|
|
||||||
|
|
||||||
Rotation 180° (2): Rotation 270° (3):
|
|
||||||
┌─────┐ ┌───────┐
|
|
||||||
│ 3 │ │ 2 │
|
|
||||||
│ x │ │ x │
|
|
||||||
│ 2 │ └───────┘
|
|
||||||
└─────┘ 3
|
|
||||||
```
|
|
||||||
|
|
||||||
The `GetSize(rotation)` method automatically swaps width/length for 90° and 270° rotations.
|
|
||||||
|
|
||||||
## Visual Examples
|
|
||||||
|
|
||||||
### Example 1: Small House
|
|
||||||
```yaml
|
|
||||||
BuildingData:
|
|
||||||
width: 2
|
|
||||||
length: 2
|
|
||||||
height: 3
|
|
||||||
|
|
||||||
Grid Footprint (top view):
|
|
||||||
┌─┬─┐
|
|
||||||
├─┼─┤ Occupies 4 grid cells (2x2)
|
|
||||||
└─┴─┘
|
|
||||||
|
|
||||||
Visual Model: Can be any size!
|
|
||||||
- Model might be 1.8 x 1.8 (smaller than grid)
|
|
||||||
- Model might be 2.3 x 2.3 (larger than grid)
|
|
||||||
- Collision still uses 2x2 grid
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example 2: Wall Segment
|
|
||||||
```yaml
|
|
||||||
BuildingData:
|
|
||||||
width: 1
|
|
||||||
length: 4
|
|
||||||
height: 2
|
|
||||||
|
|
||||||
Grid Footprint:
|
|
||||||
┌───────────┐
|
|
||||||
│ 1 x 4 │ Occupies 4 grid cells (1x4)
|
|
||||||
└───────────┘
|
|
||||||
|
|
||||||
When rotated 90°:
|
|
||||||
┌─┐
|
|
||||||
├─┤
|
|
||||||
├─┤ Becomes 4x1
|
|
||||||
├─┤
|
|
||||||
└─┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example 3: Large Building
|
|
||||||
```yaml
|
|
||||||
BuildingData:
|
|
||||||
width: 4
|
|
||||||
length: 5
|
|
||||||
height: 4
|
|
||||||
|
|
||||||
Grid Footprint:
|
|
||||||
┌─┬─┬─┬─┬─┐
|
|
||||||
├─┼─┼─┼─┼─┤
|
|
||||||
├─┼─┼─┼─┼─┤ Occupies 20 grid cells (4x5)
|
|
||||||
├─┼─┼─┼─┼─┤
|
|
||||||
└─┴─┴─┴─┴─┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## Collision Detection Code
|
|
||||||
|
|
||||||
### IsValidPlacement (BuildingManager.cs)
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// Get grid size from BuildingData (NOT collider)
|
|
||||||
Vector3 gridSize = data.GetSize(rotation);
|
|
||||||
|
|
||||||
// Shrink bounds slightly to allow adjacent placement
|
|
||||||
// Without this, Bounds.Intersects() returns true for touching bounds
|
|
||||||
Vector3 shrunkSize = gridSize - Vector3.one * 0.01f;
|
|
||||||
Bounds checkBounds = new Bounds(
|
|
||||||
groundPosition + Vector3.up * gridSize.y * 0.5f,
|
|
||||||
shrunkSize
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check against all placed buildings' GRID bounds
|
|
||||||
foreach (var building in placedBuildings)
|
|
||||||
{
|
|
||||||
Bounds buildingGridBounds = building.GetGridBounds();
|
|
||||||
if (checkBounds.Intersects(buildingGridBounds))
|
|
||||||
return false; // Overlap detected
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why shrink by 0.01f?**
|
|
||||||
Unity's `Bounds.Intersects()` returns `true` when bounds are touching (edge-to-edge), not just overlapping. By shrinking the bounds by a tiny amount (0.01 units), we allow buildings to be placed directly adjacent without triggering a false overlap detection.
|
|
||||||
|
|
||||||
### GetGridBounds (Building.cs)
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
public Bounds GetGridBounds()
|
|
||||||
{
|
|
||||||
// Uses BuildingData size, not collider
|
|
||||||
Vector3 gridSize = buildingData.GetSize(rotation);
|
|
||||||
|
|
||||||
// Shrink slightly to allow adjacent buildings
|
|
||||||
Vector3 shrunkSize = gridSize - Vector3.one * 0.01f;
|
|
||||||
return new Bounds(
|
|
||||||
transform.position + Vector3.up * gridSize.y * 0.5f,
|
|
||||||
shrunkSize
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** The 0.01f shrink is tiny and not visible, but prevents false overlap detection for adjacent buildings.
|
|
||||||
|
|
||||||
## Visualizing Grid Bounds
|
|
||||||
|
|
||||||
### In Scene View
|
|
||||||
|
|
||||||
1. **Enter Build Mode** (Press B)
|
|
||||||
2. **Look at Scene View** (not Game view)
|
|
||||||
3. You'll see:
|
|
||||||
- **Green/Red Wire Cube**: Preview's grid bounds
|
|
||||||
- **Yellow Grid Cells**: Individual cells
|
|
||||||
- **Cyan Wire Cubes**: Placed buildings' grid bounds
|
|
||||||
|
|
||||||
### Debug Settings
|
|
||||||
|
|
||||||
**BuildingPlacement:**
|
|
||||||
- `Show Grid Bounds` - Shows preview grid visualization
|
|
||||||
|
|
||||||
**Building:**
|
|
||||||
- `Show Grid Bounds` - Shows cyan wire cube for this building
|
|
||||||
- `Grid Bounds Color` - Customize visualization color
|
|
||||||
|
|
||||||
### Example Scene
|
|
||||||
|
|
||||||
```
|
|
||||||
Scene View:
|
|
||||||
Preview (Green)
|
|
||||||
┌─────┐
|
|
||||||
│ 2x3 │
|
|
||||||
└─────┘
|
|
||||||
Placed Building (Cyan)
|
|
||||||
┌───┐
|
|
||||||
│1x2│
|
|
||||||
└───┘
|
|
||||||
|
|
||||||
Grid Cells (Yellow):
|
|
||||||
┌─┬─┬─┬─┬─┬─┐
|
|
||||||
├─┼─┼─┼─┼─┼─┤
|
|
||||||
└─┴─┴─┴─┴─┴─┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### 1. Match Grid Size to Gameplay
|
|
||||||
```csharp
|
|
||||||
// Small decorations
|
|
||||||
width: 1, length: 1, height: 1
|
|
||||||
|
|
||||||
// Medium buildings
|
|
||||||
width: 2-3, length: 2-3, height: 2-3
|
|
||||||
|
|
||||||
// Large buildings
|
|
||||||
width: 4-6, length: 4-6, height: 3-5
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Visual Model Can Differ
|
|
||||||
The visual model doesn't need to match grid size exactly:
|
|
||||||
- Overhang/decorations can extend beyond grid
|
|
||||||
- Model can be smaller, leaving empty grid space
|
|
||||||
- Use `placementOffset` to adjust visual alignment
|
|
||||||
|
|
||||||
### 3. Height Consideration
|
|
||||||
```csharp
|
|
||||||
height: 2f // Building is 2 units tall
|
|
||||||
```
|
|
||||||
- Used for vertical bounds checking
|
|
||||||
- Prevents buildings on top of each other
|
|
||||||
- Can be float (e.g., 2.5f for tall buildings)
|
|
||||||
|
|
||||||
### 4. Colliders Still Needed
|
|
||||||
Building prefabs should still have colliders for:
|
|
||||||
- Player/entity collision during gameplay
|
|
||||||
- Physics interactions
|
|
||||||
- Raycast detection
|
|
||||||
|
|
||||||
But these are NOT used for placement validation!
|
|
||||||
|
|
||||||
## Common Questions
|
|
||||||
|
|
||||||
**Q: Why is my small model blocking a large area?**
|
|
||||||
A: Check the `width` and `length` in BuildingData. The grid size determines collision, not the visual model.
|
|
||||||
|
|
||||||
**Q: Buildings overlap visually but can't place?**
|
|
||||||
A: Grid bounds are overlapping. Check Scene view with "Show Grid Bounds" enabled.
|
|
||||||
|
|
||||||
**Q: How do I make a building take less space?**
|
|
||||||
A: Reduce `width` and `length` in BuildingData. The visual model size doesn't matter.
|
|
||||||
|
|
||||||
**Q: Can I use fractional grid sizes?**
|
|
||||||
A: `width` and `length` are integers (whole grid cells). Only `height` can be a float.
|
|
||||||
|
|
||||||
**Q: What if my model is rotated strangely?**
|
|
||||||
A: Grid rotation is separate from model rotation. Grid always aligns to world axes. Adjust model's prefab rotation or use `placementOffset`.
|
|
||||||
|
|
||||||
**Q: Can buildings be placed directly next to each other with no gap?**
|
|
||||||
A: Yes! The system shrinks bounds by 0.01f to allow adjacent placement. Buildings can touch edge-to-edge.
|
|
||||||
|
|
||||||
**Q: I want a gap between buildings. How?**
|
|
||||||
A: Increase the `width` and `length` values in BuildingData by 1. For example, change a 2x2 building to 3x3 to add a 1-cell buffer zone.
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
✅ **Use BuildingData grid size** for collision
|
|
||||||
✅ **Visual model can be any size**
|
|
||||||
✅ **Predictable, grid-aligned placement**
|
|
||||||
✅ **Easy debugging with gizmos**
|
|
||||||
✅ **Fast performance**
|
|
||||||
|
|
||||||
❌ **Don't rely on collider size**
|
|
||||||
❌ **Don't expect pixel-perfect visual collision**
|
|
||||||
❌ **Don't use colliders for placement validation**
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
# Building System Input Actions Setup
|
|
||||||
|
|
||||||
## Required Input Actions
|
|
||||||
|
|
||||||
Add these three actions to your `InputSystem_Actions.inputactions` file in the **Player** action map:
|
|
||||||
|
|
||||||
### 1. ToggleBuildMode (Button)
|
|
||||||
- **Action Type**: Button
|
|
||||||
- **Control Type**: Button
|
|
||||||
- **Binding**: Keyboard B
|
|
||||||
|
|
||||||
### 2. Rotate (Value)
|
|
||||||
- **Action Type**: Value
|
|
||||||
- **Control Type**: Axis
|
|
||||||
- **Binding**: Keyboard R (positive value)
|
|
||||||
- Alternative: You can use a 1D Axis composite with:
|
|
||||||
- Negative: Q
|
|
||||||
- Positive: E
|
|
||||||
|
|
||||||
### 3. Build (Button)
|
|
||||||
- **Action Type**: Button
|
|
||||||
- **Control Type**: Button
|
|
||||||
- **Binding**: Mouse Left Button
|
|
||||||
|
|
||||||
## Step-by-Step Setup in Unity
|
|
||||||
|
|
||||||
1. **Open Input Actions Asset**
|
|
||||||
- Navigate to `Assets/InputSystem_Actions.inputactions`
|
|
||||||
- Double-click to open the Input Actions window
|
|
||||||
|
|
||||||
2. **Select Player Action Map**
|
|
||||||
- In the left panel, click on "Player" action map
|
|
||||||
|
|
||||||
3. **Add ToggleBuildMode Action**
|
|
||||||
- Click the "+" button in the Actions column
|
|
||||||
- Name it: `ToggleBuildMode`
|
|
||||||
- Action Type: Button
|
|
||||||
- Add binding: Keyboard → B
|
|
||||||
|
|
||||||
4. **Add Rotate Action**
|
|
||||||
- Click the "+" button in the Actions column
|
|
||||||
- Name it: `Rotate`
|
|
||||||
- Action Type: Value
|
|
||||||
- Control Type: Axis
|
|
||||||
|
|
||||||
**Option A (Simple - Single key):**
|
|
||||||
- Add binding: Keyboard → R
|
|
||||||
|
|
||||||
**Option B (Advanced - Q/E rotation):**
|
|
||||||
- Right-click → Add 1D Axis Composite
|
|
||||||
- Negative: Keyboard → Q
|
|
||||||
- Positive: Keyboard → E
|
|
||||||
|
|
||||||
5. **Add Build Action**
|
|
||||||
- Click the "+" button in the Actions column
|
|
||||||
- Name it: `Build`
|
|
||||||
- Action Type: Button
|
|
||||||
- Add binding: Mouse → Left Button
|
|
||||||
|
|
||||||
6. **Save and Regenerate**
|
|
||||||
- Click "Save Asset" button
|
|
||||||
- The `InputSystem_Actions.cs` script will auto-regenerate
|
|
||||||
|
|
||||||
## How It Works in Code
|
|
||||||
|
|
||||||
The BuildingPlacement script subscribes to these actions:
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// Setup in OnNetworkSpawn
|
|
||||||
_inputActions.Player.ToggleBuildMode.performed += OnToggleBuildMode;
|
|
||||||
_inputActions.Player.Rotate.performed += OnRotate;
|
|
||||||
_inputActions.Player.Build.performed += OnBuild;
|
|
||||||
|
|
||||||
// Cleanup in OnNetworkDespawn
|
|
||||||
_inputActions.Player.ToggleBuildMode.performed -= OnToggleBuildMode;
|
|
||||||
_inputActions.Player.Rotate.performed -= OnRotate;
|
|
||||||
_inputActions.Player.Build.performed -= OnBuild;
|
|
||||||
```
|
|
||||||
|
|
||||||
### ToggleBuildMode
|
|
||||||
- Triggers when B is pressed
|
|
||||||
- Toggles build mode on/off
|
|
||||||
- Shows/hides building preview
|
|
||||||
|
|
||||||
### Rotate
|
|
||||||
- Triggers when R is pressed (or Q/E if using composite)
|
|
||||||
- Reads value: positive = rotate right, negative = rotate left
|
|
||||||
- Rotates building preview by 90°
|
|
||||||
|
|
||||||
### Build
|
|
||||||
- Triggers when left mouse button is pressed
|
|
||||||
- Only works when build mode is active
|
|
||||||
- Places the building at the preview position
|
|
||||||
|
|
||||||
## Customizing Controls
|
|
||||||
|
|
||||||
To change the key bindings:
|
|
||||||
1. Open `InputSystem_Actions.inputactions`
|
|
||||||
2. Click on the binding you want to change
|
|
||||||
3. Click "Path" dropdown
|
|
||||||
4. Select new key/button
|
|
||||||
5. Save Asset
|
|
||||||
|
|
||||||
No code changes needed - the script uses the action names, not specific keys!
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
After setup:
|
|
||||||
1. Enter Play mode
|
|
||||||
2. Press **B** → Should see "Entered Build Mode" in console
|
|
||||||
3. Press **R** → Building preview should rotate
|
|
||||||
4. Click **Left Mouse Button** → Should place building (if valid position)
|
|
||||||
5. Press **B** again → Should exit build mode
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
**Error: "ToggleBuildMode does not exist"**
|
|
||||||
- Make sure you named the action exactly `ToggleBuildMode` (case-sensitive)
|
|
||||||
- Save the Input Actions asset to regenerate the code
|
|
||||||
|
|
||||||
**Rotation doesn't work:**
|
|
||||||
- Check the action name is exactly `Rotate`
|
|
||||||
- If using composite, make sure it's a 1D Axis composite
|
|
||||||
- Verify the action type is "Value" not "Button"
|
|
||||||
|
|
||||||
**Build action triggers during movement:**
|
|
||||||
- Make sure Build action is mapped to Mouse Left Button, not Attack
|
|
||||||
- Check that Build mode is active (press B first)
|
|
||||||
1240
Northbound.Game.csproj
Normal file
1240
Northbound.Game.csproj
Normal file
File diff suppressed because it is too large
Load Diff
64
Packages/manifest.json.backup
Normal file
64
Packages/manifest.json.backup
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"com.unity.2d.animation": "13.0.4",
|
||||||
|
"com.unity.2d.aseprite": "3.0.1",
|
||||||
|
"com.unity.2d.psdimporter": "12.0.1",
|
||||||
|
"com.unity.2d.sprite": "1.0.0",
|
||||||
|
"com.unity.2d.spriteshape": "13.0.0",
|
||||||
|
"com.unity.2d.tilemap": "1.0.0",
|
||||||
|
"com.unity.2d.tilemap.extras": "6.0.1",
|
||||||
|
"com.unity.2d.tooling": "1.0.0",
|
||||||
|
"com.unity.ai.navigation": "2.0.9",
|
||||||
|
"com.unity.cinemachine": "3.1.5",
|
||||||
|
"com.unity.collab-proxy": "2.11.2",
|
||||||
|
"com.unity.ide.rider": "3.0.38",
|
||||||
|
"com.unity.ide.visualstudio": "2.0.26",
|
||||||
|
"com.unity.inputsystem": "1.17.0",
|
||||||
|
"com.unity.multiplayer.center": "1.0.1",
|
||||||
|
"com.unity.multiplayer.center.quickstart": "1.1.1",
|
||||||
|
"com.unity.multiplayer.playmode": "2.0.1",
|
||||||
|
"com.unity.multiplayer.tools": "2.2.7",
|
||||||
|
"com.unity.netcode.gameobjects": "2.8.1",
|
||||||
|
"com.unity.render-pipelines.universal": "17.3.0",
|
||||||
|
"com.unity.services.multiplayer": "2.1.1",
|
||||||
|
"com.unity.services.vivox": "16.9.0",
|
||||||
|
"com.unity.test-framework": "1.6.0",
|
||||||
|
"com.unity.timeline": "1.8.10",
|
||||||
|
"com.unity.ugui": "2.0.0",
|
||||||
|
"com.unity.visualscripting": "1.9.9",
|
||||||
|
"com.unity.modules.accessibility": "1.0.0",
|
||||||
|
"com.unity.modules.adaptiveperformance": "1.0.0",
|
||||||
|
"com.unity.modules.ai": "1.0.0",
|
||||||
|
"com.unity.modules.androidjni": "1.0.0",
|
||||||
|
"com.unity.modules.animation": "1.0.0",
|
||||||
|
"com.unity.modules.assetbundle": "1.0.0",
|
||||||
|
"com.unity.modules.audio": "1.0.0",
|
||||||
|
"com.unity.modules.cloth": "1.0.0",
|
||||||
|
"com.unity.modules.director": "1.0.0",
|
||||||
|
"com.unity.modules.imageconversion": "1.0.0",
|
||||||
|
"com.unity.modules.imgui": "1.0.0",
|
||||||
|
"com.unity.modules.jsonserialize": "1.0.0",
|
||||||
|
"com.unity.modules.particlesystem": "1.0.0",
|
||||||
|
"com.unity.modules.physics": "1.0.0",
|
||||||
|
"com.unity.modules.physics2d": "1.0.0",
|
||||||
|
"com.unity.modules.screencapture": "1.0.0",
|
||||||
|
"com.unity.modules.terrain": "1.0.0",
|
||||||
|
"com.unity.modules.terrainphysics": "1.0.0",
|
||||||
|
"com.unity.modules.tilemap": "1.0.0",
|
||||||
|
"com.unity.modules.ui": "1.0.0",
|
||||||
|
"com.unity.modules.uielements": "1.0.0",
|
||||||
|
"com.unity.modules.umbra": "1.0.0",
|
||||||
|
"com.unity.modules.unityanalytics": "1.0.0",
|
||||||
|
"com.unity.modules.unitywebrequest": "1.0.0",
|
||||||
|
"com.unity.modules.unitywebrequestassetbundle": "1.0.0",
|
||||||
|
"com.unity.modules.unitywebrequestaudio": "1.0.0",
|
||||||
|
"com.unity.modules.unitywebrequesttexture": "1.0.0",
|
||||||
|
"com.unity.modules.unitywebrequestwww": "1.0.0",
|
||||||
|
"com.unity.modules.vectorgraphics": "1.0.0",
|
||||||
|
"com.unity.modules.vehicles": "1.0.0",
|
||||||
|
"com.unity.modules.video": "1.0.0",
|
||||||
|
"com.unity.modules.vr": "1.0.0",
|
||||||
|
"com.unity.modules.wind": "1.0.0",
|
||||||
|
"com.unity.modules.xr": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -144,7 +144,8 @@ PlayerSettings:
|
|||||||
visionOSBundleVersion: 1.0
|
visionOSBundleVersion: 1.0
|
||||||
tvOSBundleVersion: 1.0
|
tvOSBundleVersion: 1.0
|
||||||
bundleVersion: 1.0
|
bundleVersion: 1.0
|
||||||
preloadedAssets: []
|
preloadedAssets:
|
||||||
|
- {fileID: -944628639613478452, guid: 2bcd2660ca9b64942af0de543d8d7100, type: 3}
|
||||||
metroInputSource: 0
|
metroInputSource: 0
|
||||||
wsaTransparentSwapchain: 0
|
wsaTransparentSwapchain: 0
|
||||||
m_HolographicPauseOnTrackingLoss: 1
|
m_HolographicPauseOnTrackingLoss: 1
|
||||||
@@ -296,7 +297,10 @@ PlayerSettings:
|
|||||||
androidSymbolsSizeThreshold: 800
|
androidSymbolsSizeThreshold: 800
|
||||||
m_BuildTargetIcons: []
|
m_BuildTargetIcons: []
|
||||||
m_BuildTargetPlatformIcons: []
|
m_BuildTargetPlatformIcons: []
|
||||||
m_BuildTargetBatching: []
|
m_BuildTargetBatching:
|
||||||
|
- m_BuildTarget: Standalone
|
||||||
|
m_StaticBatching: 1
|
||||||
|
m_DynamicBatching: 1
|
||||||
m_BuildTargetShaderSettings: []
|
m_BuildTargetShaderSettings: []
|
||||||
m_BuildTargetGraphicsJobs: []
|
m_BuildTargetGraphicsJobs: []
|
||||||
m_BuildTargetGraphicsJobMode: []
|
m_BuildTargetGraphicsJobMode: []
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
UnityConnectSettings:
|
UnityConnectSettings:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
serializedVersion: 1
|
serializedVersion: 1
|
||||||
m_Enabled: 0
|
m_Enabled: 1
|
||||||
m_TestMode: 0
|
m_TestMode: 0
|
||||||
m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events
|
m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events
|
||||||
m_EventUrl: https://cdp.cloud.unity3d.com/v1/events
|
m_EventUrl: https://cdp.cloud.unity3d.com/v1/events
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
# Tower CSV Importer Guide
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
The tower CSV importer allows you to create tower prefabs from CSV data in one step, similar to the monster importer.
|
|
||||||
|
|
||||||
## Files Created
|
|
||||||
1. **TowerDataComponent.cs** - Component that applies TowerData to a GameObject
|
|
||||||
2. **TowerPrefabSetup.cs** - Implementation of IPrefabSetup for tower prefab generation
|
|
||||||
3. **CSVToSOImporter.cs** - Generic CSV importer that works with any data type
|
|
||||||
|
|
||||||
## How to Use
|
|
||||||
|
|
||||||
### Step 1: Ensure Tower Template Exists
|
|
||||||
If you haven't created the TowerTemplate yet:
|
|
||||||
1. In Unity, go to `Tools > Data > Create Tower Template`
|
|
||||||
2. This creates `Assets/Data/Templates/TowerTemplate.prefab` with all required components:
|
|
||||||
- NetworkObject
|
|
||||||
- Building
|
|
||||||
- TowerDataComponent
|
|
||||||
- MeshFilter & MeshRenderer
|
|
||||||
- BoxCollider
|
|
||||||
- NavMeshObstacle
|
|
||||||
|
|
||||||
### Step 2: Update Tower.csv
|
|
||||||
Edit `GameData/Tower.csv` with your tower data. The CSV uses camelCase field names matching TowerData class:
|
|
||||||
|
|
||||||
```csv
|
|
||||||
id,memo,mana,manpower,sizeX,sizeY,maxHp,atkRange,atkDamage,atkIntervalSec,modelPath
|
|
||||||
1,타워,25,10,3,3,50,10,5,2,Assets/Meshes/building_tower_B_blue.fbx
|
|
||||||
2,벽,5,5,1,1,30,0,0,0,Assets/Meshes/building_tower_B_blue.fbx
|
|
||||||
```
|
|
||||||
|
|
||||||
**Field Descriptions:**
|
|
||||||
- `id`: Unique tower ID
|
|
||||||
- `memo`: Tower name/description
|
|
||||||
- `mana`: Mana cost to build
|
|
||||||
- `manpower`: Construction work required
|
|
||||||
- `sizeX`: Grid width
|
|
||||||
- `sizeY`: Grid length
|
|
||||||
- `maxHp`: Maximum health
|
|
||||||
- `atkRange`: Attack range
|
|
||||||
- `atkDamage`: Attack damage
|
|
||||||
- `atkIntervalSec`: Attack interval in seconds
|
|
||||||
- `modelPath`: Path to FBX model or mesh asset
|
|
||||||
|
|
||||||
### Step 3: Import CSV
|
|
||||||
In Unity, go to `Tools > Data > Import All CSV`
|
|
||||||
|
|
||||||
The importer will:
|
|
||||||
1. Create ScriptableObject files in `Assets/Data/ScriptableObjects/Tower/`
|
|
||||||
2. Create/Update prefabs in `Assets/Prefabs/Tower/`
|
|
||||||
3. Apply models from `modelPath` column
|
|
||||||
4. Configure components based on CSV data
|
|
||||||
5. Link TowerData to TowerDataComponent
|
|
||||||
|
|
||||||
### Step 4: Use Tower Prefabs
|
|
||||||
Your generated tower prefabs are ready to use in the BuildingManager system!
|
|
||||||
|
|
||||||
## File Structure After Import
|
|
||||||
```
|
|
||||||
Assets/
|
|
||||||
├── Data/
|
|
||||||
│ ├── ScriptableObjects/
|
|
||||||
│ │ └── Tower/ # SO files (generated from CSV)
|
|
||||||
│ │ ├── Tower1.asset
|
|
||||||
│ │ └── Tower2.asset
|
|
||||||
│ └── Templates/ # Template prefabs (created once)
|
|
||||||
│ └── TowerTemplate.prefab
|
|
||||||
├── Prefabs/
|
|
||||||
│ └── Tower/ # Generated tower prefabs
|
|
||||||
│ ├── Tower1.prefab
|
|
||||||
│ └── Tower2.prefab
|
|
||||||
└── GameData/
|
|
||||||
└── Tower.csv # Source data (editable)
|
|
||||||
```
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
### CSV Import Pipeline
|
|
||||||
```
|
|
||||||
Tower.csv (CSV data)
|
|
||||||
↓ CSVToSOImporter
|
|
||||||
TowerData (ScriptableObject)
|
|
||||||
↓ TowerPrefabSetup
|
|
||||||
Tower.prefab (GameObject with components)
|
|
||||||
```
|
|
||||||
|
|
||||||
### TowerPrefabSetup Logic
|
|
||||||
1. **TowerDataComponent**: Links TowerData SO to the prefab
|
|
||||||
2. **Model Application**: Loads and applies FBX model from modelPath
|
|
||||||
3. **Collider Setup**: Creates BoxCollider sized to tower dimensions
|
|
||||||
4. **Building Integration**: Converts TowerData to BuildingData for Building component
|
|
||||||
|
|
||||||
## Benefits
|
|
||||||
- ✅ **One-click import**: Import all towers from CSV at once
|
|
||||||
- ✅ **Consistent structure**: All towers have the same components
|
|
||||||
- ✅ **Easy updates**: Edit CSV and re-import to update all towers
|
|
||||||
- ✅ **Designer-friendly**: Non-programmers can add new towers
|
|
||||||
- ✅ **Type-safe**: TowerData class ensures data integrity
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
**"No prefab setup found for Tower"**
|
|
||||||
- Make sure TowerPrefabSetup.cs is in the Assets/Scripts/Editor folder
|
|
||||||
- Restart Unity Editor to ensure the script is compiled
|
|
||||||
|
|
||||||
**"Template not found"**
|
|
||||||
- Run `Tools > Data > Create Tower Template` first
|
|
||||||
- Verify TowerTemplate.prefab exists in Assets/Data/Templates/
|
|
||||||
|
|
||||||
**Prefabs not created**
|
|
||||||
- Check the Console for error messages
|
|
||||||
- Verify Tower.csv exists in GameData folder
|
|
||||||
- Ensure modelPath in CSV points to valid assets
|
|
||||||
|
|
||||||
**Model not showing**
|
|
||||||
- Verify modelPath in CSV is correct
|
|
||||||
- Check that the FBX file exists at the specified path
|
|
||||||
- Ensure the model has valid materials assigned
|
|
||||||
|
|
||||||
## Customization
|
|
||||||
|
|
||||||
### Adding New Components to All Towers
|
|
||||||
1. Open `Assets/Data/Templates/TowerTemplate.prefab`
|
|
||||||
2. Add the component
|
|
||||||
3. Configure defaults
|
|
||||||
4. Save template
|
|
||||||
5. Re-import CSV to apply changes to new towers
|
|
||||||
|
|
||||||
### Modifying Existing Towers
|
|
||||||
- Edit the prefab directly (changes persist on next import)
|
|
||||||
- OR modify Tower.csv and re-import (will update SO link and model)
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
- TowerDataComponent converts TowerData to BuildingData for compatibility with the existing Building system
|
|
||||||
- The importer creates prefabs if they don't exist, or updates existing ones
|
|
||||||
- Prefab edits (other than SO and model) are preserved on re-import
|
|
||||||
- CSV field names must match TowerData class property names exactly (case-sensitive, camelCase)
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
# Tower Quickslot Setup Guide
|
|
||||||
|
|
||||||
## 🎯 ULTRA-SIMPLE 1-CLICK SETUP (For Non-Programmers!)
|
|
||||||
|
|
||||||
### Just ONE Step!
|
|
||||||
|
|
||||||
Edit your `GameData/Tower.csv` file with tower data, then in Unity:
|
|
||||||
|
|
||||||
```
|
|
||||||
Tools > Data > Import All CSV
|
|
||||||
```
|
|
||||||
|
|
||||||
**THAT'S IT!** This automatically does everything:
|
|
||||||
- ✅ Creates TowerData from CSV (TowerData now extends BuildingData!)
|
|
||||||
- ✅ Creates Tower prefabs with TowerDataComponent
|
|
||||||
- ✅ **Auto-configures BuildingManager with all TowerData!**
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
Press **B** in-game → All towers appear in quickslot! 🎉
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 Adding New Towers
|
|
||||||
|
|
||||||
1. Add row to `GameData/Tower.csv`
|
|
||||||
2. Run `Tools > Data > Import All CSV`
|
|
||||||
3. Done! 🚀
|
|
||||||
|
|
||||||
Everything is automatic!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 Troubleshooting
|
|
||||||
|
|
||||||
### Error: "BuildingManager not found in scene"
|
|
||||||
|
|
||||||
**Cause:** Your scene doesn't have a GameObject with BuildingManager component.
|
|
||||||
|
|
||||||
**Fix:**
|
|
||||||
1. Create a new GameObject (e.g., "BuildingManager")
|
|
||||||
2. Add BuildingManager component to it
|
|
||||||
3. Run `Tools > Data > Import All CSV` again
|
|
||||||
4. Done!
|
|
||||||
|
|
||||||
### Error: "MissingReferenceException: prefab doesn't exist"
|
|
||||||
|
|
||||||
**Cause:** TowerData doesn't have prefab reference.
|
|
||||||
|
|
||||||
**Fix:**
|
|
||||||
1. Run `Northbound > Diagnose Tower System`
|
|
||||||
2. Check which TowerData shows `✗ MISSING PREFAB`
|
|
||||||
3. Run `Tools > Data > Import All CSV` to fix
|
|
||||||
4. Done!
|
|
||||||
|
|
||||||
### No towers appear in quickslot
|
|
||||||
|
|
||||||
**Cause:** BuildingManager wasn't found or configured.
|
|
||||||
|
|
||||||
**Fix:**
|
|
||||||
1. Run `Northbound > Diagnose Tower System`
|
|
||||||
2. Check if BuildingManager is found and has TowerData in list
|
|
||||||
3. If not, add BuildingManager to your scene
|
|
||||||
4. Run `Tools > Data > Import All CSV`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎮 Menu Commands
|
|
||||||
|
|
||||||
| Command | What it does | When to use |
|
|
||||||
|---------|--------------|--------------|
|
|
||||||
| `Tools > Data > Import All CSV` | **ONE-CLICK SETUP** - Import all CSV files, create prefabs, auto-configure BuildingManager with TowerData | After editing Tower.csv or anytime you want to update towers |
|
|
||||||
| `Northbound > Diagnose Tower System` | Check tower system health and find issues | When something doesn't work |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Why This is Awesome
|
|
||||||
|
|
||||||
- ✅ **ONE CLICK** - Edit CSV, run importer, done!
|
|
||||||
- ✅ **One folder for towers** - `Assets/Prefabs/Tower/` only
|
|
||||||
- ✅ **No duplicates** - No manual copying anywhere
|
|
||||||
- ✅ **Fully automatic** - BuildingManager configured automatically
|
|
||||||
- ✅ **Non-programmer friendly** - Just edit CSV and click one button!
|
|
||||||
- ✅ **Works in builds** - Everything is pre-populated
|
|
||||||
- ✅ **Instant testing** - Press B and see all towers!
|
|
||||||
- ✅ **Unified data structure** - TowerData extends BuildingData, no redundant data!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎉 Complete Workflow for New Teammates
|
|
||||||
|
|
||||||
1. Open Unity project
|
|
||||||
2. Add BuildingManager GameObject to scene (once)
|
|
||||||
3. Edit `GameData/Tower.csv` with tower data
|
|
||||||
4. Run `Tools > Data > Import All CSV`
|
|
||||||
5. Play game, press **B**, build towers! 🏗️
|
|
||||||
|
|
||||||
**That's literally it! No manual configuration needed!**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏗️ Technical Details
|
|
||||||
|
|
||||||
### Unified Data Structure
|
|
||||||
|
|
||||||
**Before:** Two separate data systems
|
|
||||||
- TowerData (CSV, tower-specific stats)
|
|
||||||
- BuildingData (auto-generated, building stats)
|
|
||||||
|
|
||||||
**Now:** Single unified system
|
|
||||||
- TowerData extends BuildingData
|
|
||||||
- TowerData has all tower-specific fields (atkRange, atkDamage, etc.)
|
|
||||||
- TowerData automatically maps to BuildingData fields (width, length, maxHealth, etc.)
|
|
||||||
- BuildingManager uses TowerData directly (no conversion needed!)
|
|
||||||
|
|
||||||
### Benefits of Unified Structure
|
|
||||||
- ✅ No duplicate data
|
|
||||||
- ✅ Single source of truth
|
|
||||||
- ✅ Cleaner code
|
|
||||||
- ✅ Easier to maintain
|
|
||||||
- ✅ Tower prefabs can be used directly without intermediate BuildingData files
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
# Building Ghost Preview Troubleshooting
|
|
||||||
|
|
||||||
## FIXED: Preview Activated But Not Visible
|
|
||||||
|
|
||||||
If console shows "Preview activated" but you can't see it, this was a **material/transparency issue**.
|
|
||||||
|
|
||||||
**Solution implemented:**
|
|
||||||
- Better material creation supporting both URP and Standard RP
|
|
||||||
- Proper transparency setup for ghost materials
|
|
||||||
- Multiple material slots handled correctly
|
|
||||||
- Auto-creates debug cube if prefab has no renderer
|
|
||||||
|
|
||||||
**Test the fix:**
|
|
||||||
1. Press Play
|
|
||||||
2. Press B to enter build mode
|
|
||||||
3. Move mouse over ground
|
|
||||||
4. You should now see a **green transparent ghost** of your building
|
|
||||||
5. Invalid positions show **red transparent ghost**
|
|
||||||
|
|
||||||
If still not visible after update, continue with checklist below.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Checklist
|
|
||||||
|
|
||||||
Run through these checks in order:
|
|
||||||
|
|
||||||
### 1. Check Console Logs
|
|
||||||
Press B to enter build mode and look for these messages:
|
|
||||||
- ✅ "Entered Build Mode"
|
|
||||||
- ✅ "[BuildingPlacement] Created preview: BuildingPreview"
|
|
||||||
- ✅ "[BuildingPlacement] Found X renderers"
|
|
||||||
- ✅ "[BuildingPlacement] Applied valid material to preview"
|
|
||||||
- ✅ "[BuildingPlacement] Preview activated at (x, y, z)"
|
|
||||||
|
|
||||||
**If you see errors:**
|
|
||||||
- ❌ "BuildingManager.Instance is null" → Go to Step 2
|
|
||||||
- ❌ "No buildings available" → Go to Step 3
|
|
||||||
- ❌ "Prefab is null" → Go to Step 4
|
|
||||||
- ❌ "Preview deactivated - no ground hit" → Go to Step 5
|
|
||||||
|
|
||||||
### 2. Check BuildingManager Setup
|
|
||||||
1. In Hierarchy, find "BuildingManager" GameObject
|
|
||||||
2. Verify it has:
|
|
||||||
- `BuildingManager` component
|
|
||||||
- `NetworkObject` component
|
|
||||||
3. In BuildingManager component:
|
|
||||||
- **Grid Size**: Should be > 0 (e.g., 1)
|
|
||||||
- **Ground Layer**: Should be set (e.g., "Default" or "Ground")
|
|
||||||
- **Available Buildings**: Should have at least 1 BuildingData
|
|
||||||
|
|
||||||
**Fix:** If BuildingManager doesn't exist, create it:
|
|
||||||
- Create Empty GameObject → Name: "BuildingManager"
|
|
||||||
- Add Component → BuildingManager
|
|
||||||
- Add Component → NetworkObject
|
|
||||||
- Configure settings
|
|
||||||
|
|
||||||
### 3. Check BuildingData Asset
|
|
||||||
1. Project window → Find your BuildingData asset
|
|
||||||
2. Select it and check Inspector:
|
|
||||||
- **Building Name**: Should have a name
|
|
||||||
- **Prefab**: Should be assigned (not "None")
|
|
||||||
- **Width/Length/Height**: Should be > 0
|
|
||||||
3. Verify this BuildingData is in BuildingManager's "Available Buildings" list
|
|
||||||
|
|
||||||
**Fix:** If BuildingData missing:
|
|
||||||
- Right-click in Project → Create → Northbound → Building Data
|
|
||||||
- Assign a prefab
|
|
||||||
- Add to BuildingManager's Available Buildings list
|
|
||||||
|
|
||||||
### 4. Check Building Prefab
|
|
||||||
1. Find your building prefab in Project
|
|
||||||
2. Open it and verify:
|
|
||||||
- Has a `MeshRenderer` or `SkinnedMeshRenderer` component
|
|
||||||
- Renderer has a `Material` assigned
|
|
||||||
- Has a `Collider` (for placement validation)
|
|
||||||
- Optionally has `NetworkObject` (will be removed from preview)
|
|
||||||
|
|
||||||
**Fix:** If prefab is missing components:
|
|
||||||
- Add a 3D object (Cube, for testing)
|
|
||||||
- Save as prefab
|
|
||||||
- Assign to BuildingData
|
|
||||||
|
|
||||||
### 5. Check Ground Layer
|
|
||||||
The preview won't show if the raycast can't hit the ground.
|
|
||||||
|
|
||||||
1. In Scene view, select your terrain/ground
|
|
||||||
2. Check its **Layer** (top of Inspector)
|
|
||||||
3. In BuildingManager:
|
|
||||||
- **Ground Layer** mask should include this layer
|
|
||||||
4. In BuildingPlacement (on Player):
|
|
||||||
- **Ground Layer** mask should match BuildingManager
|
|
||||||
|
|
||||||
**Fix:**
|
|
||||||
- Set terrain Layer to "Default" or create "Ground" layer
|
|
||||||
- In BuildingManager/BuildingPlacement → Ground Layer → Check "Default" (or your ground layer)
|
|
||||||
|
|
||||||
### 6. Check Camera
|
|
||||||
1. Verify `Camera.main` exists in scene
|
|
||||||
2. Camera should be able to see the ground
|
|
||||||
|
|
||||||
**Fix:**
|
|
||||||
- Make sure Main Camera has tag "MainCamera"
|
|
||||||
- Camera should be positioned to see the scene
|
|
||||||
|
|
||||||
### 7. Check Player Setup
|
|
||||||
1. Find your Player prefab
|
|
||||||
2. Verify it has:
|
|
||||||
- `BuildingPlacement` component
|
|
||||||
- **Ground Layer** is set
|
|
||||||
- **Max Placement Distance** > 0 (e.g., 100)
|
|
||||||
|
|
||||||
### 8. Visual Check in Scene View
|
|
||||||
1. Press B to enter build mode
|
|
||||||
2. Open **Hierarchy** window
|
|
||||||
3. Look for object named "BuildingPreview"
|
|
||||||
4. If it exists:
|
|
||||||
- Click on it
|
|
||||||
- Check if it's active (checkbox next to name)
|
|
||||||
- Look at its position in Inspector
|
|
||||||
- Switch to **Scene** view and look for it
|
|
||||||
- Check if materials are assigned in Renderer component
|
|
||||||
|
|
||||||
### 9. Test with Simple Cube
|
|
||||||
Create a minimal test setup:
|
|
||||||
1. Create a Cube in scene → Save as Prefab → Delete from scene
|
|
||||||
2. Create BuildingData → Assign Cube prefab
|
|
||||||
3. Add to BuildingManager → Available Buildings
|
|
||||||
4. Create a Plane → Position at (0, 0, 0)
|
|
||||||
5. Press Play → Press B → Move mouse over Plane
|
|
||||||
|
|
||||||
You should see the transparent cube preview.
|
|
||||||
|
|
||||||
## Common Issues
|
|
||||||
|
|
||||||
### Preview exists but is invisible
|
|
||||||
**Cause:** Materials not rendering or transparent issues
|
|
||||||
**Fix (Now Automated):**
|
|
||||||
- The system now auto-detects your render pipeline (URP or Standard)
|
|
||||||
- Creates proper transparent materials automatically
|
|
||||||
- If prefab has no renderer, adds a debug cube
|
|
||||||
|
|
||||||
**Manual Test:**
|
|
||||||
1. Add `GhostMaterialTest` component to any object with a renderer
|
|
||||||
2. Press Play
|
|
||||||
3. Object should become transparent green
|
|
||||||
4. If it works, the ghost system will work too
|
|
||||||
|
|
||||||
### Preview doesn't follow mouse
|
|
||||||
**Cause:** Ground layer not set correctly or no ground detected
|
|
||||||
**Fix:**
|
|
||||||
- Check console for "no ground hit" message
|
|
||||||
- Verify Ground Layer includes your terrain
|
|
||||||
- Increase Max Placement Distance
|
|
||||||
- Make sure you're pointing at ground, not sky
|
|
||||||
|
|
||||||
### Preview spawns at (0,0,0) and doesn't move
|
|
||||||
**Cause:** Raycast not hitting ground
|
|
||||||
**Fix:**
|
|
||||||
- Same as above - Ground Layer issue
|
|
||||||
|
|
||||||
### Materials are black/pink
|
|
||||||
**Cause:** Shader/material issues
|
|
||||||
**Fix:**
|
|
||||||
- Make sure you're using the correct render pipeline shaders
|
|
||||||
- Standard RP: Use "Standard" shader
|
|
||||||
- URP: Use "Universal Render Pipeline/Lit" shader
|
|
||||||
|
|
||||||
## Debug Commands
|
|
||||||
|
|
||||||
Add these temporary debug lines to check:
|
|
||||||
|
|
||||||
In `CreatePreview()` after instantiation:
|
|
||||||
```csharp
|
|
||||||
Debug.Log($"Preview position: {previewObject.transform.position}");
|
|
||||||
Debug.Log($"Preview active: {previewObject.activeSelf}");
|
|
||||||
Debug.Log($"Renderers: {previewRenderers.Length}");
|
|
||||||
```
|
|
||||||
|
|
||||||
In `UpdatePreviewPosition()`:
|
|
||||||
```csharp
|
|
||||||
Debug.DrawRay(ray.origin, ray.direction * maxPlacementDistance, Color.red);
|
|
||||||
```
|
|
||||||
|
|
||||||
This shows the raycast in Scene view (red line from camera to mouse).
|
|
||||||
|
|
||||||
## Still Not Working?
|
|
||||||
|
|
||||||
Check these final items:
|
|
||||||
1. Input Actions are set up (ToggleBuildMode, Rotate, Build)
|
|
||||||
2. BuildingManager is spawned on network (in scene, not prefab)
|
|
||||||
3. You're the owner of the player (IsOwner = true)
|
|
||||||
4. No errors in Console
|
|
||||||
5. Build mode is actually active (check console for "Entered Build Mode")
|
|
||||||
|
|
||||||
If all else fails, check the Console for any errors and share them!
|
|
||||||
Reference in New Issue
Block a user