### C# FloorGrid Building Placement Example Source: https://context7.com/alankalles/poplifesimulator/llms.txt Demonstrates how to use the FloorGrid system in C# for initializing the grid, placing buildings transactionally, moving, removing, converting coordinates, and querying building information. This example requires Unity and the PopLife runtime/data libraries. It interacts with `FloorGrid`, `BuildingArchetype`, and various `BuildingInstance` types. ```csharp using UnityEngine; using PopLife.Runtime; using PopLife.Data; public class BuildingPlacementExample : MonoBehaviour { public FloorGrid floor; public BuildingArchetype shelfArchetype; void Start() { // Initialize grid (usually done automatically) floor.Init(); // Get grid info Debug.Log($"Grid size: {floor.gridSize}"); Debug.Log($"Cell size: {floor.cellSize}"); Debug.Log($"Floor ID: {floor.floorId}"); } void PlaceBuilding() { Vector2Int position = new Vector2Int(5, 0); // Grid coordinates int rotation = 0; // 0, 1, 2, 3 (0/90/180/270 degrees) // Check if placement is valid var footprint = shelfArchetype.GetRotatedFootprint(rotation); bool canPlace = floor.CanPlaceFootprint(footprint, position); if (canPlace) { // Transactional placement - automatically validates resources // Returns null if blueprint missing or insufficient money BuildingInstance instance = floor.PlaceBuildingTransactional( shelfArchetype, position, rotation ); if (instance != null) { Debug.Log($"Placed {instance.archetype.displayName} at {position}"); Debug.Log($"Instance ID: {instance.instanceId}"); // Access instance properties if (instance is ShelfInstance shelf) { Debug.Log($"Max stock: {shelf.maxStock}"); Debug.Log($"Price: ${shelf.currentPrice}"); } } else { Debug.Log("Placement failed - check resources or blueprint"); } } else { Debug.Log("Cannot place building at position - occupied or invalid"); } } void MoveBuilding() { // Get building instance BuildingInstance building = GetBuildingToMove(); Vector2Int newPosition = new Vector2Int(8, 0); int newRotation = 1; // Move building (charges moveCost) bool success = floor.MoveBuilding(building, newPosition, newRotation); if (success) { Debug.Log($"Moved building to {newPosition}, rotation: {newRotation * 90}°"); } else { Debug.Log("Move failed - invalid position or insufficient funds"); } } void RemoveBuilding() { BuildingInstance building = GetBuildingToRemove(); bool refundBlueprint = true; // Remove from grid (optionally refund blueprint) floor.RemoveBuilding(building, refundBlueprint); Destroy(building.gameObject); } void CoordinateConversion() { // World to grid Vector3 worldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition); Vector2Int gridPos = floor.WorldToGrid(worldPos); // Grid to world Vector3 worldPosition = floor.GridToWorld(gridPos); Debug.Log($"World {worldPos} -> Grid {gridPos} -> World {worldPosition}"); } void QueryBuildings() { // Get all shelves on this floor foreach (var shelf in floor.AllShelves()) { Debug.Log($"Shelf: {shelf.archetype.displayName}, Stock: {shelf.currentStock}"); } // Get all facilities foreach (var facility in floor.AllFacilities()) { Debug.Log($"Facility: {facility.archetype.displayName}"); } // Check for specific facility type bool hasCashier = floor.HasFacilityOfType(FacilityType.Cashier); Debug.Log($"Has cashier: {hasCashier}"); } BuildingInstance GetBuildingToMove() => null; // Implement selection logic BuildingInstance GetBuildingToRemove() => null; } ``` -------------------------------- ### Emergency Scenarios - VIP Priority Service Example Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/StoreClosingDoc/StoreClosing_Implementation_Summary.md Illustrates how VIP customers can be prioritized using behavior trees. This example shows a `SkipQueueAction` and `DirectServiceAction`, demonstrating the flexibility of the system for differentiated customer treatment. ```text Condition: IsVIPCustomer └─ SkipQueueAction └─ DirectServiceAction ``` -------------------------------- ### Strategy Pattern Example for AI Policies Source: https://github.com/alankalles/poplifesimulator/blob/main/CLAUDE.md Demonstrates the implementation of the Strategy pattern in C# for customer AI decision-making. It shows an abstract base class for policies and placeholders for concrete implementations. ```csharp 抽象基类:TargetSelectorPolicy, PurchasePolicy, CheckoutPolicy... 具体实现:WeightedRandomSelector, RandomPurchasePolicy... ``` -------------------------------- ### Accessing and Managing ShelfInstance Properties (C#) Source: https://context7.com/alankalles/poplifesimulator/llms.txt Demonstrates how to access and manage various properties of a ShelfInstance, including product category, stock levels, pricing, sales statistics, queue length, and attractiveness. It also shows examples of customer interactions like trying to take or sell items, and inventory management tasks such as restocking and resetting daily sales. ```csharp using UnityEngine; using PopLife.Runtime; using PopLife.Data; public class ShelfManagementExample : MonoBehaviour { public ShelfInstance shelf; void Start() { // Access shelf properties Debug.Log($"Product: {GetShelfCategory(shelf)}"); Debug.Log($"Current stock: {shelf.currentStock}/{shelf.maxStock}"); Debug.Log($"Price: ${shelf.currentPrice}"); Debug.Log($"Today's sales: {shelf.todaySales}"); Debug.Log($"Queue length: {shelf.GetQueueLength()}"); // Get attractiveness (level × category multiplier) float attractiveness = shelf.GetAttractiveness(); Debug.Log($"Attractiveness: {attractiveness}"); } void CustomerInteraction() { // TryTakeOne: Reduces stock, does NOT add player money bool taken = shelf.TryTakeOne(); if (taken) { Debug.Log($"Item taken. Stock: {shelf.currentStock}"); Debug.Log("Money will be added when customer checks out at cashier"); } // TrySellOne: Legacy method that reduces stock AND adds money immediately // Used for direct sales scenarios outside normal customer flow bool sold = shelf.TrySellOne(); if (sold) { Debug.Log("Item sold directly, money added immediately"); } } void InventoryManagement() { // Check if operational if (!shelf.isOperational) { Debug.Log("Shelf is not operational (needs power, maintenance, etc.)"); return; } // Restock shelf (sets stock to maxStock) shelf.Restock(); Debug.Log($"Restocked to {shelf.maxStock} units"); // Reset daily sales shelf.todaySales = 0; } void UpgradeShelf() { // Upgrade building (inherited from BuildingInstance) bool upgraded = shelf.TryUpgrade(); if (upgraded) { // Upgrade effects (from OnUpgraded override): // - maxStock increased // - currentStock set to maxStock (auto-refill) // - currentPrice updated // - attractiveness increased Debug.Log($"Upgraded to level {shelf.currentLevel}"); Debug.Log($"New max stock: {shelf.maxStock}"); Debug.Log($"New price: ${shelf.currentPrice}"); } else { Debug.Log("Cannot upgrade - insufficient Fame or max level reached"); } } void QueueSystem() { // Get interaction point for customer pathfinding Transform interactionPoint = shelf.GetInteractionPoint(); // Queue automatically managed by ShelfQueueController component: // - Position 0: interactionAnchor (front of queue) // - Position 1+: predefined queueSlots or dynamic calculation int queueLength = shelf.GetQueueLength(); Debug.Log($"Customers in queue: {queueLength}"); } ProductCategory GetShelfCategory(ShelfInstance shelf) { var shelfArchetype = (ShelfArchetype)shelf.archetype; return shelfArchetype.category; } // Save/Load system void SaveShelfState() { BuildingSaveData saveData = shelf.GetSaveData(); string json = JsonUtility.ToJson(saveData); // Save to file or database Debug.Log($"Saved shelf state: {json}"); } void LoadShelfState(string json) { BuildingSaveData saveData = JsonUtility.FromJson(json); shelf.LoadFromSaveData(saveData); Debug.Log($"Loaded shelf: Stock={shelf.currentStock}, Sales={shelf.todaySales}"); } } ``` -------------------------------- ### Sequencer 'Go To Exit' Node Setup (NodeCanvas) Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/StoreClosingDoc/StoreClosing_Unity_Setup_Guide.md Defines the actions within a 'Go To Exit' Sequencer node for customer behavior. It utilizes custom actions like SelectExitPointAction and MoveToTargetAction, along with standard SetVariable and SetUrgentMoveSpeedAction, to guide customers to an exit point. ```NodeCanvas Action 1: SelectExitPointAction - Type: `PopLife/Customer/SelectExitPointAction` - Blackboard Variables: - `targetExitPoint`: Connect to blackboard's `targetExitPoint` - `targetExitId`: Connect to blackboard's `targetExitId` Action 2: SetVariable (HasReachedTarget = false) - Type: `NodeCanvas/Tasks/Actions/SetVariable` - Configuration: - `valueA`: Connect to blackboard's `Has Reached Target` - `valueB`: `false` Action 3: SetUrgentMoveSpeedAction - Type: `PopLife/Store/SetUrgentMoveSpeedAction` - Parameters: - `urgentSpeedMultiplier`: `2.0` Action 4: MoveToTargetAction - Type: `PopLife/Customer/MoveToTargetAction` - Blackboard Variables: - `assignedQueueSlot`: Connect to blackboard's `targetExitPoint` - `moveSpeed`: Connect to blackboard's `moveSpeed` - `hasReachedTarget`: Connect to blackboard's `Has Reached Target` Action 5: DestroyAgentAction - Type: `PopLife/Customer/DestroyAgentAction` - Parameters: - `delay`: `0` ``` -------------------------------- ### Emergency Scenarios - Robbery Event Example Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/StoreClosingDoc/StoreClosing_Implementation_Summary.md Shows an example of implementing a robbery event within the behavior tree framework. It utilizes a selector node to choose between hiding or fleeing, reusing actions like `SetUrgentMoveSpeedAction`. ```text Condition: CheckRobberyInProgress └─ HideOrFlee Selector ├─ FindHidingSpot └─ RunToExit (复用 SetUrgentMoveSpeedAction) ``` -------------------------------- ### Emergency Scenarios - Fire Evacuation Example Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/StoreClosingDoc/StoreClosing_Implementation_Summary.md Demonstrates how the existing `ForceExitShoppingLoopAction` can be reused and extended with new actions like `PanicMovement` for other emergency scenarios such as fire evacuation. This highlights the reusability of behavior tree nodes. ```text Condition: CheckFireAlarm └─ ForceExitShoppingLoopAction (复用) └─ PanicMovement (更高速度倍率 ×3) └─ ExitBuilding ``` -------------------------------- ### Implementing a Custom AI Strategy in C# Source: https://github.com/alankalles/poplifesimulator/blob/main/CLAUDE.md Shows how to create a custom AI strategy by inheriting from a base policy class in C#. The example defines a `MyTargetSelector` class that overrides the `SelectTargetShelf` method to implement custom decision-making logic. ```csharp // 1. 继承对应的 XxxPolicy 基类 public class MyTargetSelector : TargetSelectorPolicy { public override int SelectTargetShelf( CustomerContext ctx, ShelfSnapshot[] candidates) { // 实现自定义决策逻辑 return bestShelfIndex; } } // 2. 创建 ScriptableObject 实例 // 3. 分配到 CustomerArchetype.defaultPolicies ``` -------------------------------- ### Debug Log Examples - Console Output Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/StoreClosingDoc/StoreClosing_BehaviorTree_Design.md Provides examples of log output generated by new behavior tree nodes during debugging. These logs help track the execution flow and state of customer actions within the simulation. ```log [CheckStoreClosing] Closing time: true [ForceExitShoppingLoop] Customer C001 exited shopping loop [SetUrgentMoveSpeed] Urgent mode: speed = 6.0 [EmergencyCheckout] Customer C001 paid $150 ``` -------------------------------- ### Behavior Tree Structure Example Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/StoreClosingDoc/StoreClosing_Implementation_Summary.md Illustrates the hierarchical structure of the new behavior tree, emphasizing priority selectors and sequencers for emergency exit logic. This structure is designed to be implemented within the Unity editor using NodeCanvas. ```text Root: Priority Selector │ ├─── [Branch 1 - 最高优先级] Emergency Exit │ Condition: CheckStoreClosingCondition │ │ │ └─── Sequencer "Emergency Exit Sequence" │ ├─── ForceExitShoppingLoopAction (清理购物状态) │ │ │ ├─── Priority Selector "Exit Strategy" │ │ ├─── [A] CheckPendingPaymentCondition → Rush Checkout │ │ │ └─── Sequencer │ │ │ ├─── SetUrgentMoveSpeedAction │ │ │ ├─── SelectCashierAction (修改版) │ │ │ ├─── AcquireQueueSlotAction │ │ │ ├─── MoveToTargetAction │ │ │ ├─── SkipWaitIfClosingAction (0.1s) │ │ │ ├─── Fallback Selector │ │ │ │ ├─── ExecuteCheckoutAction │ │ │ │ └─── EmergencyCheckoutAction │ │ │ └─── ReleaseQueueSlotAction │ │ │ │ │ └─── [B] No Payment → Direct Exit │ │ │ └─── Sequencer "Go To Exit" │ ├─── SelectExitPointAction │ ├─── SetVariable (HasReachedTarget = false) │ ├─── SetUrgentMoveSpeedAction │ ├─── MoveToTargetAction │ └─── DestroyAgentAction │ └─── [Branch 2 - 正常优先级] Normal Shopping & Checkout (原有流程) ``` -------------------------------- ### SavePathManager for Cross-Platform Path Management (C#) Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/CustomerSpawnDoc/SpawnerRefactorLog.md Manages save paths across different platforms, distinguishing between editor and runtime environments. It automatically copies initial data on the first run and provides interfaces for getting read and write paths. This utility class ensures consistent data storage regardless of the operating system or build type. ```csharp using System; using System.IO; using UnityEngine; public static class SavePathManager { private const string CUSTOMER_DATA_FILENAME = "Customers.json"; private const string PROFILE_DATA_FILENAME = "SpawnerProfile.json"; public static string GetReadPath(string filename) { #if UNITY_EDITOR return Path.Combine(Application.streamingAssetsPath, filename); #else return Path.Combine(Application.persistentDataPath, filename); #endif } public static string GetWritePath(string filename) { #if UNITY_EDITOR // In editor, we might want to write to persistentDataPath for testing, or StreamingAssets for initial setup. // For this refactor, StreamingAssets is used for initial data, and persistentDataPath for runtime changes. // However, the prompt specifies StreamingAssets for editor reads, so we'll stick to that for consistency in read path. // For writing, if we want changes to persist in editor for testing, persistentDataPath is better. // Let's align with the prompt's implication for editor mode reading from StreamingAssets. // For writing in editor, it's a bit ambiguous without more context, but typically changes should go to persistentDataPath. return Path.Combine(Application.persistentDataPath, filename); // Assuming editor writes should be persistent for testing #else return Path.Combine(Application.persistentDataPath, filename); #endif } public static string GetCustomerDataReadPath() => GetReadPath(CUSTOMER_DATA_FILENAME); public static string GetCustomerDataWritePath() => GetWritePath(CUSTOMER_DATA_FILENAME); public static string GetSpawnerProfileReadPath() => GetReadPath(PROFILE_DATA_FILENAME); public static string GetSpawnerProfileWritePath() => GetWritePath(PROFILE_DATA_FILENAME); public static void EnsureInitialDataCopied() { string persistentPath = Application.persistentDataPath; string streamingAssetsPath = Application.streamingAssetsPath; // Ensure persistent data directory exists if (!Directory.Exists(persistentPath)) { Directory.CreateDirectory(persistentPath); } // Copy Customers.json if it doesn't exist in persistent data path string destCustomerPath = GetCustomerDataWritePath(); if (!File.Exists(destCustomerPath)) { string sourceCustomerPath = Path.Combine(streamingAssetsPath, CUSTOMER_DATA_FILENAME); if (File.Exists(sourceCustomerPath)) { File.Copy(sourceCustomerPath, destCustomerPath); Debug.Log($"Copied initial Customers.json from {{sourceCustomerPath}} to {{destCustomerPath}}"); } else { Debug.LogError($"Initial Customers.json not found in StreamingAssets: {{sourceCustomerPath}}"); } } // Copy SpawnerProfile.json if it doesn't exist in persistent data path string destProfilePath = GetSpawnerProfileWritePath(); if (!File.Exists(destProfilePath)) { string sourceProfilePath = Path.Combine(streamingAssetsPath, PROFILE_DATA_FILENAME); if (File.Exists(sourceProfilePath)) { File.Copy(sourceProfilePath, destProfilePath); Debug.Log($"Copied initial SpawnerProfile.json from {{sourceProfilePath}} to {{destProfilePath}}"); } else { Debug.LogError($"Initial SpawnerProfile.json not found in StreamingAssets: {{sourceProfilePath}}"); } } } } ``` -------------------------------- ### TimePreference Data Structure and Logic (C#) Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/CustomerSpawnDoc/SpawnerRefactorLog.md Defines a data structure for time ranges, including start and end hours. It provides methods to check if a given time falls within the range, calculate the center of the time range, and determine its duration. This is used for customer spawning preferences based on time of day. ```csharp using System; using UnityEngine; [Serializable] public struct TimePreference { public float startTime; public float endTime; public bool IsInRange(float timeOfDay) { // Handles cases where the time range spans midnight (e.g., 22:00 to 02:00) if (startTime > endTime) { return timeOfDay >= startTime || timeOfDay < endTime; } else { return timeOfDay >= startTime && timeOfDay < endTime; } } public float GetCenter() { if (startTime > endTime) // Spans midnight { // If the range is large, like 12 hours spanning midnight, the center is conceptually tricky. // For simplicity, we can average, but need to consider wrap-around. // Example: 22:00 (22) to 02:00 (2). Midpoint is 00:00 (0). // (22 + 2) / 2 = 12. But this is incorrect. Need to adjust for wrap-around. // A better approach is to normalize times to a 24h cycle and find the midpoint. // Or, consider the duration and add half of it to the start time, handling wrap-around. float duration = (24f - startTime) + endTime; float center = startTime + (duration / 2f); return center % 24f; } else { return (startTime + endTime) / 2f; } } public float GetDuration() { if (startTime > endTime) // Spans midnight { return (24f - startTime) + endTime; } else { return endTime - startTime; } } } ``` -------------------------------- ### 配置 ShelfQueueController 组件 (Unity) Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/QUEUE_SYSTEM_SETUP.md 此代码片段展示了如何在 Unity 中为货架 Prefab 添加和配置 ShelfQueueController 组件。它列出了重要的字段及其推荐值,用于定义货架的交互点、队列起点、队列方向、队位间距、服务时间等。 ```csharp 1. 选中货架 Prefab 2. Add Component → ShelfQueueController 3. 配置以下字段: | 字段 | 说明 | 推荐值 | |------|------|--------| | **Interaction Anchor** | 交互点(顾客购买时站立位置) | 创建空 GameObject 放在货架前沿 | | **Queue Anchor** | 队列起点(第一个排队位置) | 创建空 GameObject 放在交互点后方1米 | | **Queue Direction** | 队列方向 | (0, -1, 0) 向下排队 | | **Slot Spacing** | 队位间距 | 1.0 米 | | **Queue Slots** (可选) | 预设队位数组 | 留空使用自动计算 | | **Seconds Per Customer** | 每位顾客服务时间 | 10 秒 | | **Arrival Distance** | 到达容忍距离(RVO兼容) | 0.8 米 | ``` -------------------------------- ### Customer Prefab Component Configuration (Unity) Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/StoreClosingDoc/StoreClosing_Unity_Setup_Guide.md Details the essential components required for the Customer prefab in Unity. This includes AI pathfinding components (A* Pathfinding), simulation logic (CustomerAgent, CustomerBlackboardAdapter), and behavior tree execution (BehaviourTree, Blackboard). ```Unity C# - `CustomerAgent` - `CustomerBlackboardAdapter` - `BehaviourTree` (NodeCanvas) - `Blackboard` (NodeCanvas) - `FollowerEntity` (A* Pathfinding) - `AIDestinationSetter` (A* Pathfinding) ``` -------------------------------- ### 顾客 Prefab 组件要求 (Unity) Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/QUEUE_SYSTEM_SETUP.md 此代码片段列出了顾客 Prefab 所需的核心组件,以支持队列系统和寻路功能。包括用于 AI 行为的 CustomerAgent, BlackboardAdapter, FollowerEntity, AIDestinationSetter, RVOController, 以及 NodeCanvas 的 BehaviourTree 和 Blackboard。 ```csharp customer1.prefab ├── CustomerAgent ├── CustomerBlackboardAdapter ├── FollowerEntity (A* Pathfinding) ├── AIDestinationSetter (A* Pathfinding) ├── RVOController (A* Pathfinding, 可选但推荐) ├── BehaviourTree (NodeCanvas) └── Blackboard (NodeCanvas) ``` -------------------------------- ### 配置 CashierQueueController 组件 (Unity) Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/QUEUE_SYSTEM_SETUP.md 此代码片段展示了如何在 Unity 中为收银台 Prefab 添加和配置 CashierQueueController 组件。配置字段与 ShelfQueueController 类似,但推荐值针对收银台的特性进行了调整,例如更长的服务时间。 ```csharp 1. 选中收银台 Prefab 2. Add Component → CashierQueueController 3. 配置字段(同货架配置) | 字段 | 推荐值 | |------|--------| | **Interaction Anchor** | 收银台前方 0.5 米 | | **Queue Anchor** | 收银台前方 1.5 米 | | **Queue Direction** | (0, -1, 0) 或 (-1, 0, 0) | | **Slot Spacing** | 1.0 米 | | **Seconds Per Customer** | 15 秒 (结账通常比浏览慢) | | **Arrival Distance** | 0.8 米 | ``` -------------------------------- ### Unity NodeCanvas: 配置 'Rush Checkout' Sequencer Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/StoreClosingDoc/StoreClosing_Unity_Setup_Guide.md 配置一个 'Rush Checkout' Sequencer,用于在商店关闭时快速完成客户的结账流程。包含设置移动速度、选择收银台、排队、移动、跳过等待、执行结账和释放队列等一系列动作。 ```plaintext Sequencer "Rush Checkout" ├─ Action: SetUrgentMoveSpeedAction (urgentSpeedMultiplier: 2.0) ├─ Action: SelectCashierAction (policies, targetCashierId, goalCell) ├─ Action: AcquireQueueSlotAction (targetCashierId, assignedQueueSlot) ├─ Action: MoveToTargetAction (assignedQueueSlot, moveSpeed, hasReachedTarget, stoppingDistance: 0.5) ├─ Action: SkipWaitIfClosingAction (normalWaitTime: 1.0, urgentWaitTime: 0.1) ├─ Action: ExecuteCheckoutAction └─ Action: ReleaseQueueSlotAction (targetCashierId) ``` -------------------------------- ### Initialize CustomerAgent with CustomerRecord - C# Source: https://context7.com/alankalles/poplifesimulator/llms.txt This C# snippet demonstrates how to manually initialize a CustomerAgent instance. It involves creating a CustomerRecord with various attributes like ID, name, appearance, traits, and personalized interests. The agent is then instantiated and initialized with the record, archetype, traits, and other game-specific parameters. Dependencies include Unity Engine, CustomerRuntime, and CustomerData namespaces. ```csharp using UnityEngine; using PopLife.Customers.Runtime; using PopLife.Customers.Data; public class CustomerAgentExample : MonoBehaviour { public CustomerAgent agentPrefab; public CustomerArchetype officeWorkerArchetype; public Trait[] customerTraits; // e.g., Male, Straight, Single public AppearanceDatabase appearanceDB; void SpawnCustomerManually() { // Create customer record CustomerRecord record = new CustomerRecord { customerId = "customer_007", name = "John Smith", archetypeId = "OfficeWorker", appearanceId = "male_01", traitIds = new string[] { "Male", "Straight", "Single" }, // Personalized interest deltas (makes each customer unique) interestPersonalDelta = new float[] { 0.5f, -0.2f, 0.3f, 0f, 0.1f, -0.5f }, // Persistence data loyaltyLevel = 1, trustValue = 0, totalVisits = 0, lifetimeSpent = 0, walletCapBase = 100 }; // Instantiate at spawn point Transform spawnPoint = GameObject.Find("SpawnPoint").transform; CustomerAgent agent = Instantiate(agentPrefab, spawnPoint.position, Quaternion.identity); // Initialize customer int categoriesCount = 6; // ProductCategory enum count int daySeed = Random.Range(0, int.MaxValue); agent.Initialize(record, officeWorkerArchetype, customerTraits, categoriesCount, daySeed); // Agent is now active with: // - Visual appearance set from appearanceDB // - Final interests calculated (archetype + personal + traits) // - Money bag sampled from wallet capacity curve // - Embarrassment cap calculated from curves // - Behavior tree variables injected via CustomerBlackboardAdapter Debug.Log($"Spawned customer: {agent.customerID}"); } void AccessCustomerData() { CustomerAgent agent = FindAnyObjectByType(); if (agent != null) { Debug.Log($"Customer ID: {agent.customerID}"); // Access blackboard adapter for runtime state var bb = agent.bb; Debug.Log($"Money bag: ${bb.moneyBag}"); Debug.Log($"Embarrassment: {bb.embarrassment}/{bb.embarrassmentCap}"); Debug.Log($"Pending payment: ${bb.pendingPayment}"); Debug.Log($"Target shelf: {bb.targetShelfId}"); Debug.Log($"Target cashier: {bb.targetCashierId}"); } } } ``` -------------------------------- ### Modify SelectTargetShelfAction for Closing Time in C# Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/StoreClosingDoc/StoreClosing_BehaviorTree_Design.md This modification to the `SelectTargetShelfAction` script allows the agent to skip shelf selection if it's closing time and the customer has pending payments. It depends on `CustomerBlackboardAdapter` for closing time and payment status. It prevents customers from starting new shopping trips when the store is closing. ```C# protected override void OnExecute() { var adapter = agent.GetComponent(); // 【新增】闭店检查 if (adapter != null && adapter.isClosingTime) { if (adapter.pendingPayment > 0) { // 有待结账金额 → 跳过购物,返回失败触发 Repeater 退出 Debug.Log($"[SelectTargetShelf] Store closing, customer {adapter.customerId} skip shopping (pending: ${adapter.pendingPayment})"); EndAction(false); return; } // 无待结账金额 → 继续正常选货架(可能需要买东西) } // 原有逻辑... } ``` -------------------------------- ### Unity NodeCanvas: 添加黑板变量 'isClosingTime' Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/StoreClosingDoc/StoreClosing_Unity_Setup_Guide.md 在 NodeCanvas 行为树的 Blackboard 中添加一个名为 'isClosingTime' 的布尔类型变量。此变量用于控制商店是否进入闭店状态,是实现闭店机制的基础。 ```plaintext 1. 在 Blackboard 面板中,点击 [+ Add Variable]。 2. 选择类型:Boolean。 3. 变量名:isClosingTime。 4. 默认值:false。 ``` -------------------------------- ### Unity NodeCanvas: 配置 'ForceExitShoppingLoopAction' Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/StoreClosingDoc/StoreClosing_Unity_Setup_Guide.md 配置一个 'ForceExitShoppingLoopAction' 节点,用于强制客户立即退出当前的购物循环。此节点是紧急退出流程的一部分,无需额外参数,会自动读取黑板变量。 ```plaintext 1. 添加 Action 节点。 2. 选择类型:PopLife/Store/ForceExitShoppingLoopAction。 3. 无需配置参数。 ``` -------------------------------- ### Unity NodeCanvas: 配置 'Go To Exit' Sequencer Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/StoreClosingDoc/StoreClosing_Unity_Setup_Guide.md 配置一个 'Go To Exit' Sequencer,用于引导客户离开商店。包含选择出口、设置目标、调整移动速度、移动到目标点以及销毁代理等一系列动作。 ```plaintext Sequencer "Go To Exit" ├─ Action: SelectExitPointAction ├─ Action: SetVariable (HasReachedTarget = false) ├─ Action: SetUrgentMoveSpeedAction ├─ Action: MoveToTargetAction └─ Action: DestroyAgentAction ``` -------------------------------- ### Programmatic Spawner Profile Management in C# Source: https://context7.com/alankalles/poplifesimulator/llms.txt Demonstrates how to programmatically create and manage the SpawnerProfile, which stores unlocked customer data. It shows loading an existing profile, unlocking customers, and saving the changes. The profile is saved to JSON files in either the StreamingAssets or persistent data path, depending on the environment. ```csharp // Example: Creating SpawnerProfile programmatically public class SpawnerProfileManager : MonoBehaviour { void CreateProfile() { // Profile is automatically saved to: // StreamingAssets/SpawnerProfile.json (editor) // persistentDataPath/SpawnerProfile.json (build) var profile = SpawnerProfile.Load(); // Add initial unlocked customers profile.UnlockCustomer("customer_001"); profile.UnlockCustomer("customer_002"); profile.Save(); } } ``` -------------------------------- ### Test Scene Configuration (Unity) Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/StoreClosingDoc/StoreClosing_Unity_Setup_Guide.md Steps for creating a dedicated test scene in Unity for validating store closing functionality. It involves duplicating the main scene, setting up essential store elements (shelves, cashiers), and configuring the DayLoopManager for accelerated testing. ```Unity Editor 1. Create Test Scene: - Duplicate `Scenes/Main.unity` to `Scenes/Main_ClosingTest.unity` - Add 1-2 shelves and 1 cashier. - Configure `DayLoopManager`: - `realSecondsPerDay`: `60` (1 minute = 1 day for testing) ``` -------------------------------- ### NodeCanvas Blackboard 变量配置 Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/QUEUE_SYSTEM_SETUP.md 此代码片段列出了 NodeCanvas Blackboard 中所需的关键变量,用于在行为树节点之间传递信息。包括目标货架/收银台 ID、分配的队列位置、移动速度等。 ```csharp | 变量名 | 类型 | 说明 | |--------|------|------| | `targetShelfId` | String | 目标货架 ID | | `targetCashierId` | String | 目标收银台 ID | | `assignedQueueSlot` | Transform | 分配的队列位置 | | `moveSpeed` | Float | 移动速度 | | `hasReachedTarget` | Bool | 是否到达目标 | | `policies` | BehaviorPolicySet | 策略集合 | ``` -------------------------------- ### BehaviourTree and Blackboard Configuration (Unity) Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/StoreClosingDoc/StoreClosing_Unity_Setup_Guide.md Configuration steps for the BehaviourTree and Blackboard components on the Customer prefab in Unity. This involves assigning the behavior tree asset, linking the blackboard, and setting the update mode for the BehaviourTree, along with defining and verifying essential blackboard variables. ```Unity Inspector 1. BehaviourTree Component: - Behaviour Tree: Drag `CustomerBehaviorTree.asset` - Blackboard: Point to the `Blackboard` component on the same GameObject - Update Mode: `Normal Update` 2. Blackboard Component: - Click 'Edit Blackboard Variables' - Confirm essential variables: - `customerId` (String) - `loyaltyLevel` (Int) - `moneyBag` (Int) - `moveSpeed` (Float) - `targetShelfId` (String) - `targetCashierId` (String) - `targetExitPoint` (Transform) - `assignedQueueSlot` (Transform) - `pendingPayment` (Int) - `purchaseQuantity` (Int) - `goalCell` (Vector2Int) - `policies` (BehaviorPolicySet) - `Has Reached Target` (Boolean) - `isClosingTime` (Boolean) ← New ``` -------------------------------- ### 使用预设队位配置 (Unity) Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/QUEUE_SYSTEM_SETUP.md 此代码片段说明如何在 Unity 中使用预设队位来创建固定队列形状。通过在货架下创建空 GameObject 作为队位,并在 ShelfQueueController 中指定它们。 ```csharp 1. 在货架下创建多个空 GameObject ShelfPrefab ├── QueueSlot1 ├── QueueSlot2 ├── QueueSlot3 └── QueueSlot4 2. 在 ShelfQueueController 中: Queue Slots[0] = QueueSlot1 Queue Slots[1] = QueueSlot2 ... ``` -------------------------------- ### 排查顾客无法移动问题 (Unity) Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/QUEUE_SYSTEM_SETUP.md 此检查清单提供了排查顾客在 Unity 中无法移动问题的步骤。它涵盖了确保顾客 Prefab 拥有必要的寻路组件,以及 A* 导航网格是否已扫描。 ```csharp - 顾客 Prefab 有 `FollowerEntity` 组件 - 顾客 Prefab 有 `AIDestinationSetter` 组件 - A* 导航网格已扫描 (`AstarPath.Scan()`) - `MoveToTargetAction` 的 `assignedQueueSlot` 参数已连接到黑板变量 ``` -------------------------------- ### CustomerSpawner Configuration (Unity) Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/StoreClosingDoc/StoreClosing_Unity_Setup_Guide.md Configuration details for the CustomerSpawner GameObject in the Unity scene. This involves assigning the updated Customer prefab, defining spawn points, setting the maximum number of customers, and ensuring the script incorporates logic to stop spawning customers when the store closes. ```Unity Inspector 1. CustomerSpawner GameObject: - Customer Prefab: Drag the updated `Customer.prefab` - Spawn Points: Configure spawn point array - Max Customers On Floor: Set a limit (e.g., `10`) - Script must include `StopSpawning()` method with closing logic. ``` -------------------------------- ### Unity NodeCanvas: 配置 'Direct Exit' Sequencer Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/StoreClosingDoc/StoreClosing_Unity_Setup_Guide.md 配置一个 'Direct Exit' Sequencer 作为 'Exit Strategy' PrioritySelector 的备选分支。当客户没有待处理付款时,此 Sequencer 将被执行,用于客户直接离开商店的逻辑。 ```plaintext Sequencer "Direct Exit" (fallback) - 暂时留空或添加调试日志节点。 ``` -------------------------------- ### 排查队列前移不工作问题 (Unity) Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/QUEUE_SYSTEM_SETUP.md 此检查清单帮助排查 Unity 中队列前移不工作的问题。关键检查点包括是否在购买后调用了 ReleaseQueueSlotAction,以及顾客 GameObject 的命名和组件配置。 ```csharp - 购买完成后调用了 `ReleaseQueueSlotAction` - 顾客 GameObject 的名称是 `customerId` (用于查找) - 顾客有 `CustomerBlackboardAdapter` 和 `AIDestinationSetter` ``` -------------------------------- ### 动态队列方向自动计算 (C#) Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/QUEUE_SYSTEM_SETUP.md 此 C# 代码片段展示了当 Queue Direction 设置为 (0, 0, 0) 时,系统如何自动计算队列方向。方向向量是根据 Queue Anchor 和 Interaction Anchor 的相对位置计算得出的。 ```csharp queueDirection = (queueAnchor.position - interactionAnchor.position).normalized ``` -------------------------------- ### 行为树新节点流程 (NodeCanvas) Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/QUEUE_SYSTEM_SETUP.md 此代码片段描述了用于顾客选择货架、申请队列位置、移动到目标以及释放队列位置的新行为树节点流程。它取代了旧的 SelectAndMoveToShelfAction。 ```csharp 1. SelectTargetShelfAction // 策略选择货架 ↓ 2. AcquireQueueSlotAction // 申请队列位置 ↓ 3. MoveToTargetAction // 移动到分配的队位 ↓ 4. (等待队首/购买逻辑) ↓ 5. ReleaseQueueSlotAction // 释放队位(触发后续顾客前移) ``` -------------------------------- ### Create Shelf and Facility Archetypes in C# Source: https://context7.com/alankalles/poplifesimulator/llms.txt This C# code demonstrates how to create instances of `ShelfArchetype` and `FacilityArchetype` using ScriptableObjects in Unity. It defines properties like ID, display name, costs, footprint, product category for shelves, and facility types and effects for facilities. It also shows how to access rotated footprints and level-specific data from a `BuildingArchetype`. ```csharp using UnityEngine; using PopLife.Data; using System.Collections.Generic; // Create derived types in Unity Editor public class BuildingArchetypeExample : MonoBehaviour { void CreateShelfArchetype() { // Create via: Assets/Create/PopLife/Buildings/Shelf ShelfArchetype shelf = ScriptableObject.CreateInstance(); shelf.archetypeId = "shelf_condom"; shelf.displayName = "Condom Display"; shelf.buildCost = 500; shelf.moveCost = 50; shelf.requiresBlueprint = true; shelf.canRotate = true; // Define footprint (relative to origin) // Example: 2x1 shelf shelf.footprintPattern = new List { new Vector2Int(0, 0), new Vector2Int(1, 0) }; // Product category shelf.category = ProductCategory.Condom; // Define level progression shelf.levels = new ShelfLevelData[] { new ShelfLevelData { level = 1, maxStock = 10, price = 15, attractiveness = 5, maintenanceFee = 5, upgradeFameCost = 0 }, new ShelfLevelData { level = 2, maxStock = 20, price = 18, attractiveness = 8, maintenanceFee = 8, upgradeFameCost = 50 } }; } void CreateFacilityArchetype() { // Create via: Assets/Create/PopLife/Buildings/Facility FacilityArchetype cashier = ScriptableObject.CreateInstance(); cashier.archetypeId = "cashier_basic"; cashier.displayName = "Cashier Counter"; cashier.buildCost = 1000; cashier.requiresBlueprint = false; cashier.facilityType = FacilityType.Cashier; // Define effects cashier.effects = new FacilityEffect[] { new FacilityEffect { effectType = EffectType.ReduceEmbarrassment, scope = EffectScope.Radius, radius = 5f, value = -10f // Reduces embarrassment by 10 } }; // Custom placement validation (cashiers must be against walls) // Implemented in FacilityArchetype.ValidatePlacement override } void UseArchetype(BuildingArchetype archetype) { // Get rotated footprint int rotation = 1; // 90 degrees List footprint = archetype.GetRotatedFootprint(rotation); foreach (var cell in footprint) { Debug.Log($"Occupies cell: {cell}"); } // Check rotation support if (archetype.canRotate) { Debug.Log("Building supports rotation"); } // Get level data int level = 2; BuildingLevelData levelData = archetype.GetLevel(level); Debug.Log($"Level {level} maintenance: ${levelData.maintenanceFee}"); Debug.Log($"Upgrade cost: {levelData.upgradeFameCost} Fame"); } } ``` -------------------------------- ### 排查顾客重叠问题 (Unity) Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/QUEUE_SYSTEM_SETUP.md 此代码片段提供了解决 Unity 中顾客重叠问题的方案。可能的原因包括未调用 AcquireQueueSlotAction、Slot Spacing 过小或 RVO 设置不当。解决方案包括确保行为树流程、调整间距和启用 RVO。 ```csharp 1. 确保行为树流程包含 AcquireQueueSlotAction 2. 增大 Slot Spacing 到 1.0 米以上 3. 启用 RVOController 并设置 Radius = 0.3+ ``` -------------------------------- ### Unity NodeCanvas: 重构行为树根节点为 PrioritySelector Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/StoreClosingDoc/StoreClosing_Unity_Setup_Guide.md 将 Unity NodeCanvas 行为树的根节点从现有的 Iterator 更改为 PrioritySelector。新的根节点将包含一个紧急退出分支和一个指向原行为树的流程分支,以实现闭店逻辑。 ```plaintext 1. 添加 PrioritySelector 节点并设为根节点。 2. 将原根节点(StepIterator)连接为 PrioritySelector 的第二个子节点。 3. 添加 ConditionalEvaluator 节点并设为 PrioritySelector 的第一个子节点(优先级最高)。 ``` -------------------------------- ### 排查顾客到达目标问题 (Unity) Source: https://github.com/alankalles/poplifesimulator/blob/main/Pop Life Simulator/Assets/Documents/QUEUE_SYSTEM_SETUP.md 此代码片段提供了解决 Unity 中顾客“永远到不了”目标的方案。可能的原因包括 Arrival Distance 过小或目标点在障碍物内。解决方案是增大 stoppingDistance 和 arrivalDistance。 ```csharp 1. 增大 MoveToTargetAction.stoppingDistance 到 0.8 米 2. 增大 ShelfQueueController.arrivalDistance 到 0.8 米 3. 确保 InteractionAnchor 不在货架碰撞体内 ```