손님 비헤이비어 트리 초안 작성

This commit is contained in:
Jeonghyeon Ha 2025-08-21 16:40:49 +09:00
parent 5efc97f64d
commit 8f56d99bf7
29 changed files with 1424 additions and 70 deletions

View File

@ -136,7 +136,7 @@ MonoBehaviour:
m_EditorClassIdentifier:
_interactionType: 1
_executionParameters:
_holdTime: 1
_holdTime: 0.1
_displayParameters:
_messageKey:
_interactionAvailableFlows: 1

Binary file not shown.

View File

@ -120,11 +120,12 @@ MonoBehaviour:
m_EditorClassIdentifier:
_interactionType: 1
_executionParameters:
_holdTime: 1
_holdTime: 0
_displayParameters:
_messageKey:
_interactionAvailableFlows: 1
_aiInteractionPoints: []
autoInitialize: 1
--- !u!114 &4545680930728379745
MonoBehaviour:
m_ObjectHideFlags: 0
@ -377,3 +378,4 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 1235f6bde9304d8f85079f2777bd4b3c, type: 3}
m_Name:
m_EditorClassIdentifier:
_managementType: 0

File diff suppressed because it is too large Load Diff

View File

@ -234,10 +234,18 @@ MonoBehaviour:
m_Data:
m_TaskData: []
m_EventTaskData: []
m_SharedVariableData: []
m_SharedVariableData:
- m_ObjectType: 'Opsive.GraphDesigner.Runtime.Variables.SharedVariable`1[[UnityEngine.GameObject,
UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]'
m_ValueHashes:
m_LongValueHashes: 0d00eb254f8d1b29baa620a07799d549a996976a4a64278927dafeacd28e0b00
m_ValuePositions: 000000000e0000000e0000000f000000
m_Values: 53656c6647616d654f626a65637402ffffffff
m_UnityObjects: []
m_Version: 3.4
m_DisabledEventNodesData: []
m_DisabledLogicNodesData: []
m_UniqueID: -1885404201
m_UniqueID: 1495981264
m_LogicNodePropertiesData: []
m_EventNodePropertiesData: []
m_GroupPropertiesData: []

View File

@ -366,6 +366,9 @@ PrefabInstance:
- targetCorrespondingSourceObject: {fileID: 5259510642736920361, guid: 3db3fc62639929c4ba6031ca4ae6600c, type: 3}
insertIndex: -1
addedObject: {fileID: 8993310060139522557}
- targetCorrespondingSourceObject: {fileID: 5259510642736920361, guid: 3db3fc62639929c4ba6031ca4ae6600c, type: 3}
insertIndex: -1
addedObject: {fileID: -6848683434426724985}
- targetCorrespondingSourceObject: {fileID: 6791841979869644848, guid: 3db3fc62639929c4ba6031ca4ae6600c, type: 3}
insertIndex: -1
addedObject: {fileID: 662634663174340165}
@ -461,6 +464,36 @@ MonoBehaviour:
m_EditorClassIdentifier:
blockOutlineAndGlow: 1
blockOverlay: 1
--- !u!114 &-6848683434426724985
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7316134055819320434}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0cdaa3305fa954c45a80c9662aa6f425, type: 3}
m_Name:
m_EditorClassIdentifier:
m_GraphName: Behavior Tree
m_Index: 0
m_Data:
m_TaskData: []
m_EventTaskData: []
m_SharedVariableData: []
m_DisabledEventNodesData: []
m_DisabledLogicNodesData: []
m_UniqueID: 732308450
m_LogicNodePropertiesData: []
m_EventNodePropertiesData: []
m_GroupPropertiesData: []
m_StartWhenEnabled: 1
m_PauseWhenDisabled: 0
m_UpdateMode: 0
m_EvaluationType: 0
m_MaxEvaluationCount: 1
m_Subtree: {fileID: 0}
--- !u!4 &7511707580127947132 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 4993183601549197863, guid: 3db3fc62639929c4ba6031ca4ae6600c, type: 3}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1fed3b9fae5245cdbf255f627a82a1e6
timeCreated: 1755747969

View File

@ -0,0 +1,57 @@
using System;
using System.Threading.Tasks;
using Opsive.BehaviorDesigner.Runtime;
using Unity.Entities;
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace DDD
{
[RequireComponent(typeof(BehaviorTree))]
[RequireComponent(typeof(RestaurantCustomerBlackboardComponent))]
public class RestaurantCustomerAiComponent : MonoBehaviour, IRestaurantCustomerAi
{
protected BehaviorTree _behaviorTree;
protected RestaurantCustomerBlackboardComponent _blackboardComponent;
private void Awake()
{
_behaviorTree = GetComponent<BehaviorTree>();
_blackboardComponent = GetComponent<RestaurantCustomerBlackboardComponent>();
}
public void InitializeAi(CustomerData inCustomerData)
{
try
{
InitializeAiInternal(inCustomerData);
}
catch (Exception e)
{
// Log
Debug.LogError(e);
throw; // TODO 예외 처리
}
}
private async Task InitializeAiInternal(CustomerData inCustomerData)
{
var customerState = RestaurantState.Instance.CustomerState;
var subtree = customerState.GetLoadedSubtree(inCustomerData.CustomerType);
if (subtree == null)
{
Debug.LogError(
$"[CustomerCharacter] No preloaded subtree found for CustomerType: {inCustomerData.CustomerType}. Make sure CustomerBehaviorData is loaded.");
subtree = await customerState.GetOrLoadSubtree(inCustomerData.CustomerType);
}
_behaviorTree.Subgraph = subtree;
_blackboardComponent.InitializeWithBehaviorTree(subtree);
_blackboardComponent.SetCustomerData(inCustomerData);
// TODO : 1. Subtree - Action, Condition
// TODO : 2. Blackboard
_behaviorTree.StartBehavior();
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: af69e82818254bfa9cabb2dbf9430850
timeCreated: 1755748000

View File

@ -0,0 +1,21 @@
using Opsive.BehaviorDesigner.Runtime;
using UnityEngine;
namespace DDD
{
public class RestaurantCustomerBlackboardComponent : MonoBehaviour, IRestaurantCustomerBlackboard
{
private Subtree _subtree;
public void InitializeWithBehaviorTree(Subtree subtree)
{
_subtree = subtree;
_subtree.SetVariableValue(nameof(RestaurantCustomerBlackboardKey.SelfGameObject), gameObject);
}
public void SetCustomerData(CustomerData inCustomerData)
{
_subtree.SetVariableValue(nameof(RestaurantCustomerBlackboardKey.CustomerData), inCustomerData);;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 784c770c13244dc0a0804056065eaf92
timeCreated: 1755748886

View File

@ -0,0 +1,7 @@
namespace DDD
{
public interface IRestaurantCustomerAi
{
void InitializeAi(CustomerData inCustomerData);
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 308488f2a02448d3853514eff04711fa
timeCreated: 1755748296

View File

@ -0,0 +1,13 @@
namespace DDD
{
public enum RestaurantCustomerBlackboardKey
{
SelfGameObject,
CustomerData,
}
public interface IRestaurantCustomerBlackboard
{
void SetCustomerData(CustomerData inCustomerData);
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a4f20d91da7045e4bc226be60254ef2b
timeCreated: 1755748894

View File

@ -5,48 +5,25 @@
namespace DDD
{
[RequireComponent(typeof(RestaurantCustomerAiComponent))]
public class CustomerCharacter : RestaurantNpcCharacter, ICustomerInitializer
{
private CustomerData _customerData;
protected IRestaurantCustomerAi restaurantCustomerAi;
public async void Initialize(CustomerData customerData)
protected override void Awake()
{
base.Awake();
restaurantCustomerAi = GetComponent<IRestaurantCustomerAi>();
}
public void Initialize(CustomerData customerData)
{
_customerData = customerData;
restaurantCustomerAi.InitializeAi(_customerData);
// 스킨 설정
_spineController.SetSkin(_customerData.SpineSkinKey);
// CustomerType에 따른 behavior tree subtree 할당
await InitializeBehaviorTree();
}
private async Task InitializeBehaviorTree()
{
var customerData = RestaurantData.Instance.CustomerData;
if (customerData?.CustomerBehaviorData?.TryGetValue(_customerData.CustomerType, out var subtreeReference) != true)
{
Debug.LogError($"[CustomerCharacter] No behavior data found for CustomerType: {_customerData.CustomerType}");
return;
}
try
{
var subtree = await subtreeReference.LoadAssetAsync<Subtree>().Task;
if (subtree != null)
{
_behaviorTree.Subgraph = subtree;
_behaviorTree.StartBehavior();
}
else
{
Debug.LogError($"[CustomerCharacter] Failed to load subtree for CustomerType: {_customerData.CustomerType}");
}
}
catch (System.Exception e)
{
Debug.LogError($"[CustomerCharacter] Error loading subtree for CustomerType {_customerData.CustomerType}: {e.Message}");
}
}
}
}

View File

@ -3,16 +3,12 @@
namespace DDD
{
[RequireComponent(typeof(BehaviorTree))]
[RequireComponent(typeof(RestaurantNpcMovement))]
public class RestaurantNpcCharacter : RestaurantCharacter
{
protected BehaviorTree _behaviorTree;
protected override void Awake()
{
base.Awake();
_behaviorTree = GetComponent<BehaviorTree>();
}
}
}

View File

@ -1,5 +1,8 @@
using UnityEngine;
namespace DDD
{
[RequireComponent(typeof(RestaurantPlayerMovement))]
public class RestaurantPlayerCharacter : RestaurantCharacter
{
protected override async void Awake()

View File

@ -27,6 +27,9 @@ public override Task InitializeController()
{
_restaurantCustomerStateSo = RestaurantState.Instance.CustomerState;
_restaurantRunStateSo = RestaurantState.Instance.RunState;
_iCustomerFactory ??= new CustomerFactory();
return Task.CompletedTask;
}
@ -41,6 +44,9 @@ public override async Task OnReadyNewFlow(GameFlowState newFlowState)
{
if (newFlowState == GameFlowState.RunRestaurant)
{
_iCustomerFactory.LoadAssets();
await _restaurantCustomerStateSo.LoadCustomerBehaviorData();
_cts?.Cancel();
_cts?.Dispose();
_cts = new CancellationTokenSource();
@ -52,6 +58,9 @@ public override Task OnExitCurrentFlow(GameFlowState exitingFlowState)
{
if (exitingFlowState == GameFlowState.RunRestaurant)
{
_iCustomerFactory.UnloadAssets();
_restaurantCustomerStateSo.UnloadCustomerBehaviorData();
_cts?.Cancel();
_cts?.Dispose();
_cts = null;
@ -61,8 +70,6 @@ public override Task OnExitCurrentFlow(GameFlowState exitingFlowState)
private async Task StartSpawnLoopAsync(CancellationToken token)
{
_iCustomerFactory ??= new CustomerFactory();
var currentGameLevel = GameState.Instance.LevelState.Level;
_levelDataSo ??= DataManager.Instance.GetDataSo<LevelDataSo>();
_customerDataSo ??= DataManager.Instance.GetDataSo<CustomerDataSo>();
@ -115,7 +122,7 @@ SpawnSchedule MakeSchedule() => scheduleBuilder.Build(new SpawnScheduleBuildArgs
{
var rotation = Quaternion.identity;
_ = _iCustomerFactory.CreateAsync(new CustomerSpawnArgs
await _iCustomerFactory.CreateAsync(new CustomerSpawnArgs
{
CustomerData = customerData,
Position = _restaurantRunStateSo.SpawnPoint,

View File

@ -1,11 +1,15 @@
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
namespace DDD
{
public interface ICustomerFactory
{
Task<GameObject> CreateAsync(CustomerSpawnArgs args);
void LoadAssets();
void UnloadAssets();
}
public interface ICustomerInitializer
@ -24,26 +28,14 @@ public struct CustomerSpawnArgs
public class CustomerFactory : ICustomerFactory
{
private GameObject _customerPrefab;
private AsyncOperationHandle<GameObject> _customerPrefabHandle;
public async Task<GameObject> CreateAsync(CustomerSpawnArgs args)
{
if (!_customerPrefab)
{
var customerDataAsset = RestaurantData.Instance ? RestaurantData.Instance.CustomerData : null;
if (customerDataAsset == null || customerDataAsset.CustomerPrefab == null)
{
Debug.LogError("[CustomerFactory] RestaurantCustomerData or its CustomerPrefab reference is not set or not loaded.");
return null;
}
var handle = customerDataAsset.CustomerPrefab.LoadAssetAsync<GameObject>();
await handle.Task;
if (handle.Result == null)
{
Debug.LogError("[CustomerFactory] Failed to load customer prefab from AssetReference.");
return null;
}
_customerPrefab = handle.Result;
Debug.LogError("[CustomerFactory] Customer prefab is not loaded. Call LoadAssets() first.");
await LoadCustomerAsset();
}
var newCustomer = Object.Instantiate(_customerPrefab, args.Position, args.Rotation, args.Parent);
@ -54,5 +46,47 @@ public async Task<GameObject> CreateAsync(CustomerSpawnArgs args)
}
return newCustomer;
}
public async void LoadAssets()
{
await LoadCustomerAsset();
}
private async Task LoadCustomerAsset()
{
if (_customerPrefab != null)
{
return;
}
var customerDataAsset = RestaurantData.Instance ? RestaurantData.Instance.CustomerData : null;
if (customerDataAsset == null || customerDataAsset.CustomerPrefab == null)
{
Debug.LogError("[CustomerFactory] RestaurantCustomerData or its CustomerPrefab reference is not set.");
return;
}
_customerPrefabHandle = customerDataAsset.CustomerPrefab.LoadAssetAsync<GameObject>();
await _customerPrefabHandle.Task;
if (_customerPrefabHandle.Result == null)
{
Debug.LogError("[CustomerFactory] Failed to load customer prefab from AssetReference.");
return;
}
_customerPrefab = _customerPrefabHandle.Result;
Debug.Log("[CustomerFactory] Customer prefab loaded successfully.");
}
public void UnloadAssets()
{
if (_customerPrefabHandle.IsValid())
{
Addressables.Release(_customerPrefabHandle);
}
_customerPrefab = null;
Debug.Log("[CustomerFactory] Customer prefab unloaded.");
}
}
}

View File

@ -10,6 +10,8 @@ public enum RestaurantOrderType : uint
Reserved = 1u,
Order = 1u << 1,
Serve = 1u << 2,
Busy = 1u << 3,
Dirty = 1u << 4,
}
public class RestaurantOrderInteractionSubsystem : MonoBehaviour, IInteractionSubsystemObject<RestaurantOrderType>

View File

@ -12,7 +12,9 @@ public static class RestaurantOrderSolvers
{ RestaurantOrderType.Wait, typeof(RestaurantOrderSolver_Wait) },
{ RestaurantOrderType.Reserved, typeof(RestaurantOrderSolver_Reserved) },
{ RestaurantOrderType.Order, typeof(RestaurantOrderSolver_Order) },
{ RestaurantOrderType.Serve, typeof(RestaurantOrderSolver_Serve) }
{ RestaurantOrderType.Serve, typeof(RestaurantOrderSolver_Serve) },
{ RestaurantOrderType.Busy, typeof(RestaurantOrderSolver_Busy) },
{ RestaurantOrderType.Dirty, typeof(RestaurantOrderSolver_Dirty) }
};
}

View File

@ -0,0 +1,19 @@
using UnityEngine;
namespace DDD.RestaurantOrders
{
public class RestaurantOrderSolver_Busy : MonoBehaviour, IInteractionSubsystemSolver<RestaurantOrderType>
{
public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null)
{
// TODO : DO SOMETHING!!!
return true;
}
public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null,
ScriptableObject payloadSo = null)
{
return true;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c185b3957ffe47088703be10e709ff66
timeCreated: 1755761370

View File

@ -0,0 +1,19 @@
using UnityEngine;
namespace DDD.RestaurantOrders
{
public class RestaurantOrderSolver_Dirty : MonoBehaviour, IInteractionSubsystemSolver<RestaurantOrderType>
{
public bool ExecuteInteractionSubsystem(IInteractor interactor, IInteractable interactable, ScriptableObject payloadSo = null)
{
// TODO : DO SOMETHING!!!
return true;
}
public bool CanExecuteInteractionSubsystem(IInteractor interactor = null, IInteractable interactable = null,
ScriptableObject payloadSo = null)
{
return true;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b97bc6ce36df4e05a4d329f11daef43f
timeCreated: 1755761294

View File

@ -1,9 +1,106 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Opsive.BehaviorDesigner.Runtime;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
namespace DDD
{
public class RestaurantCustomerState : ScriptableObject
{
private Dictionary<CustomerType, Subtree> _loadedSubtrees = new Dictionary<CustomerType, Subtree>();
private Dictionary<CustomerType, AsyncOperationHandle<Subtree>> _subtreeHandles = new Dictionary<CustomerType, AsyncOperationHandle<Subtree>>();
public async Task LoadCustomerBehaviorData()
{
var customerData = RestaurantData.Instance?.CustomerData;
if (customerData?.CustomerBehaviorData == null)
{
Debug.LogError("[RestaurantCustomerState] RestaurantCustomerData or CustomerBehaviorData is null");
return;
}
var loadTasks = new List<Task>();
foreach (var behaviorPair in customerData.CustomerBehaviorData)
{
var customerType = behaviorPair.Key;
var subtreeReference = behaviorPair.Value;
if (_loadedSubtrees.ContainsKey(customerType))
continue; // Already loaded
loadTasks.Add(LoadSubtreeAsync(customerType, subtreeReference));
}
await Task.WhenAll(loadTasks);
Debug.Log($"[RestaurantCustomerState] Loaded {_loadedSubtrees.Count} customer behavior subtrees");
}
private async Task LoadSubtreeAsync(CustomerType customerType, AssetReference subtreeReference)
{
var handle = Addressables.LoadAssetAsync<Subtree>(subtreeReference);
_subtreeHandles[customerType] = handle;
await handle.Task;
if (handle.Result != null)
{
_loadedSubtrees[customerType] = handle.Result;
Debug.Log($"[RestaurantCustomerState] Loaded subtree for {customerType}");
}
else
{
Debug.LogError($"[RestaurantCustomerState] Failed to load subtree for {customerType}");
}
}
public void UnloadCustomerBehaviorData()
{
foreach (var handle in _subtreeHandles.Values)
{
if (handle.IsValid())
{
Addressables.Release(handle);
}
}
_loadedSubtrees.Clear();
_subtreeHandles.Clear();
Debug.Log("[RestaurantCustomerState] Unloaded all customer behavior subtrees");
}
public Subtree GetLoadedSubtree(CustomerType customerType)
{
_loadedSubtrees.TryGetValue(customerType, out var subtree);
return subtree;
}
public async Task<Subtree> GetOrLoadSubtree(CustomerType customerType)
{
if (IsSubtreeLoaded(customerType))
{
return GetLoadedSubtree(customerType);
}
else
{
var customerData = RestaurantData.Instance?.CustomerData;
if (customerData?.CustomerBehaviorData == null ||
!customerData.CustomerBehaviorData.TryGetValue(customerType, out var subtreeReference))
{
Debug.LogError($"[RestaurantCustomerState] No behavior data found for {customerType}");
return null;
}
await LoadSubtreeAsync(customerType, subtreeReference);
return GetLoadedSubtree(customerType);
}
}
public bool IsSubtreeLoaded(CustomerType customerType)
{
return _loadedSubtrees.ContainsKey(customerType);
}
}
}

BIN
ProjectSettings/EditorBuildSettings.asset (Stored with Git LFS)

Binary file not shown.