데이터 파이프라인 개선 및 포탈 로직 생성
csv import 시 자동으로 완전한 프리팹이 생성될 수 있도록 함.
This commit is contained in:
120
AUTOMATED_PREFAB_PIPELINE.md
Normal file
120
AUTOMATED_PREFAB_PIPELINE.md
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
# 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)
|
||||||
@@ -48,16 +48,19 @@
|
|||||||
<Analyzer Include="C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Tools\BuildPipeline\Unity.SourceGenerators\Unity.UIToolkit.SourceGenerator.dll" />
|
<Analyzer Include="C:\Program Files\Unity\Hub\Editor\6000.3.5f2\Editor\Data\Tools\BuildPipeline\Unity.SourceGenerators\Unity.UIToolkit.SourceGenerator.dll" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Assets\Scripts\Editor\MonsterPrefabSetup.cs" />
|
||||||
<Compile Include="Assets\FlatKit\[Render Pipeline] URP\Water\Editor\Tooltips.cs" />
|
<Compile Include="Assets\FlatKit\[Render Pipeline] URP\Water\Editor\Tooltips.cs" />
|
||||||
<Compile Include="Assets\Scripts\Editor\EnemyPortalEditor.cs" />
|
<Compile Include="Assets\Scripts\Editor\EnemyPortalEditor.cs" />
|
||||||
<Compile Include="Assets\Editor\DataImporter\ImporterWindow.cs" />
|
<Compile Include="Assets\Editor\DataImporter\ImporterWindow.cs" />
|
||||||
<Compile Include="Assets\Scripts\Editor\ObstacleSpawnerEditor.cs" />
|
<Compile Include="Assets\Scripts\Editor\ObstacleSpawnerEditor.cs" />
|
||||||
<Compile Include="Assets\Editor\DataImporter\CSVDebugger.cs" />
|
<Compile Include="Assets\Editor\DataImporter\CSVDebugger.cs" />
|
||||||
|
<Compile Include="Assets\Scripts\Editor\IPrefabSetup.cs" />
|
||||||
<Compile Include="Assets\FlatKit\[Render Pipeline] URP\Water\Editor\WaterEditor.cs" />
|
<Compile Include="Assets\FlatKit\[Render Pipeline] URP\Water\Editor\WaterEditor.cs" />
|
||||||
<Compile Include="Assets\Editor\DataImporter\CSVToSOImporter.cs" />
|
<Compile Include="Assets\Editor\DataImporter\CSVToSOImporter.cs" />
|
||||||
<Compile Include="Assets\FlatKit\Shaders\Editor\ObjectOutlineEditorUtils.cs" />
|
<Compile Include="Assets\FlatKit\Shaders\Editor\ObjectOutlineEditorUtils.cs" />
|
||||||
<Compile Include="Assets\FlatKit\Demos\Common\Scripts\Motion\Editor\LinearMotionEditor.cs" />
|
<Compile Include="Assets\FlatKit\Demos\Common\Scripts\Motion\Editor\LinearMotionEditor.cs" />
|
||||||
<Compile Include="Assets\FlatKit\Shaders\GradientSkybox\Editor\GradientSkyboxEditor.cs" />
|
<Compile Include="Assets\FlatKit\Shaders\GradientSkybox\Editor\GradientSkyboxEditor.cs" />
|
||||||
|
<Compile Include="Assets\Scripts\Editor\TemplateCreator.cs" />
|
||||||
<Compile Include="Assets\FlatKit\Shaders\Editor\Tooltips.cs" />
|
<Compile Include="Assets\FlatKit\Shaders\Editor\Tooltips.cs" />
|
||||||
<Compile Include="Assets\FlatKit\Shaders\Editor\StylizedSurfaceEditor.cs" />
|
<Compile Include="Assets\FlatKit\Shaders\Editor\StylizedSurfaceEditor.cs" />
|
||||||
<Compile Include="Assets\Scripts\Editor\FogOfWarVisibilitySetup.cs" />
|
<Compile Include="Assets\Scripts\Editor\FogOfWarVisibilitySetup.cs" />
|
||||||
|
|||||||
@@ -91,6 +91,7 @@
|
|||||||
<Compile Include="Assets\Scripts\BuildingManager.cs" />
|
<Compile Include="Assets\Scripts\BuildingManager.cs" />
|
||||||
<Compile Include="Assets\Scripts\GhostMaterialTest.cs" />
|
<Compile Include="Assets\Scripts\GhostMaterialTest.cs" />
|
||||||
<Compile Include="Assets\Scripts\BuildingPlacement.cs" />
|
<Compile Include="Assets\Scripts\BuildingPlacement.cs" />
|
||||||
|
<Compile Include="Assets\Scripts\MonsterDataComponent.cs" />
|
||||||
<Compile Include="Assets\Scripts\Core.cs" />
|
<Compile Include="Assets\Scripts\Core.cs" />
|
||||||
<Compile Include="Assets\Scripts\EnemyPortal.cs" />
|
<Compile Include="Assets\Scripts\EnemyPortal.cs" />
|
||||||
<Compile Include="Assets\FlatKit\Demos\Common\Scripts\Motion\LinearMotion.cs" />
|
<Compile Include="Assets\FlatKit\Demos\Common\Scripts\Motion\LinearMotion.cs" />
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ MonoBehaviour:
|
|||||||
atkRange: 1
|
atkRange: 1
|
||||||
atkDamage: 3
|
atkDamage: 3
|
||||||
atkIntervalSec: 1.2
|
atkIntervalSec: 1.2
|
||||||
prefabPath: Assets/Prefabs/EnemyTest.prefab
|
meshPath: Assets/Meshes/Skeleton_Minion.fbx
|
||||||
cost: 1
|
cost: 1
|
||||||
weight: 1
|
weight: 1
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ MonoBehaviour:
|
|||||||
atkRange: 1
|
atkRange: 1
|
||||||
atkDamage: 2
|
atkDamage: 2
|
||||||
atkIntervalSec: 1
|
atkIntervalSec: 1
|
||||||
prefabPath: Assets/Prefabs/MonsterTest.prefab
|
meshPath: Assets/Meshes/Skeleton_Minion.fbx
|
||||||
cost: 2
|
cost: 2
|
||||||
weight: 0.5
|
weight: 0.5
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ MonoBehaviour:
|
|||||||
atkRange: 1
|
atkRange: 1
|
||||||
atkDamage: 4
|
atkDamage: 4
|
||||||
atkIntervalSec: 1.5
|
atkIntervalSec: 1.5
|
||||||
prefabPath: Assets/Prefabs/Core
|
meshPath: Assets/Meshes/Skeleton_Minion.fbx
|
||||||
cost: 5
|
cost: 5
|
||||||
weight: 0.2
|
weight: 0.2
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ MonoBehaviour:
|
|||||||
atkRange: 5
|
atkRange: 5
|
||||||
atkDamage: 2
|
atkDamage: 2
|
||||||
atkIntervalSec: 1.4
|
atkIntervalSec: 1.4
|
||||||
prefabPath: Assets/Prefabs/Resource
|
meshPath: Assets/Meshes/Skeleton_Minion.fbx
|
||||||
cost: 3
|
cost: 3
|
||||||
weight: 0.333
|
weight: 0.333
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ MonoBehaviour:
|
|||||||
atkRange: 1
|
atkRange: 1
|
||||||
atkDamage: 7
|
atkDamage: 7
|
||||||
atkIntervalSec: 1.3
|
atkIntervalSec: 1.3
|
||||||
prefabPath: Assets/Prefabs/ResourcePickup
|
meshPath: Assets/Meshes/Skeleton_Minion.fbx
|
||||||
cost: 10
|
cost: 10
|
||||||
weight: 0.1
|
weight: 0.1
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ namespace Northbound.Data
|
|||||||
public int atkDamage;
|
public int atkDamage;
|
||||||
/// <summary>공격 주기</summary>
|
/// <summary>공격 주기</summary>
|
||||||
public float atkIntervalSec;
|
public float atkIntervalSec;
|
||||||
/// <summary>프리팹/리소스 경로</summary>
|
/// <summary>메시 경로</summary>
|
||||||
public string prefabPath;
|
public string meshPath;
|
||||||
|
/// <summary>애니메이터 컨트롤러 경로</summary>
|
||||||
|
public string animatorControllerPath;
|
||||||
/// <summary>몬스터 난이도 점수</summary>
|
/// <summary>몬스터 난이도 점수</summary>
|
||||||
public int cost;
|
public int cost;
|
||||||
/// <summary>등장 가중치</summary>
|
/// <summary>등장 가중치</summary>
|
||||||
|
|||||||
8
Assets/Data/Templates.meta
Normal file
8
Assets/Data/Templates.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6430af38bc9c421459efd6be785c0853
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
229
Assets/Data/Templates/MonsterTemplate.prefab
Normal file
229
Assets/Data/Templates/MonsterTemplate.prefab
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!1 &3810918154428190126
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 244366501961263783}
|
||||||
|
- component: {fileID: 6694091345637567571}
|
||||||
|
- component: {fileID: 876002834352819743}
|
||||||
|
- component: {fileID: 3376121002006894933}
|
||||||
|
- component: {fileID: 4485945348237935463}
|
||||||
|
- component: {fileID: 3318886927439461238}
|
||||||
|
- component: {fileID: 3710510283885710978}
|
||||||
|
- component: {fileID: 8707976825772989039}
|
||||||
|
- component: {fileID: 305627289932973437}
|
||||||
|
m_Layer: 11
|
||||||
|
m_Name: MonsterTemplate
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!4 &244366501961263783
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 3810918154428190126}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!136 &6694091345637567571
|
||||||
|
CapsuleCollider:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 3810918154428190126}
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_IncludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_ExcludeLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 0
|
||||||
|
m_LayerOverridePriority: 0
|
||||||
|
m_IsTrigger: 0
|
||||||
|
m_ProvidesContacts: 0
|
||||||
|
m_Enabled: 1
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Radius: 0.5
|
||||||
|
m_Height: 2
|
||||||
|
m_Direction: 1
|
||||||
|
m_Center: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!114 &876002834352819743
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 3810918154428190126}
|
||||||
|
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: 949228241
|
||||||
|
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!114 &3376121002006894933
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 3810918154428190126}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 345fc6e7d4f06314f8b548129700eccb, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Assembly-CSharp::Northbound.EnemyUnit
|
||||||
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
|
enemyTeam: 3
|
||||||
|
maxHealth: 100
|
||||||
|
damageEffectPrefab: {fileID: 0}
|
||||||
|
destroyEffectPrefab: {fileID: 0}
|
||||||
|
--- !u!195 &4485945348237935463
|
||||||
|
NavMeshAgent:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 3810918154428190126}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_AgentTypeID: 0
|
||||||
|
m_Radius: 0.5
|
||||||
|
m_Speed: 3.5
|
||||||
|
m_Acceleration: 8
|
||||||
|
avoidancePriority: 50
|
||||||
|
m_AngularSpeed: 120
|
||||||
|
m_StoppingDistance: 0
|
||||||
|
m_AutoTraverseOffMeshLink: 1
|
||||||
|
m_AutoBraking: 1
|
||||||
|
m_AutoRepath: 1
|
||||||
|
m_Height: 2
|
||||||
|
m_BaseOffset: 0
|
||||||
|
m_WalkableMask: 4294967295
|
||||||
|
m_ObstacleAvoidanceType: 4
|
||||||
|
--- !u!114 &3318886927439461238
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 3810918154428190126}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 453e726e48d16214f84c6d5737edd7df, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Assembly-CSharp::Northbound.EnemyAIController
|
||||||
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
|
aiType: 3
|
||||||
|
detectionRange: 15
|
||||||
|
detectionAngle: 120
|
||||||
|
playerLayer:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 4294967295
|
||||||
|
obstacleLayer:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 4294967295
|
||||||
|
maxChaseDistance: 30
|
||||||
|
chaseGiveUpDistance: 25
|
||||||
|
attackRange: 2
|
||||||
|
attackInterval: 1.5
|
||||||
|
attackDamage: 10
|
||||||
|
moveSpeed: 3.5
|
||||||
|
chaseSpeedMultiplier: 1.5
|
||||||
|
showDebugInfo: 1
|
||||||
|
--- !u!114 &3710510283885710978
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 3810918154428190126}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: d702d872f7bcec54baa1f2ee285fc844, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: Assembly-CSharp::Northbound.MonsterDataComponent
|
||||||
|
monsterData: {fileID: 0}
|
||||||
|
autoApplyOnAwake: 1
|
||||||
|
--- !u!33 &8707976825772989039
|
||||||
|
MeshFilter:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 3810918154428190126}
|
||||||
|
m_Mesh: {fileID: 0}
|
||||||
|
--- !u!23 &305627289932973437
|
||||||
|
MeshRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 3810918154428190126}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_CastShadows: 1
|
||||||
|
m_ReceiveShadows: 1
|
||||||
|
m_DynamicOccludee: 1
|
||||||
|
m_StaticShadowCaster: 0
|
||||||
|
m_MotionVectors: 1
|
||||||
|
m_LightProbeUsage: 1
|
||||||
|
m_ReflectionProbeUsage: 1
|
||||||
|
m_RayTracingMode: 2
|
||||||
|
m_RayTraceProcedural: 0
|
||||||
|
m_RayTracingAccelStructBuildFlagsOverride: 0
|
||||||
|
m_RayTracingAccelStructBuildFlags: 1
|
||||||
|
m_SmallMeshCulling: 1
|
||||||
|
m_ForceMeshLod: -1
|
||||||
|
m_MeshLodSelectionBias: 0
|
||||||
|
m_RenderingLayerMask: 1
|
||||||
|
m_RendererPriority: 0
|
||||||
|
m_Materials:
|
||||||
|
- {fileID: 0}
|
||||||
|
m_StaticBatchInfo:
|
||||||
|
firstSubMesh: 0
|
||||||
|
subMeshCount: 0
|
||||||
|
m_StaticBatchRoot: {fileID: 0}
|
||||||
|
m_ProbeAnchor: {fileID: 0}
|
||||||
|
m_LightProbeVolumeOverride: {fileID: 0}
|
||||||
|
m_ScaleInLightmap: 1
|
||||||
|
m_ReceiveGI: 1
|
||||||
|
m_PreserveUVs: 0
|
||||||
|
m_IgnoreNormalsForChartDetection: 0
|
||||||
|
m_ImportantGI: 0
|
||||||
|
m_StitchLightmapSeams: 1
|
||||||
|
m_SelectedEditorRenderState: 3
|
||||||
|
m_MinimumChartSize: 4
|
||||||
|
m_AutoUVMaxDistance: 0.5
|
||||||
|
m_AutoUVMaxAngle: 89
|
||||||
|
m_LightmapParameters: {fileID: 0}
|
||||||
|
m_GlobalIlluminationMeshLod: 0
|
||||||
|
m_SortingLayerID: 0
|
||||||
|
m_SortingLayer: 0
|
||||||
|
m_SortingOrder: 0
|
||||||
|
m_MaskInteraction: 0
|
||||||
|
m_AdditionalVertexStreams: {fileID: 0}
|
||||||
7
Assets/Data/Templates/MonsterTemplate.prefab.meta
Normal file
7
Assets/Data/Templates/MonsterTemplate.prefab.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a91e5d5b7475218478febc7ec41f4b9a
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -69,3 +69,33 @@ MonoBehaviour:
|
|||||||
SourcePrefabToOverride: {fileID: 0}
|
SourcePrefabToOverride: {fileID: 0}
|
||||||
SourceHashToOverride: 0
|
SourceHashToOverride: 0
|
||||||
OverridingTargetPrefab: {fileID: 0}
|
OverridingTargetPrefab: {fileID: 0}
|
||||||
|
- Override: 0
|
||||||
|
Prefab: {fileID: 3810918154428190126, guid: a91e5d5b7475218478febc7ec41f4b9a, type: 3}
|
||||||
|
SourcePrefabToOverride: {fileID: 0}
|
||||||
|
SourceHashToOverride: 0
|
||||||
|
OverridingTargetPrefab: {fileID: 0}
|
||||||
|
- Override: 0
|
||||||
|
Prefab: {fileID: 5764052602947399654, guid: eccdace7a7b21a446891da5739b1549f, type: 3}
|
||||||
|
SourcePrefabToOverride: {fileID: 0}
|
||||||
|
SourceHashToOverride: 0
|
||||||
|
OverridingTargetPrefab: {fileID: 0}
|
||||||
|
- Override: 0
|
||||||
|
Prefab: {fileID: 8497750939283614707, guid: 4ee9b364dfe9d0949a23374e660d3e13, type: 3}
|
||||||
|
SourcePrefabToOverride: {fileID: 0}
|
||||||
|
SourceHashToOverride: 0
|
||||||
|
OverridingTargetPrefab: {fileID: 0}
|
||||||
|
- Override: 0
|
||||||
|
Prefab: {fileID: 7862005309935152867, guid: 9cac250ebe8d420469bd4da3ab8cfd86, type: 3}
|
||||||
|
SourcePrefabToOverride: {fileID: 0}
|
||||||
|
SourceHashToOverride: 0
|
||||||
|
OverridingTargetPrefab: {fileID: 0}
|
||||||
|
- Override: 0
|
||||||
|
Prefab: {fileID: 4357115039351603180, guid: ff2924ffa397a8e44925625790831d25, type: 3}
|
||||||
|
SourcePrefabToOverride: {fileID: 0}
|
||||||
|
SourceHashToOverride: 0
|
||||||
|
OverridingTargetPrefab: {fileID: 0}
|
||||||
|
- Override: 0
|
||||||
|
Prefab: {fileID: 4147164034265547914, guid: e8d09b814275ad745a427433c28dd53f, type: 3}
|
||||||
|
SourcePrefabToOverride: {fileID: 0}
|
||||||
|
SourceHashToOverride: 0
|
||||||
|
OverridingTargetPrefab: {fileID: 0}
|
||||||
|
|||||||
@@ -12,6 +12,27 @@ namespace Northbound.Editor
|
|||||||
{
|
{
|
||||||
private static readonly string GAMEDATA_PATH = Path.Combine(Application.dataPath, "..", "GameData");
|
private static readonly string GAMEDATA_PATH = Path.Combine(Application.dataPath, "..", "GameData");
|
||||||
private static readonly string SO_BASE_PATH = "Assets/Data/ScriptableObjects";
|
private static readonly string SO_BASE_PATH = "Assets/Data/ScriptableObjects";
|
||||||
|
private static readonly string PREFAB_BASE_PATH = "Assets/Prefabs";
|
||||||
|
private static readonly string TEMPLATE_BASE_PATH = "Assets/Data/Templates";
|
||||||
|
|
||||||
|
private static System.Collections.Generic.Dictionary<string, IPrefabSetup> prefabSetups =
|
||||||
|
new System.Collections.Generic.Dictionary<string, IPrefabSetup>();
|
||||||
|
|
||||||
|
static CSVToSOImporter()
|
||||||
|
{
|
||||||
|
RegisterPrefabSetups();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RegisterPrefabSetups()
|
||||||
|
{
|
||||||
|
prefabSetups.Clear();
|
||||||
|
prefabSetups["Monster"] = new MonsterPrefabSetup();
|
||||||
|
|
||||||
|
// To add new data types, create a class implementing IPrefabSetup
|
||||||
|
// Example:
|
||||||
|
// prefabSetups["Tower"] = new TowerPrefabSetup();
|
||||||
|
// prefabSetups["Player"] = new PlayerPrefabSetup();
|
||||||
|
}
|
||||||
|
|
||||||
[MenuItem("Tools/Data/Import All CSV")] // 메뉴 추가 (편의성)
|
[MenuItem("Tools/Data/Import All CSV")] // 메뉴 추가 (편의성)
|
||||||
public static void ImportAll()
|
public static void ImportAll()
|
||||||
@@ -54,6 +75,7 @@ namespace Northbound.Editor
|
|||||||
if (lines.Length < 2) return false;
|
if (lines.Length < 2) return false;
|
||||||
|
|
||||||
var headers = ParseCSVLine(lines[0]);
|
var headers = ParseCSVLine(lines[0]);
|
||||||
|
var createdSOs = new List<ScriptableObject>();
|
||||||
|
|
||||||
for (int lineIndex = 1; lineIndex < lines.Length; lineIndex++)
|
for (int lineIndex = 1; lineIndex < lines.Length; lineIndex++)
|
||||||
{
|
{
|
||||||
@@ -72,7 +94,12 @@ namespace Northbound.Editor
|
|||||||
}
|
}
|
||||||
|
|
||||||
string assetName = GetAssetName(so, lineIndex);
|
string assetName = GetAssetName(so, lineIndex);
|
||||||
AssetDatabase.CreateAsset(so, Path.Combine(outputPath, $"{assetName}.asset"));
|
string soPath = Path.Combine(outputPath, $"{assetName}.asset");
|
||||||
|
|
||||||
|
AssetDatabase.CreateAsset(so, soPath);
|
||||||
|
createdSOs.Add(so);
|
||||||
|
|
||||||
|
GenerateOrUpdatePrefab(schemaName, assetName, so, lineIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
AssetDatabase.SaveAssets();
|
AssetDatabase.SaveAssets();
|
||||||
@@ -148,11 +175,97 @@ namespace Northbound.Editor
|
|||||||
return l == "true" || l == "1" || l == "yes";
|
return l == "true" || l == "1" || l == "yes";
|
||||||
}
|
}
|
||||||
if (targetType == typeof(string)) return value.Replace("\\n", "\n");
|
if (targetType == typeof(string)) return value.Replace("\\n", "\n");
|
||||||
return Convert.ChangeType(value, targetType);
|
return Convert.ChangeType(value, targetType);
|
||||||
}
|
}
|
||||||
catch { return null; }
|
catch { return null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int GetColumnIndex(string[] headers, string columnName)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < headers.Length; i++)
|
||||||
|
{
|
||||||
|
if (headers[i].Equals(columnName, System.StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void GenerateOrUpdatePrefab(string schemaName, string prefabName, ScriptableObject so, int lineNumber)
|
||||||
|
{
|
||||||
|
string prefabOutputPath = Path.Combine(PREFAB_BASE_PATH, schemaName);
|
||||||
|
if (!Directory.Exists(prefabOutputPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(prefabOutputPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
string prefabPath = Path.Combine(prefabOutputPath, $"{prefabName}.prefab");
|
||||||
|
IPrefabSetup prefabSetup = GetPrefabSetup(schemaName);
|
||||||
|
|
||||||
|
if (prefabSetup == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[CSVImporter] No prefab setup found for {schemaName}, skipping prefab generation");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameObject template = LoadTemplate(schemaName);
|
||||||
|
if (template == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[CSVImporter] No template found for {schemaName}, skipping prefab generation");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath) != null)
|
||||||
|
{
|
||||||
|
UpdateExistingPrefab(prefabPath, so, prefabSetup);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GameObject prefabInstance = GameObject.Instantiate(template);
|
||||||
|
prefabInstance.name = prefabName;
|
||||||
|
PrefabUtility.SaveAsPrefabAsset(prefabInstance, prefabPath);
|
||||||
|
GameObject.DestroyImmediate(prefabInstance);
|
||||||
|
|
||||||
|
UpdateExistingPrefab(prefabPath, so, prefabSetup);
|
||||||
|
|
||||||
|
Debug.Log($"[CSVImporter] Created prefab: {prefabPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameObject LoadTemplate(string schemaName)
|
||||||
|
{
|
||||||
|
string templatePath = Path.Combine(TEMPLATE_BASE_PATH, $"{schemaName}Template.prefab");
|
||||||
|
return AssetDatabase.LoadAssetAtPath<GameObject>(templatePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IPrefabSetup GetPrefabSetup(string schemaName)
|
||||||
|
{
|
||||||
|
prefabSetups.TryGetValue(schemaName, out IPrefabSetup setup);
|
||||||
|
return setup;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UpdateExistingPrefab(string prefabPath, ScriptableObject so, IPrefabSetup prefabSetup)
|
||||||
|
{
|
||||||
|
GameObject prefabContents = PrefabUtility.LoadPrefabContents(prefabPath);
|
||||||
|
|
||||||
|
prefabSetup.SetupPrefab(prefabContents, so);
|
||||||
|
|
||||||
|
PrefabUtility.SaveAsPrefabAsset(prefabContents, prefabPath);
|
||||||
|
GameObject.DestroyImmediate(prefabContents);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RemoveOldModel(GameObject prefab)
|
||||||
|
{
|
||||||
|
Transform oldModel = prefab.transform.Find("Model");
|
||||||
|
if (oldModel != null)
|
||||||
|
{
|
||||||
|
GameObject.DestroyImmediate(oldModel.gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --- 유틸리티 메서드 (기존과 동일) ---
|
// --- 유틸리티 메서드 (기존과 동일) ---
|
||||||
private static string[] ParseCSVLine(string line)
|
private static string[] ParseCSVLine(string line)
|
||||||
{
|
{
|
||||||
|
|||||||
8
Assets/Meshes.meta
Normal file
8
Assets/Meshes.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8e1c86a713708ce44a1f0dfaec088917
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/Meshes/Skeleton_Minion.fbx
Normal file
BIN
Assets/Meshes/Skeleton_Minion.fbx
Normal file
Binary file not shown.
110
Assets/Meshes/Skeleton_Minion.fbx.meta
Normal file
110
Assets/Meshes/Skeleton_Minion.fbx.meta
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 134023580cb126541be31f65cf1db789
|
||||||
|
ModelImporter:
|
||||||
|
serializedVersion: 24200
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
materials:
|
||||||
|
materialImportMode: 2
|
||||||
|
materialName: 0
|
||||||
|
materialSearch: 1
|
||||||
|
materialLocation: 1
|
||||||
|
animations:
|
||||||
|
legacyGenerateAnimations: 4
|
||||||
|
bakeSimulation: 0
|
||||||
|
resampleCurves: 1
|
||||||
|
optimizeGameObjects: 0
|
||||||
|
removeConstantScaleCurves: 0
|
||||||
|
motionNodeName:
|
||||||
|
animationImportErrors:
|
||||||
|
animationImportWarnings:
|
||||||
|
animationRetargetingWarnings:
|
||||||
|
animationDoRetargetingWarnings: 0
|
||||||
|
importAnimatedCustomProperties: 0
|
||||||
|
importConstraints: 0
|
||||||
|
animationCompression: 3
|
||||||
|
animationRotationError: 0.5
|
||||||
|
animationPositionError: 0.5
|
||||||
|
animationScaleError: 0.5
|
||||||
|
animationWrapMode: 0
|
||||||
|
extraExposedTransformPaths: []
|
||||||
|
extraUserProperties: []
|
||||||
|
clipAnimations: []
|
||||||
|
isReadable: 0
|
||||||
|
meshes:
|
||||||
|
lODScreenPercentages: []
|
||||||
|
globalScale: 1
|
||||||
|
meshCompression: 0
|
||||||
|
addColliders: 0
|
||||||
|
useSRGBMaterialColor: 1
|
||||||
|
sortHierarchyByName: 1
|
||||||
|
importPhysicalCameras: 1
|
||||||
|
importVisibility: 1
|
||||||
|
importBlendShapes: 1
|
||||||
|
importCameras: 1
|
||||||
|
importLights: 1
|
||||||
|
nodeNameCollisionStrategy: 1
|
||||||
|
fileIdsGeneration: 2
|
||||||
|
swapUVChannels: 0
|
||||||
|
generateSecondaryUV: 0
|
||||||
|
useFileUnits: 1
|
||||||
|
keepQuads: 0
|
||||||
|
weldVertices: 1
|
||||||
|
bakeAxisConversion: 0
|
||||||
|
preserveHierarchy: 0
|
||||||
|
skinWeightsMode: 0
|
||||||
|
maxBonesPerVertex: 4
|
||||||
|
minBoneWeight: 0.001
|
||||||
|
optimizeBones: 1
|
||||||
|
generateMeshLods: 0
|
||||||
|
meshLodGenerationFlags: 0
|
||||||
|
maximumMeshLod: -1
|
||||||
|
meshOptimizationFlags: -1
|
||||||
|
indexFormat: 0
|
||||||
|
secondaryUVAngleDistortion: 8
|
||||||
|
secondaryUVAreaDistortion: 15.000001
|
||||||
|
secondaryUVHardAngle: 88
|
||||||
|
secondaryUVMarginMethod: 1
|
||||||
|
secondaryUVMinLightmapResolution: 40
|
||||||
|
secondaryUVMinObjectScale: 1
|
||||||
|
secondaryUVPackMargin: 4
|
||||||
|
useFileScale: 1
|
||||||
|
strictVertexDataChecks: 0
|
||||||
|
tangentSpace:
|
||||||
|
normalSmoothAngle: 60
|
||||||
|
normalImportMode: 0
|
||||||
|
tangentImportMode: 3
|
||||||
|
normalCalculationMode: 4
|
||||||
|
legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0
|
||||||
|
blendShapeNormalImportMode: 1
|
||||||
|
normalSmoothingSource: 0
|
||||||
|
referencedClips: []
|
||||||
|
importAnimation: 1
|
||||||
|
humanDescription:
|
||||||
|
serializedVersion: 3
|
||||||
|
human: []
|
||||||
|
skeleton: []
|
||||||
|
armTwist: 0.5
|
||||||
|
foreArmTwist: 0.5
|
||||||
|
upperLegTwist: 0.5
|
||||||
|
legTwist: 0.5
|
||||||
|
armStretch: 0.05
|
||||||
|
legStretch: 0.05
|
||||||
|
feetSpacing: 0
|
||||||
|
globalScale: 1
|
||||||
|
rootMotionBoneName: root
|
||||||
|
hasTranslationDoF: 0
|
||||||
|
hasExtraRoot: 1
|
||||||
|
skeletonHasParents: 1
|
||||||
|
lastHumanDescriptionAvatarSource: {instanceID: 0}
|
||||||
|
autoGenerateAvatarMappingIfUnspecified: 1
|
||||||
|
animationType: 2
|
||||||
|
humanoidOversampling: 1
|
||||||
|
avatarSetup: 1
|
||||||
|
addHumanoidExtraRootOnlyWhenUsingAvatar: 1
|
||||||
|
importBlendShapeDeformPercent: 1
|
||||||
|
remapMaterialsIfMaterialImportModeIsNone: 0
|
||||||
|
additionalBone: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
BIN
Assets/Meshes/Skeleton_Warrior.fbx
Normal file
BIN
Assets/Meshes/Skeleton_Warrior.fbx
Normal file
Binary file not shown.
110
Assets/Meshes/Skeleton_Warrior.fbx.meta
Normal file
110
Assets/Meshes/Skeleton_Warrior.fbx.meta
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e5f93785b41f28a41b45c24d60a3eb28
|
||||||
|
ModelImporter:
|
||||||
|
serializedVersion: 24200
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
materials:
|
||||||
|
materialImportMode: 2
|
||||||
|
materialName: 0
|
||||||
|
materialSearch: 1
|
||||||
|
materialLocation: 1
|
||||||
|
animations:
|
||||||
|
legacyGenerateAnimations: 4
|
||||||
|
bakeSimulation: 0
|
||||||
|
resampleCurves: 1
|
||||||
|
optimizeGameObjects: 0
|
||||||
|
removeConstantScaleCurves: 0
|
||||||
|
motionNodeName:
|
||||||
|
animationImportErrors:
|
||||||
|
animationImportWarnings:
|
||||||
|
animationRetargetingWarnings:
|
||||||
|
animationDoRetargetingWarnings: 0
|
||||||
|
importAnimatedCustomProperties: 0
|
||||||
|
importConstraints: 0
|
||||||
|
animationCompression: 1
|
||||||
|
animationRotationError: 0.5
|
||||||
|
animationPositionError: 0.5
|
||||||
|
animationScaleError: 0.5
|
||||||
|
animationWrapMode: 0
|
||||||
|
extraExposedTransformPaths: []
|
||||||
|
extraUserProperties: []
|
||||||
|
clipAnimations: []
|
||||||
|
isReadable: 0
|
||||||
|
meshes:
|
||||||
|
lODScreenPercentages: []
|
||||||
|
globalScale: 1
|
||||||
|
meshCompression: 0
|
||||||
|
addColliders: 0
|
||||||
|
useSRGBMaterialColor: 1
|
||||||
|
sortHierarchyByName: 1
|
||||||
|
importPhysicalCameras: 1
|
||||||
|
importVisibility: 1
|
||||||
|
importBlendShapes: 1
|
||||||
|
importCameras: 1
|
||||||
|
importLights: 1
|
||||||
|
nodeNameCollisionStrategy: 1
|
||||||
|
fileIdsGeneration: 2
|
||||||
|
swapUVChannels: 0
|
||||||
|
generateSecondaryUV: 0
|
||||||
|
useFileUnits: 1
|
||||||
|
keepQuads: 0
|
||||||
|
weldVertices: 1
|
||||||
|
bakeAxisConversion: 0
|
||||||
|
preserveHierarchy: 0
|
||||||
|
skinWeightsMode: 0
|
||||||
|
maxBonesPerVertex: 4
|
||||||
|
minBoneWeight: 0.001
|
||||||
|
optimizeBones: 1
|
||||||
|
generateMeshLods: 0
|
||||||
|
meshLodGenerationFlags: 0
|
||||||
|
maximumMeshLod: -1
|
||||||
|
meshOptimizationFlags: -1
|
||||||
|
indexFormat: 0
|
||||||
|
secondaryUVAngleDistortion: 8
|
||||||
|
secondaryUVAreaDistortion: 15.000001
|
||||||
|
secondaryUVHardAngle: 88
|
||||||
|
secondaryUVMarginMethod: 1
|
||||||
|
secondaryUVMinLightmapResolution: 40
|
||||||
|
secondaryUVMinObjectScale: 1
|
||||||
|
secondaryUVPackMargin: 4
|
||||||
|
useFileScale: 1
|
||||||
|
strictVertexDataChecks: 0
|
||||||
|
tangentSpace:
|
||||||
|
normalSmoothAngle: 60
|
||||||
|
normalImportMode: 0
|
||||||
|
tangentImportMode: 3
|
||||||
|
normalCalculationMode: 4
|
||||||
|
legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0
|
||||||
|
blendShapeNormalImportMode: 1
|
||||||
|
normalSmoothingSource: 0
|
||||||
|
referencedClips: []
|
||||||
|
importAnimation: 1
|
||||||
|
humanDescription:
|
||||||
|
serializedVersion: 3
|
||||||
|
human: []
|
||||||
|
skeleton: []
|
||||||
|
armTwist: 0.5
|
||||||
|
foreArmTwist: 0.5
|
||||||
|
upperLegTwist: 0.5
|
||||||
|
legTwist: 0.5
|
||||||
|
armStretch: 0.05
|
||||||
|
legStretch: 0.05
|
||||||
|
feetSpacing: 0
|
||||||
|
globalScale: 1
|
||||||
|
rootMotionBoneName:
|
||||||
|
hasTranslationDoF: 0
|
||||||
|
hasExtraRoot: 0
|
||||||
|
skeletonHasParents: 1
|
||||||
|
lastHumanDescriptionAvatarSource: {instanceID: 0}
|
||||||
|
autoGenerateAvatarMappingIfUnspecified: 1
|
||||||
|
animationType: 2
|
||||||
|
humanoidOversampling: 1
|
||||||
|
avatarSetup: 0
|
||||||
|
addHumanoidExtraRootOnlyWhenUsingAvatar: 1
|
||||||
|
importBlendShapeDeformPercent: 1
|
||||||
|
remapMaterialsIfMaterialImportModeIsNone: 0
|
||||||
|
additionalBone: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/Prefabs/DefaultSettings.meta
Normal file
8
Assets/Prefabs/DefaultSettings.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a685391aa8ab5b94a9930a3b11bdb4e4
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -49,7 +49,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: 3777549664
|
GlobalObjectIdHash: 2325681781
|
||||||
InScenePlacedSourceGlobalObjectIdHash: 190018048
|
InScenePlacedSourceGlobalObjectIdHash: 190018048
|
||||||
DeferredDespawnTick: 0
|
DeferredDespawnTick: 0
|
||||||
Ownership: 1
|
Ownership: 1
|
||||||
@@ -77,7 +77,6 @@ MonoBehaviour:
|
|||||||
ShowTopMostFoldoutHeaderGroup: 1
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
enemyTeam: 2
|
enemyTeam: 2
|
||||||
maxHealth: 100
|
maxHealth: 100
|
||||||
visionRange: 0
|
|
||||||
damageEffectPrefab: {fileID: 0}
|
damageEffectPrefab: {fileID: 0}
|
||||||
destroyEffectPrefab: {fileID: 0}
|
destroyEffectPrefab: {fileID: 0}
|
||||||
--- !u!195 &3722870329869553245
|
--- !u!195 &3722870329869553245
|
||||||
|
|||||||
8
Assets/Prefabs/Monster.meta
Normal file
8
Assets/Prefabs/Monster.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 01eaba87f2878e2419105e30e0b9e572
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
2119
Assets/Prefabs/Monster/Monster101.prefab
Normal file
2119
Assets/Prefabs/Monster/Monster101.prefab
Normal file
File diff suppressed because it is too large
Load Diff
7
Assets/Prefabs/Monster/Monster101.prefab.meta
Normal file
7
Assets/Prefabs/Monster/Monster101.prefab.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: eccdace7a7b21a446891da5739b1549f
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
2096
Assets/Prefabs/Monster/Monster102.prefab
Normal file
2096
Assets/Prefabs/Monster/Monster102.prefab
Normal file
File diff suppressed because it is too large
Load Diff
7
Assets/Prefabs/Monster/Monster102.prefab.meta
Normal file
7
Assets/Prefabs/Monster/Monster102.prefab.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4ee9b364dfe9d0949a23374e660d3e13
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
2096
Assets/Prefabs/Monster/Monster103.prefab
Normal file
2096
Assets/Prefabs/Monster/Monster103.prefab
Normal file
File diff suppressed because it is too large
Load Diff
7
Assets/Prefabs/Monster/Monster103.prefab.meta
Normal file
7
Assets/Prefabs/Monster/Monster103.prefab.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9cac250ebe8d420469bd4da3ab8cfd86
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
2096
Assets/Prefabs/Monster/Monster104.prefab
Normal file
2096
Assets/Prefabs/Monster/Monster104.prefab
Normal file
File diff suppressed because it is too large
Load Diff
7
Assets/Prefabs/Monster/Monster104.prefab.meta
Normal file
7
Assets/Prefabs/Monster/Monster104.prefab.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ff2924ffa397a8e44925625790831d25
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
2096
Assets/Prefabs/Monster/Monster105.prefab
Normal file
2096
Assets/Prefabs/Monster/Monster105.prefab
Normal file
File diff suppressed because it is too large
Load Diff
7
Assets/Prefabs/Monster/Monster105.prefab.meta
Normal file
7
Assets/Prefabs/Monster/Monster105.prefab.meta
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e8d09b814275ad745a427433c28dd53f
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/Prefabs/Player.meta
Normal file
8
Assets/Prefabs/Player.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9f4ee65fec2f6ff439a564550b072221
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
8
Assets/Prefabs/Tower.meta
Normal file
8
Assets/Prefabs/Tower.meta
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e2baea03a27214c4fb8085788c5b7443
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -1893,7 +1893,7 @@ PrefabInstance:
|
|||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 6859825394817103090, guid: 11e3760dda2c0164abf759c18d918893, type: 3}
|
- target: {fileID: 6859825394817103090, guid: 11e3760dda2c0164abf759c18d918893, type: 3}
|
||||||
propertyPath: monsterEntries.Array.size
|
propertyPath: monsterEntries.Array.size
|
||||||
value: 2
|
value: 0
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 6859825394817103090, guid: 11e3760dda2c0164abf759c18d918893, type: 3}
|
- target: {fileID: 6859825394817103090, guid: 11e3760dda2c0164abf759c18d918893, type: 3}
|
||||||
propertyPath: monsterEntries.Array.data[0].prefab
|
propertyPath: monsterEntries.Array.data[0].prefab
|
||||||
@@ -1906,11 +1906,11 @@ PrefabInstance:
|
|||||||
- target: {fileID: 6859825394817103090, guid: 11e3760dda2c0164abf759c18d918893, type: 3}
|
- target: {fileID: 6859825394817103090, guid: 11e3760dda2c0164abf759c18d918893, type: 3}
|
||||||
propertyPath: monsterEntries.Array.data[0].monsterData
|
propertyPath: monsterEntries.Array.data[0].monsterData
|
||||||
value:
|
value:
|
||||||
objectReference: {fileID: 11400000, guid: cec2336a0be4b6c4eb954ac3d08aa8d9, type: 2}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 6859825394817103090, guid: 11e3760dda2c0164abf759c18d918893, type: 3}
|
- target: {fileID: 6859825394817103090, guid: 11e3760dda2c0164abf759c18d918893, type: 3}
|
||||||
propertyPath: monsterEntries.Array.data[1].monsterData
|
propertyPath: monsterEntries.Array.data[1].monsterData
|
||||||
value:
|
value:
|
||||||
objectReference: {fileID: 11400000, guid: fde07a19ade09f044aca9151f407d073, type: 2}
|
objectReference: {fileID: 0}
|
||||||
m_RemovedComponents: []
|
m_RemovedComponents: []
|
||||||
m_RemovedGameObjects: []
|
m_RemovedGameObjects: []
|
||||||
m_AddedGameObjects: []
|
m_AddedGameObjects: []
|
||||||
@@ -3246,7 +3246,7 @@ PrefabInstance:
|
|||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 6859825394817103090, guid: 11e3760dda2c0164abf759c18d918893, type: 3}
|
- target: {fileID: 6859825394817103090, guid: 11e3760dda2c0164abf759c18d918893, type: 3}
|
||||||
propertyPath: monsterEntries.Array.size
|
propertyPath: monsterEntries.Array.size
|
||||||
value: 2
|
value: 0
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 6859825394817103090, guid: 11e3760dda2c0164abf759c18d918893, type: 3}
|
- target: {fileID: 6859825394817103090, guid: 11e3760dda2c0164abf759c18d918893, type: 3}
|
||||||
propertyPath: monsterEntries.Array.data[0].prefab
|
propertyPath: monsterEntries.Array.data[0].prefab
|
||||||
@@ -3259,11 +3259,11 @@ PrefabInstance:
|
|||||||
- target: {fileID: 6859825394817103090, guid: 11e3760dda2c0164abf759c18d918893, type: 3}
|
- target: {fileID: 6859825394817103090, guid: 11e3760dda2c0164abf759c18d918893, type: 3}
|
||||||
propertyPath: monsterEntries.Array.data[0].monsterData
|
propertyPath: monsterEntries.Array.data[0].monsterData
|
||||||
value:
|
value:
|
||||||
objectReference: {fileID: 11400000, guid: cec2336a0be4b6c4eb954ac3d08aa8d9, type: 2}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 6859825394817103090, guid: 11e3760dda2c0164abf759c18d918893, type: 3}
|
- target: {fileID: 6859825394817103090, guid: 11e3760dda2c0164abf759c18d918893, type: 3}
|
||||||
propertyPath: monsterEntries.Array.data[1].monsterData
|
propertyPath: monsterEntries.Array.data[1].monsterData
|
||||||
value:
|
value:
|
||||||
objectReference: {fileID: 11400000, guid: fde07a19ade09f044aca9151f407d073, type: 2}
|
objectReference: {fileID: 0}
|
||||||
m_RemovedComponents: []
|
m_RemovedComponents: []
|
||||||
m_RemovedGameObjects: []
|
m_RemovedGameObjects: []
|
||||||
m_AddedGameObjects: []
|
m_AddedGameObjects: []
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using Northbound;
|
||||||
|
using Northbound.Data;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -6,7 +8,7 @@ using System.Linq;
|
|||||||
[CustomEditor(typeof(EnemyPortal))]
|
[CustomEditor(typeof(EnemyPortal))]
|
||||||
public class EnemyPortalEditor : Editor
|
public class EnemyPortalEditor : Editor
|
||||||
{
|
{
|
||||||
private const string MONSTER_DATA_FOLDER = "Assets/Data/ScriptableObjects/Monster";
|
private const string MONSTER_PREFAB_FOLDER = "Assets/Prefabs/Monster";
|
||||||
|
|
||||||
public override void OnInspectorGUI()
|
public override void OnInspectorGUI()
|
||||||
{
|
{
|
||||||
@@ -22,12 +24,12 @@ public class EnemyPortalEditor : Editor
|
|||||||
LoadMonsterData(portal);
|
LoadMonsterData(portal);
|
||||||
}
|
}
|
||||||
|
|
||||||
EditorGUILayout.HelpBox("Click 'Load Monster Data' to automatically load all monsters from " + MONSTER_DATA_FOLDER, MessageType.Info);
|
EditorGUILayout.HelpBox("Click 'Load Monster Data' to automatically load all monster prefabs from " + MONSTER_PREFAB_FOLDER, MessageType.Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadMonsterData(EnemyPortal portal)
|
private void LoadMonsterData(EnemyPortal portal)
|
||||||
{
|
{
|
||||||
string[] guids = AssetDatabase.FindAssets("t:MonsterData", new[] { MONSTER_DATA_FOLDER });
|
string[] guids = AssetDatabase.FindAssets("t:Prefab", new[] { MONSTER_PREFAB_FOLDER });
|
||||||
|
|
||||||
List<EnemyPortal.MonsterEntry> entries = new List<EnemyPortal.MonsterEntry>();
|
List<EnemyPortal.MonsterEntry> entries = new List<EnemyPortal.MonsterEntry>();
|
||||||
int loadedCount = 0;
|
int loadedCount = 0;
|
||||||
@@ -35,24 +37,23 @@ public class EnemyPortalEditor : Editor
|
|||||||
foreach (string guid in guids)
|
foreach (string guid in guids)
|
||||||
{
|
{
|
||||||
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
|
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
|
||||||
Northbound.Data.MonsterData monsterData = AssetDatabase.LoadAssetAtPath<Northbound.Data.MonsterData>(assetPath);
|
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(assetPath);
|
||||||
|
|
||||||
if (monsterData != null)
|
if (prefab != null)
|
||||||
{
|
{
|
||||||
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(monsterData.prefabPath);
|
MonsterDataComponent monsterDataComponent = prefab.GetComponent<MonsterDataComponent>();
|
||||||
|
|
||||||
if (prefab != null)
|
if (monsterDataComponent != null && monsterDataComponent.monsterData != null)
|
||||||
{
|
{
|
||||||
entries.Add(new EnemyPortal.MonsterEntry
|
entries.Add(new EnemyPortal.MonsterEntry
|
||||||
{
|
{
|
||||||
monsterData = monsterData,
|
|
||||||
prefab = prefab
|
prefab = prefab
|
||||||
});
|
});
|
||||||
loadedCount++;
|
loadedCount++;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.LogWarning($"[EnemyPortal] Could not load prefab at path: {monsterData.prefabPath} for monster {monsterData.id}");
|
Debug.LogWarning($"[EnemyPortal] Prefab {prefab.name} does not have MonsterDataComponent with valid monsterData reference");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,13 +66,12 @@ public class EnemyPortalEditor : Editor
|
|||||||
for (int i = 0; i < entries.Count; i++)
|
for (int i = 0; i < entries.Count; i++)
|
||||||
{
|
{
|
||||||
SerializedProperty element = entriesProperty.GetArrayElementAtIndex(i);
|
SerializedProperty element = entriesProperty.GetArrayElementAtIndex(i);
|
||||||
element.FindPropertyRelative("monsterData").objectReferenceValue = entries[i].monsterData;
|
|
||||||
element.FindPropertyRelative("prefab").objectReferenceValue = entries[i].prefab;
|
element.FindPropertyRelative("prefab").objectReferenceValue = entries[i].prefab;
|
||||||
}
|
}
|
||||||
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
AssetDatabase.SaveAssets();
|
AssetDatabase.SaveAssets();
|
||||||
|
|
||||||
Debug.Log($"[EnemyPortal] Loaded {loadedCount} monsters from {MONSTER_DATA_FOLDER}");
|
Debug.Log($"[EnemyPortal] Loaded {loadedCount} monsters from {MONSTER_PREFAB_FOLDER}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
Assets/Scripts/Editor/IPrefabSetup.cs
Normal file
10
Assets/Scripts/Editor/IPrefabSetup.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Northbound.Editor
|
||||||
|
{
|
||||||
|
public interface IPrefabSetup
|
||||||
|
{
|
||||||
|
string GetTemplateName();
|
||||||
|
void SetupPrefab(GameObject prefab, ScriptableObject data);
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/Editor/IPrefabSetup.cs.meta
Normal file
2
Assets/Scripts/Editor/IPrefabSetup.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ddd133259cf685c4da37370144003c52
|
||||||
99
Assets/Scripts/Editor/MonsterPrefabSetup.cs
Normal file
99
Assets/Scripts/Editor/MonsterPrefabSetup.cs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
using Northbound.Data;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Northbound.Editor
|
||||||
|
{
|
||||||
|
public class MonsterPrefabSetup : IPrefabSetup
|
||||||
|
{
|
||||||
|
public string GetTemplateName()
|
||||||
|
{
|
||||||
|
return "MonsterTemplate";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetupPrefab(GameObject prefab, ScriptableObject data)
|
||||||
|
{
|
||||||
|
if (!(data is MonsterData monsterData))
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[MonsterPrefabSetup] Expected MonsterData, got {data.GetType().Name}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var monsterDataComponent = prefab.GetComponent<MonsterDataComponent>();
|
||||||
|
if (monsterDataComponent != null)
|
||||||
|
{
|
||||||
|
monsterDataComponent.monsterData = monsterData;
|
||||||
|
monsterDataComponent.ApplyMonsterData();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(monsterData.meshPath))
|
||||||
|
{
|
||||||
|
RemoveOldModel(prefab);
|
||||||
|
|
||||||
|
if (monsterData.meshPath.ToLower().EndsWith(".fbx"))
|
||||||
|
{
|
||||||
|
GameObject fbxModel = AssetDatabase.LoadAssetAtPath<GameObject>(monsterData.meshPath);
|
||||||
|
if (fbxModel != null)
|
||||||
|
{
|
||||||
|
GameObject fbxInstance = GameObject.Instantiate(fbxModel);
|
||||||
|
fbxInstance.name = "Model";
|
||||||
|
fbxInstance.transform.SetParent(prefab.transform, false);
|
||||||
|
fbxInstance.transform.localPosition = Vector3.zero;
|
||||||
|
fbxInstance.transform.localRotation = Quaternion.identity;
|
||||||
|
fbxInstance.transform.localScale = Vector3.one;
|
||||||
|
|
||||||
|
Debug.Log($"[MonsterPrefabSetup] Applied FBX model: {monsterData.meshPath}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[MonsterPrefabSetup] Could not load FBX model: {monsterData.meshPath}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var meshFilter = prefab.GetComponent<MeshFilter>();
|
||||||
|
if (meshFilter != null)
|
||||||
|
{
|
||||||
|
Mesh mesh = AssetDatabase.LoadAssetAtPath<Mesh>(monsterData.meshPath);
|
||||||
|
if (mesh != null)
|
||||||
|
{
|
||||||
|
meshFilter.sharedMesh = mesh;
|
||||||
|
Debug.Log($"[MonsterPrefabSetup] Applied mesh: {monsterData.meshPath}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[MonsterPrefabSetup] Could not load mesh: {monsterData.meshPath}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(monsterData.animatorControllerPath))
|
||||||
|
{
|
||||||
|
Animator animator = prefab.GetComponent<Animator>();
|
||||||
|
if (animator != null)
|
||||||
|
{
|
||||||
|
RuntimeAnimatorController controller = AssetDatabase.LoadAssetAtPath<RuntimeAnimatorController>(monsterData.animatorControllerPath);
|
||||||
|
if (controller != null)
|
||||||
|
{
|
||||||
|
animator.runtimeAnimatorController = controller;
|
||||||
|
Debug.Log($"[MonsterPrefabSetup] Applied Animator Controller: {monsterData.animatorControllerPath}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[MonsterPrefabSetup] Could not load Animator Controller: {monsterData.animatorControllerPath}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveOldModel(GameObject prefab)
|
||||||
|
{
|
||||||
|
Transform oldModel = prefab.transform.Find("Model");
|
||||||
|
if (oldModel != null)
|
||||||
|
{
|
||||||
|
GameObject.DestroyImmediate(oldModel.gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/Editor/MonsterPrefabSetup.cs.meta
Normal file
2
Assets/Scripts/Editor/MonsterPrefabSetup.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9d78ef5bde300404a89bb3af71df1610
|
||||||
180
Assets/Scripts/Editor/TemplateCreator.cs
Normal file
180
Assets/Scripts/Editor/TemplateCreator.cs
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
using Unity.Netcode;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.AI;
|
||||||
|
|
||||||
|
namespace Northbound.Editor
|
||||||
|
{
|
||||||
|
public class TemplateCreator
|
||||||
|
{
|
||||||
|
private const string TEMPLATE_BASE_PATH = "Assets/Data/Templates";
|
||||||
|
|
||||||
|
[MenuItem("Tools/Data/Create Monster Template")]
|
||||||
|
public static void CreateMonsterTemplate()
|
||||||
|
{
|
||||||
|
CreateTemplate("Monster", SetupMonsterComponents);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MenuItem("Tools/Data/Create Tower Template")]
|
||||||
|
public static void CreateTowerTemplate()
|
||||||
|
{
|
||||||
|
CreateTemplate("Tower", SetupTowerComponents);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MenuItem("Tools/Data/Create Player Template")]
|
||||||
|
public static void CreatePlayerTemplate()
|
||||||
|
{
|
||||||
|
CreateTemplate("Player", SetupPlayerComponents);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CreateTemplate(string typeName, System.Action<GameObject> setupComponents)
|
||||||
|
{
|
||||||
|
if (!AssetDatabase.IsValidFolder(TEMPLATE_BASE_PATH))
|
||||||
|
{
|
||||||
|
System.IO.Directory.CreateDirectory(Application.dataPath + "/Data/Templates");
|
||||||
|
AssetDatabase.Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
string templateName = $"{typeName}Template";
|
||||||
|
string templatePath = $"{TEMPLATE_BASE_PATH}/{templateName}.prefab";
|
||||||
|
|
||||||
|
GameObject templateGO = new GameObject(templateName);
|
||||||
|
|
||||||
|
setupComponents(templateGO);
|
||||||
|
|
||||||
|
PrefabUtility.SaveAsPrefabAsset(templateGO, templatePath);
|
||||||
|
GameObject.DestroyImmediate(templateGO);
|
||||||
|
|
||||||
|
Debug.Log($"[TemplateCreator] Created template: {templatePath}");
|
||||||
|
AssetDatabase.Refresh();
|
||||||
|
Selection.activeObject = AssetDatabase.LoadAssetAtPath<GameObject>(templatePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetupMonsterComponents(GameObject go)
|
||||||
|
{
|
||||||
|
Transform t = go.transform;
|
||||||
|
t.localPosition = Vector3.zero;
|
||||||
|
t.localRotation = Quaternion.identity;
|
||||||
|
t.localScale = Vector3.one;
|
||||||
|
|
||||||
|
if (go.GetComponent<CapsuleCollider>() == null)
|
||||||
|
{
|
||||||
|
CapsuleCollider collider = go.AddComponent<CapsuleCollider>();
|
||||||
|
collider.isTrigger = false;
|
||||||
|
collider.radius = 0.5f;
|
||||||
|
collider.height = 2f;
|
||||||
|
collider.direction = 1;
|
||||||
|
collider.center = Vector3.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (go.GetComponent<Animator>() == null)
|
||||||
|
{
|
||||||
|
Animator animator = go.AddComponent<Animator>();
|
||||||
|
animator.runtimeAnimatorController = null;
|
||||||
|
animator.avatar = null;
|
||||||
|
animator.applyRootMotion = false;
|
||||||
|
animator.updateMode = AnimatorUpdateMode.Normal;
|
||||||
|
animator.cullingMode = AnimatorCullingMode.CullUpdateTransforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (go.GetComponent<NetworkObject>() == null)
|
||||||
|
go.AddComponent<NetworkObject>();
|
||||||
|
|
||||||
|
if (go.GetComponent<EnemyUnit>() == null)
|
||||||
|
go.AddComponent<EnemyUnit>();
|
||||||
|
|
||||||
|
if (go.GetComponent<MonsterDataComponent>() == null)
|
||||||
|
go.AddComponent<MonsterDataComponent>();
|
||||||
|
|
||||||
|
if (go.GetComponent<NavMeshAgent>() == null)
|
||||||
|
{
|
||||||
|
NavMeshAgent agent = go.AddComponent<NavMeshAgent>();
|
||||||
|
agent.speed = 2.6f;
|
||||||
|
agent.angularSpeed = 120f;
|
||||||
|
agent.acceleration = 8f;
|
||||||
|
agent.stoppingDistance = 0f;
|
||||||
|
agent.autoBraking = true;
|
||||||
|
agent.radius = 0.5f;
|
||||||
|
agent.height = 2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (go.GetComponent<EnemyAIController>() == null)
|
||||||
|
{
|
||||||
|
EnemyAIController ai = go.AddComponent<EnemyAIController>();
|
||||||
|
ai.aiType = TeamType.Monster;
|
||||||
|
ai.detectionRange = 6f;
|
||||||
|
ai.detectionAngle = 360f;
|
||||||
|
ai.maxChaseDistance = 30f;
|
||||||
|
ai.attackRange = 1f;
|
||||||
|
ai.attackInterval = 1.2f;
|
||||||
|
ai.attackDamage = 3;
|
||||||
|
ai.moveSpeed = 2.6f;
|
||||||
|
ai.chaseSpeedMultiplier = 1.2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
MeshFilter meshFilter = go.GetComponent<MeshFilter>();
|
||||||
|
if (meshFilter == null)
|
||||||
|
meshFilter = go.AddComponent<MeshFilter>();
|
||||||
|
|
||||||
|
MeshRenderer meshRenderer = go.GetComponent<MeshRenderer>();
|
||||||
|
if (meshRenderer == null)
|
||||||
|
meshRenderer = go.AddComponent<MeshRenderer>();
|
||||||
|
|
||||||
|
int monsterLayer = LayerMask.NameToLayer("Monster");
|
||||||
|
if (monsterLayer >= 0)
|
||||||
|
{
|
||||||
|
go.layer = monsterLayer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
go.layer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkObject netObj = go.GetComponent<NetworkObject>();
|
||||||
|
if (netObj != null)
|
||||||
|
{
|
||||||
|
netObj.SynchronizeTransform = true;
|
||||||
|
netObj.SpawnWithObservers = true;
|
||||||
|
netObj.AlwaysReplicateAsRoot = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnemyUnit enemyUnit = go.GetComponent<EnemyUnit>();
|
||||||
|
if (enemyUnit != null)
|
||||||
|
{
|
||||||
|
enemyUnit.enemyTeam = TeamType.Monster;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetupTowerComponents(GameObject go)
|
||||||
|
{
|
||||||
|
if (go.GetComponent<NetworkObject>() == null)
|
||||||
|
go.AddComponent<NetworkObject>();
|
||||||
|
|
||||||
|
int defaultLayer = LayerMask.NameToLayer("Default");
|
||||||
|
if (defaultLayer >= 0)
|
||||||
|
{
|
||||||
|
go.layer = defaultLayer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
go.layer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetupPlayerComponents(GameObject go)
|
||||||
|
{
|
||||||
|
if (go.GetComponent<NetworkObject>() == null)
|
||||||
|
go.AddComponent<NetworkObject>();
|
||||||
|
|
||||||
|
int playerLayer = LayerMask.NameToLayer("Player");
|
||||||
|
if (playerLayer >= 0)
|
||||||
|
{
|
||||||
|
go.layer = playerLayer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
go.layer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/Editor/TemplateCreator.cs.meta
Normal file
2
Assets/Scripts/Editor/TemplateCreator.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9b0786fac9db245429b33a2597a2d7b4
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Northbound;
|
using Northbound;
|
||||||
|
using Northbound.Data;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Unity.Netcode;
|
using Unity.Netcode;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -8,12 +9,11 @@ public class EnemyPortal : MonoBehaviour
|
|||||||
[System.Serializable]
|
[System.Serializable]
|
||||||
public class MonsterEntry
|
public class MonsterEntry
|
||||||
{
|
{
|
||||||
public Northbound.Data.MonsterData monsterData;
|
|
||||||
public GameObject prefab;
|
public GameObject prefab;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Header("Spawn Settings")]
|
[Header("Spawn Settings")]
|
||||||
[Tooltip("몬스터 데이터 목록 (Editor에서 자동 로드 가능)")]
|
[Tooltip("몬스터 프리팹 목록 (Editor에서 자동 로드 가능)")]
|
||||||
[SerializeField] private List<MonsterEntry> monsterEntries = new();
|
[SerializeField] private List<MonsterEntry> monsterEntries = new();
|
||||||
|
|
||||||
[Header("Cost Settings")]
|
[Header("Cost Settings")]
|
||||||
@@ -44,8 +44,15 @@ public class EnemyPortal : MonoBehaviour
|
|||||||
while (remainingCost > 0 && monsterEntries.Count > 0)
|
while (remainingCost > 0 && monsterEntries.Count > 0)
|
||||||
{
|
{
|
||||||
MonsterEntry selectedEntry = SelectMonsterByWeight();
|
MonsterEntry selectedEntry = SelectMonsterByWeight();
|
||||||
|
MonsterData monsterData = GetMonsterDataFromPrefab(selectedEntry.prefab);
|
||||||
|
|
||||||
if (selectedEntry.monsterData.cost > remainingCost)
|
if (monsterData == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[EnemyPortal] Could not find MonsterData on {selectedEntry.prefab.name}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (monsterData.cost > remainingCost)
|
||||||
{
|
{
|
||||||
if (!CanSpawnAnyMonster(remainingCost))
|
if (!CanSpawnAnyMonster(remainingCost))
|
||||||
{
|
{
|
||||||
@@ -55,7 +62,7 @@ public class EnemyPortal : MonoBehaviour
|
|||||||
}
|
}
|
||||||
|
|
||||||
SpawnEnemy(selectedEntry.prefab);
|
SpawnEnemy(selectedEntry.prefab);
|
||||||
remainingCost -= selectedEntry.monsterData.cost;
|
remainingCost -= monsterData.cost;
|
||||||
spawnedCount++;
|
spawnedCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +76,8 @@ public class EnemyPortal : MonoBehaviour
|
|||||||
{
|
{
|
||||||
foreach (var entry in monsterEntries)
|
foreach (var entry in monsterEntries)
|
||||||
{
|
{
|
||||||
if (entry.monsterData.cost <= remainingCost)
|
MonsterData monsterData = GetMonsterDataFromPrefab(entry.prefab);
|
||||||
|
if (monsterData != null && monsterData.cost <= remainingCost)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -82,7 +90,11 @@ public class EnemyPortal : MonoBehaviour
|
|||||||
float totalWeight = 0f;
|
float totalWeight = 0f;
|
||||||
foreach (var entry in monsterEntries)
|
foreach (var entry in monsterEntries)
|
||||||
{
|
{
|
||||||
totalWeight += entry.monsterData.weight;
|
MonsterData monsterData = GetMonsterDataFromPrefab(entry.prefab);
|
||||||
|
if (monsterData != null)
|
||||||
|
{
|
||||||
|
totalWeight += monsterData.weight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float randomValue = Random.Range(0f, totalWeight);
|
float randomValue = Random.Range(0f, totalWeight);
|
||||||
@@ -90,16 +102,33 @@ public class EnemyPortal : MonoBehaviour
|
|||||||
|
|
||||||
foreach (var entry in monsterEntries)
|
foreach (var entry in monsterEntries)
|
||||||
{
|
{
|
||||||
cumulativeWeight += entry.monsterData.weight;
|
MonsterData monsterData = GetMonsterDataFromPrefab(entry.prefab);
|
||||||
if (randomValue <= cumulativeWeight)
|
if (monsterData != null)
|
||||||
{
|
{
|
||||||
return entry;
|
cumulativeWeight += monsterData.weight;
|
||||||
|
if (randomValue <= cumulativeWeight)
|
||||||
|
{
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return monsterEntries[0];
|
return monsterEntries[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MonsterData GetMonsterDataFromPrefab(GameObject prefab)
|
||||||
|
{
|
||||||
|
if (prefab == null) return null;
|
||||||
|
|
||||||
|
MonsterDataComponent component = prefab.GetComponent<MonsterDataComponent>();
|
||||||
|
if (component != null)
|
||||||
|
{
|
||||||
|
return component.monsterData;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private void SpawnEnemy(GameObject prefab)
|
private void SpawnEnemy(GameObject prefab)
|
||||||
{
|
{
|
||||||
GameObject enemy = Instantiate(prefab, transform);
|
GameObject enemy = Instantiate(prefab, transform);
|
||||||
|
|||||||
62
Assets/Scripts/MonsterDataComponent.cs
Normal file
62
Assets/Scripts/MonsterDataComponent.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using Northbound.Data;
|
||||||
|
using Unity.Netcode;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.AI;
|
||||||
|
|
||||||
|
namespace Northbound
|
||||||
|
{
|
||||||
|
[RequireComponent(typeof(EnemyUnit))]
|
||||||
|
[RequireComponent(typeof(EnemyAIController))]
|
||||||
|
[RequireComponent(typeof(NavMeshAgent))]
|
||||||
|
[RequireComponent(typeof(NetworkObject))]
|
||||||
|
public class MonsterDataComponent : MonoBehaviour
|
||||||
|
{
|
||||||
|
[Header("Data Reference")]
|
||||||
|
[Tooltip("ScriptableObject containing monster data")]
|
||||||
|
public MonsterData monsterData;
|
||||||
|
|
||||||
|
[Header("Auto-Apply Settings")]
|
||||||
|
[Tooltip("Automatically apply stats from monsterData on Awake")]
|
||||||
|
public bool autoApplyOnAwake = true;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
if (autoApplyOnAwake && monsterData != null)
|
||||||
|
{
|
||||||
|
ApplyMonsterData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyMonsterData()
|
||||||
|
{
|
||||||
|
if (monsterData == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("[MonsterDataComponent] monsterData is null", this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnemyUnit enemyUnit = GetComponent<EnemyUnit>();
|
||||||
|
if (enemyUnit != null)
|
||||||
|
{
|
||||||
|
enemyUnit.maxHealth = monsterData.maxHp;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnemyAIController aiController = GetComponent<EnemyAIController>();
|
||||||
|
if (aiController != null)
|
||||||
|
{
|
||||||
|
aiController.moveSpeed = monsterData.moveSpeed;
|
||||||
|
aiController.attackDamage = monsterData.atkDamage;
|
||||||
|
aiController.attackRange = monsterData.atkRange;
|
||||||
|
aiController.attackInterval = monsterData.atkIntervalSec;
|
||||||
|
}
|
||||||
|
|
||||||
|
NavMeshAgent navAgent = GetComponent<NavMeshAgent>();
|
||||||
|
if (navAgent != null)
|
||||||
|
{
|
||||||
|
navAgent.speed = monsterData.moveSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log($"[MonsterDataComponent] Applied data for {monsterData.id} ({monsterData.memo})", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
Assets/Scripts/MonsterDataComponent.cs.meta
Normal file
2
Assets/Scripts/MonsterDataComponent.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d702d872f7bcec54baa1f2ee285fc844
|
||||||
154
GENERIC_IMPORTER.md
Normal file
154
GENERIC_IMPORTER.md
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
# 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,6 +1,6 @@
|
|||||||
id,memo,move_speed,max_hp,atk_range,atk_damage,atk_interval_sec,prefab_path,cost,weight
|
id,memo,move_speed,max_hp,atk_range,atk_damage,atk_interval_sec,prefab_path,cost,weight,mesh_path
|
||||||
101,Grunt(기본),2.6,30,1,3,1.2,Assets/Prefabs/EnemyTest,1,1.0
|
101,Grunt(기본),2.6,30,1,3,1.2,Assets/Prefabs/EnemyTest,1,1.0,Assets/Meshes/Skeleton_Minion.fbx
|
||||||
102,Fast(빠름/약함),3.4,18,1,2,1.0,Assets/Prefabs/MonsterTest,2,0.5
|
102,Fast(빠름/약함),3.4,18,1,2,1.0,Assets/Prefabs/MonsterTest,2,0.5,Assets/Meshes/Skeleton_Minion.fbx
|
||||||
103,Tank(느림/단단),2.0,70,1,4,1.5,Assets/Prefabs/Core,5,0.2
|
103,Tank(느림/단단),2.0,70,1,4,1.5,Assets/Prefabs/Core,5,0.2,Assets/Meshes/Skeleton_Minion.fbx
|
||||||
104,Ranged(원거리/약함),2.4,22,5,2,1.4,Assets/Prefabs/Resource,3,0.333
|
104,Ranged(원거리/약함),2.4,22,5,2,1.4,Assets/Prefabs/Resource,3,0.333,Assets/Meshes/Skeleton_Minion.fbx
|
||||||
105,Elite(소수 정예),2.8,120,1,7,1.3,Assets/Prefabs/ResourcePickup,10,0.1
|
105,Elite(소수 정예),2.8,120,1,7,1.3,Assets/Prefabs/ResourcePickup,10,0.1,Assets/Meshes/Skeleton_Minion.fbx
|
||||||
|
|||||||
|
@@ -16,8 +16,8 @@ TagManager:
|
|||||||
- Enemy
|
- Enemy
|
||||||
- Player
|
- Player
|
||||||
- Obstacle
|
- Obstacle
|
||||||
-
|
- Monster
|
||||||
-
|
-
|
||||||
-
|
-
|
||||||
-
|
-
|
||||||
-
|
-
|
||||||
|
|||||||
Reference in New Issue
Block a user