데이터 파이프라인 개선 및 포탈 로직 생성
csv import 시 자동으로 완전한 프리팹이 생성될 수 있도록 함.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
using Northbound;
|
||||
using Northbound.Data;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections.Generic;
|
||||
@@ -6,7 +8,7 @@ using System.Linq;
|
||||
[CustomEditor(typeof(EnemyPortal))]
|
||||
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()
|
||||
{
|
||||
@@ -22,12 +24,12 @@ public class EnemyPortalEditor : Editor
|
||||
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)
|
||||
{
|
||||
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>();
|
||||
int loadedCount = 0;
|
||||
@@ -35,24 +37,23 @@ public class EnemyPortalEditor : Editor
|
||||
foreach (string guid in guids)
|
||||
{
|
||||
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
|
||||
{
|
||||
monsterData = monsterData,
|
||||
prefab = prefab
|
||||
});
|
||||
loadedCount++;
|
||||
}
|
||||
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++)
|
||||
{
|
||||
SerializedProperty element = entriesProperty.GetArrayElementAtIndex(i);
|
||||
element.FindPropertyRelative("monsterData").objectReferenceValue = entries[i].monsterData;
|
||||
element.FindPropertyRelative("prefab").objectReferenceValue = entries[i].prefab;
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
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.Data;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
@@ -8,12 +9,11 @@ public class EnemyPortal : MonoBehaviour
|
||||
[System.Serializable]
|
||||
public class MonsterEntry
|
||||
{
|
||||
public Northbound.Data.MonsterData monsterData;
|
||||
public GameObject prefab;
|
||||
}
|
||||
|
||||
[Header("Spawn Settings")]
|
||||
[Tooltip("몬스터 데이터 목록 (Editor에서 자동 로드 가능)")]
|
||||
[Tooltip("몬스터 프리팹 목록 (Editor에서 자동 로드 가능)")]
|
||||
[SerializeField] private List<MonsterEntry> monsterEntries = new();
|
||||
|
||||
[Header("Cost Settings")]
|
||||
@@ -44,8 +44,15 @@ public class EnemyPortal : MonoBehaviour
|
||||
while (remainingCost > 0 && monsterEntries.Count > 0)
|
||||
{
|
||||
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))
|
||||
{
|
||||
@@ -55,7 +62,7 @@ public class EnemyPortal : MonoBehaviour
|
||||
}
|
||||
|
||||
SpawnEnemy(selectedEntry.prefab);
|
||||
remainingCost -= selectedEntry.monsterData.cost;
|
||||
remainingCost -= monsterData.cost;
|
||||
spawnedCount++;
|
||||
}
|
||||
|
||||
@@ -69,7 +76,8 @@ public class EnemyPortal : MonoBehaviour
|
||||
{
|
||||
foreach (var entry in monsterEntries)
|
||||
{
|
||||
if (entry.monsterData.cost <= remainingCost)
|
||||
MonsterData monsterData = GetMonsterDataFromPrefab(entry.prefab);
|
||||
if (monsterData != null && monsterData.cost <= remainingCost)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -82,7 +90,11 @@ public class EnemyPortal : MonoBehaviour
|
||||
float totalWeight = 0f;
|
||||
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);
|
||||
@@ -90,16 +102,33 @@ public class EnemyPortal : MonoBehaviour
|
||||
|
||||
foreach (var entry in monsterEntries)
|
||||
{
|
||||
cumulativeWeight += entry.monsterData.weight;
|
||||
if (randomValue <= cumulativeWeight)
|
||||
MonsterData monsterData = GetMonsterDataFromPrefab(entry.prefab);
|
||||
if (monsterData != null)
|
||||
{
|
||||
return entry;
|
||||
cumulativeWeight += monsterData.weight;
|
||||
if (randomValue <= cumulativeWeight)
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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
|
||||
Reference in New Issue
Block a user