Rework of base interactables and managed behaviors

This commit is contained in:
Michal Pikulski
2025-11-04 11:11:27 +01:00
committed by Michal Pikulski
parent 00e1746ac4
commit f88bd0e2c9
60 changed files with 11175 additions and 1340 deletions

View File

@@ -5,8 +5,8 @@ using UnityEngine.SceneManagement;
using Utils;
using AppleHills.Core.Settings;
using Core;
using Core.Lifecycle;
using Core.SaveLoad;
using Bootstrap;
using UnityEngine.Events;
/// <summary>
@@ -24,7 +24,7 @@ public class FollowerSaveData
/// <summary>
/// Controls the follower character, including following the player, handling pickups, and managing held items.
/// </summary>
public class FollowerController : MonoBehaviour, ISaveParticipant
public class FollowerController : ManagedBehaviour, ISaveParticipant
{
private static readonly int CombineTrigger = Animator.StringToHash("Combine");
@@ -103,7 +103,9 @@ public class FollowerController : MonoBehaviour, ISaveParticipant
private bool _hasRestoredHeldItem; // Track if held item restoration completed
private string _expectedHeldItemSaveId; // Expected saveId during restoration
void Awake()
public override int ManagedAwakePriority => 110; // Follower after player
protected override void OnManagedAwake()
{
_aiPath = GetComponent<AIPath>();
// Find art prefab and animator
@@ -123,13 +125,7 @@ public class FollowerController : MonoBehaviour, ISaveParticipant
_settings = GameManager.GetSettingsObject<IPlayerFollowerSettings>();
_interactionSettings = GameManager.GetSettingsObject<IInteractionSettings>();
// Register for post-boot initialization
BootCompletionService.RegisterInitAction(InitializePostBoot);
}
private void InitializePostBoot()
{
// Register with save system after boot
// Register with save system
if (SaveLoadManager.Instance != null)
{
SaveLoadManager.Instance.RegisterParticipant(this);
@@ -140,7 +136,7 @@ public class FollowerController : MonoBehaviour, ISaveParticipant
Logging.Warning("[FollowerController] SaveLoadManager not available for registration");
}
}
void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
@@ -583,19 +579,31 @@ public class FollowerController : MonoBehaviour, ISaveParticipant
#endregion StationaryAnimations
#region ItemInteractions
// TODO: Move TryCombineItems to ItemManager/InteractionHelpers
// This is currently interaction logic living in a movement controller.
// Pros of moving: Separates game logic from character logic, easier to test
// Cons: More coordination needed, follower still needs animation callbacks
/// <summary>
/// Try to pickup an item. If already holding something, optionally drop it first.
/// </summary>
/// <param name="itemObject">The GameObject to pick up (must have Pickup component)</param>
/// <param name="itemData">The item data (redundant - can be extracted from GameObject)</param>
/// <param name="dropItem">Whether to drop currently held item before picking up new one</param>
public void TryPickupItem(GameObject itemObject, PickupItemData itemData, bool dropItem = true)
{
if (itemObject == null) return;
// Drop current item if holding something
if (_currentlyHeldItemData != null && _cachedPickupObject != null && dropItem)
{
// Drop the currently held item at the current position
DropHeldItemAt(transform.position);
}
// Pick up the new item
SetHeldItem(itemData, itemObject.GetComponent<SpriteRenderer>());
_animator.SetBool("IsCarrying", true);
_cachedPickupObject = itemObject;
_cachedPickupObject.SetActive(false);
// Use helper to set held item (handles data extraction, caching, animator)
SetHeldItemFromObject(itemObject);
itemObject.SetActive(false);
}
public enum CombinationResult
@@ -609,41 +617,41 @@ public class FollowerController : MonoBehaviour, ISaveParticipant
{
_animator.ResetTrigger(CombineTrigger);
newItem = null;
// Validation
if (_cachedPickupObject == null)
{
return CombinationResult.NotApplicable;
}
Pickup pickupB = _cachedPickupObject.GetComponent<Pickup>();
if (pickupA == null || pickupB == null)
{
return CombinationResult.NotApplicable;
}
// Use the InteractionSettings directly instead of GameManager
// Find combination rule
CombinationRule matchingRule = _interactionSettings.GetCombinationRule(pickupA.itemData, pickupB.itemData);
Vector3 spawnPos = pickupA.gameObject.transform.position;
if (matchingRule != null && matchingRule.resultPrefab != null)
{
newItem = Instantiate(matchingRule.resultPrefab, spawnPos, Quaternion.identity);
var resultPickup = newItem.GetComponent<Pickup>();
PickupItemData itemData = resultPickup.itemData;
// Mark the base items as picked up before destroying them
// (This ensures they save correctly if the game is saved during the combination animation)
pickupA.IsPickedUp = true;
pickupB.IsPickedUp = true;
Destroy(pickupA.gameObject);
Destroy(pickupB.gameObject);
TryPickupItem(newItem, itemData);
PlayAnimationStationary("Combine", 10.0f);
PulverIsCombining.Invoke();
return CombinationResult.Successful;
}
if (matchingRule == null || matchingRule.resultPrefab == null)
return CombinationResult.Unsuccessful;
// If no combination found, return Unsuccessful
return CombinationResult.Unsuccessful;
// Execute combination
Vector3 spawnPos = pickupA.gameObject.transform.position;
newItem = Instantiate(matchingRule.resultPrefab, spawnPos, Quaternion.identity);
var resultPickup = newItem.GetComponent<Pickup>();
// Mark items as picked up before destroying (for save system)
pickupA.IsPickedUp = true;
pickupB.IsPickedUp = true;
Destroy(pickupA.gameObject);
Destroy(pickupB.gameObject);
// Pickup the result (don't drop it!)
TryPickupItem(newItem, resultPickup.itemData, dropItem: false);
// Visual feedback
PlayAnimationStationary("Combine", 10.0f);
PulverIsCombining.Invoke();
return CombinationResult.Successful;
}
/// <summary>
@@ -673,6 +681,10 @@ public class FollowerController : MonoBehaviour, ISaveParticipant
return _cachedPickupObject;
}
/// <summary>
/// Set held item from a GameObject. Extracts Pickup component and sets up visuals.
/// Centralizes held item state management including animator.
/// </summary>
public void SetHeldItemFromObject(GameObject obj)
{
if (obj == null)
@@ -680,11 +692,13 @@ public class FollowerController : MonoBehaviour, ISaveParticipant
ClearHeldItem();
return;
}
var pickup = obj.GetComponent<Pickup>();
if (pickup != null)
{
SetHeldItem(pickup.itemData, pickup.iconRenderer);
_cachedPickupObject = obj;
_animator.SetBool("IsCarrying", true); // Centralized animator management
}
else
{
@@ -692,11 +706,15 @@ public class FollowerController : MonoBehaviour, ISaveParticipant
}
}
/// <summary>
/// Clear the currently held item. Centralizes state cleanup including animator.
/// </summary>
public void ClearHeldItem()
{
_cachedPickupObject = null;
_currentlyHeldItemData = null;
_animator.SetBool("IsCarrying", false);
_animator.SetBool("IsCarrying", false); // Centralized animator management
if (heldObjectRenderer != null)
{
heldObjectRenderer.sprite = null;
@@ -704,29 +722,28 @@ public class FollowerController : MonoBehaviour, ISaveParticipant
}
}
public void DropItem(FollowerController follower, Vector3 position)
/// <summary>
/// Drop the currently held item at the specified position.
/// </summary>
public void DropHeldItemAt(Vector3 position)
{
var item = follower.GetHeldPickupObject();
var item = GetHeldPickupObject();
if (item == null) return;
// Place item in world
item.transform.position = position;
item.transform.SetParent(null);
item.SetActive(true);
// Reset the pickup state so it can be picked up again and saves correctly
// Reset pickup state so it can be picked up again
var pickup = item.GetComponent<Pickup>();
if (pickup != null)
{
pickup.ResetPickupState();
}
follower.ClearHeldItem();
_animator.SetBool("IsCarrying", false);
// Optionally: fire event, update UI, etc.
}
public void DropHeldItemAt(Vector3 position)
{
DropItem(this, position);
// Clear held item state (includes animator)
ClearHeldItem();
}