[Puzzles] Add basic framework for ScriptableObject puzzle steps and puzzle solving.
This commit is contained in:
62
Assets/Scripts/DebugUIMessage.cs
Normal file
62
Assets/Scripts/DebugUIMessage.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using System.Collections;
|
||||
|
||||
public class DebugUIMessage : MonoBehaviour
|
||||
{
|
||||
private static DebugUIMessage instance;
|
||||
private Text messageText;
|
||||
private Canvas canvas;
|
||||
private Coroutine hideCoroutine;
|
||||
|
||||
public static void Show(string message, float duration = 2f)
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
var go = new GameObject("DebugUIMessage");
|
||||
instance = go.AddComponent<DebugUIMessage>();
|
||||
instance.SetupUI();
|
||||
DontDestroyOnLoad(go);
|
||||
}
|
||||
instance.ShowMessage(message, duration);
|
||||
}
|
||||
|
||||
private void SetupUI()
|
||||
{
|
||||
canvas = new GameObject("DebugUICanvas").AddComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.sortingOrder = 9999;
|
||||
canvas.gameObject.transform.SetParent(transform);
|
||||
var textGO = new GameObject("DebugUIText");
|
||||
textGO.transform.SetParent(canvas.transform);
|
||||
messageText = textGO.AddComponent<Text>();
|
||||
messageText.alignment = TextAnchor.MiddleCenter;
|
||||
messageText.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
|
||||
messageText.fontSize = 32;
|
||||
messageText.color = Color.yellow;
|
||||
var rect = messageText.GetComponent<RectTransform>();
|
||||
rect.anchorMin = new Vector2(0.5f, 0.1f);
|
||||
rect.anchorMax = new Vector2(0.5f, 0.1f);
|
||||
rect.pivot = new Vector2(0.5f, 0.5f);
|
||||
rect.anchoredPosition = Vector2.zero;
|
||||
rect.sizeDelta = new Vector2(800, 100);
|
||||
messageText.text = "";
|
||||
}
|
||||
|
||||
private void ShowMessage(string message, float duration)
|
||||
{
|
||||
messageText.text = message;
|
||||
messageText.enabled = true;
|
||||
if (hideCoroutine != null)
|
||||
StopCoroutine(hideCoroutine);
|
||||
hideCoroutine = StartCoroutine(HideAfterSeconds(duration));
|
||||
}
|
||||
|
||||
private IEnumerator HideAfterSeconds(float seconds)
|
||||
{
|
||||
yield return new WaitForSeconds(seconds);
|
||||
messageText.text = "";
|
||||
messageText.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/DebugUIMessage.cs.meta
Normal file
3
Assets/Scripts/DebugUIMessage.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5878ce6b61524c18b7cbdf0f55f143ea
|
||||
timeCreated: 1756903750
|
||||
@@ -24,7 +24,7 @@ public class InputManager : MonoBehaviour
|
||||
playerInput = GetComponent<PlayerInput>();
|
||||
if (playerInput == null)
|
||||
{
|
||||
Debug.LogError("InputManager requires a PlayerInput component attached to the same GameObject.");
|
||||
Debug.LogError("[InputManager] InputManager requires a PlayerInput component attached to the same GameObject.");
|
||||
return;
|
||||
}
|
||||
// Find actions by name in the assigned action map
|
||||
|
||||
@@ -5,10 +5,23 @@ public class Interactable : MonoBehaviour, ITouchInputConsumer
|
||||
{
|
||||
public event Action Interacted;
|
||||
|
||||
private ObjectiveStepBehaviour stepBehaviour;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
stepBehaviour = GetComponent<ObjectiveStepBehaviour>();
|
||||
}
|
||||
|
||||
// Called by InputManager when this interactable is clicked/touched
|
||||
public void OnTouchPress(Vector2 worldPosition)
|
||||
{
|
||||
Debug.Log($"Interactable.OnTouchPress at {worldPosition} on {gameObject.name}");
|
||||
if (stepBehaviour != null && !stepBehaviour.IsStepUnlocked())
|
||||
{
|
||||
DebugUIMessage.Show("Item is not unlocked yet");
|
||||
Debug.Log("[Puzzles] Tried to interact with locked step: " + gameObject.name);
|
||||
return;
|
||||
}
|
||||
Debug.Log($"[Interactable] OnTouchPress at {worldPosition} on {gameObject.name}");
|
||||
Interacted?.Invoke();
|
||||
}
|
||||
|
||||
@@ -17,4 +30,3 @@ public class Interactable : MonoBehaviour, ITouchInputConsumer
|
||||
// Optionally handle drag/move here
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
55
Assets/Scripts/ObjectiveStepBehaviour.cs
Normal file
55
Assets/Scripts/ObjectiveStepBehaviour.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using UnityEngine;
|
||||
|
||||
[RequireComponent(typeof(Interactable))]
|
||||
public class ObjectiveStepBehaviour : MonoBehaviour
|
||||
{
|
||||
public PuzzleStepSO stepData;
|
||||
private Interactable interactable;
|
||||
private bool isUnlocked = false;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
interactable = GetComponent<Interactable>();
|
||||
if (interactable != null)
|
||||
{
|
||||
interactable.Interacted += OnInteracted;
|
||||
}
|
||||
// Register with PuzzleManager
|
||||
PuzzleManager.Instance?.RegisterStepBehaviour(this);
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (interactable != null)
|
||||
{
|
||||
interactable.Interacted -= OnInteracted;
|
||||
}
|
||||
PuzzleManager.Instance?.UnregisterStepBehaviour(this);
|
||||
}
|
||||
|
||||
public void UnlockStep()
|
||||
{
|
||||
isUnlocked = true;
|
||||
Debug.Log($"[Puzzles] Step unlocked: {stepData?.stepId} on {gameObject.name}");
|
||||
// Optionally, show visual feedback for unlocked state
|
||||
}
|
||||
|
||||
public void LockStep()
|
||||
{
|
||||
isUnlocked = false;
|
||||
Debug.Log($"[Puzzles] Step locked: {stepData?.stepId} on {gameObject.name}");
|
||||
// Optionally, show visual feedback for locked state
|
||||
}
|
||||
|
||||
public bool IsStepUnlocked()
|
||||
{
|
||||
return isUnlocked;
|
||||
}
|
||||
|
||||
private void OnInteracted()
|
||||
{
|
||||
if (!isUnlocked) return;
|
||||
Debug.Log($"[Puzzles] Step interacted: {stepData?.stepId} on {gameObject.name}");
|
||||
PuzzleManager.Instance?.OnStepCompleted(stepData);
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/ObjectiveStepBehaviour.cs.meta
Normal file
3
Assets/Scripts/ObjectiveStepBehaviour.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1101f6c4eb04423b89dc78dc7c9f1aae
|
||||
timeCreated: 1756901353
|
||||
@@ -48,7 +48,7 @@ public class Pickup : MonoBehaviour
|
||||
|
||||
private void OnInteracted()
|
||||
{
|
||||
Debug.Log($"Pickup.OnInteracted: Picked up {itemData?.itemName}");
|
||||
Debug.Log($"[Pickup] OnInteracted: Picked up {itemData?.itemName}");
|
||||
// TODO: Add item to inventory manager here
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
131
Assets/Scripts/PuzzleManager.cs
Normal file
131
Assets/Scripts/PuzzleManager.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class PuzzleManager : MonoBehaviour
|
||||
{
|
||||
public static PuzzleManager Instance { get; private set; }
|
||||
private HashSet<PuzzleStepSO> completedSteps = new HashSet<PuzzleStepSO>();
|
||||
private HashSet<PuzzleStepSO> unlockedSteps = new HashSet<PuzzleStepSO>();
|
||||
|
||||
// Registration for ObjectiveStepBehaviour
|
||||
private Dictionary<PuzzleStepSO, ObjectiveStepBehaviour> stepBehaviours = new Dictionary<PuzzleStepSO, ObjectiveStepBehaviour>();
|
||||
|
||||
// Runtime dependency graph
|
||||
private Dictionary<PuzzleStepSO, List<PuzzleStepSO>> runtimeDependencies = new Dictionary<PuzzleStepSO, List<PuzzleStepSO>>();
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
|
||||
public void RegisterStepBehaviour(ObjectiveStepBehaviour behaviour)
|
||||
{
|
||||
if (behaviour?.stepData == null) return;
|
||||
if (!stepBehaviours.ContainsKey(behaviour.stepData))
|
||||
{
|
||||
stepBehaviours.Add(behaviour.stepData, behaviour);
|
||||
Debug.Log($"[Puzzles] Registered step: {behaviour.stepData.stepId} on {behaviour.gameObject.name}");
|
||||
}
|
||||
}
|
||||
|
||||
public void UnregisterStepBehaviour(ObjectiveStepBehaviour behaviour)
|
||||
{
|
||||
if (behaviour?.stepData == null) return;
|
||||
stepBehaviours.Remove(behaviour.stepData);
|
||||
Debug.Log($"[Puzzles] Unregistered step: {behaviour.stepData.stepId} on {behaviour.gameObject.name}");
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
BuildRuntimeDependencies();
|
||||
UnlockInitialSteps();
|
||||
}
|
||||
|
||||
private void BuildRuntimeDependencies()
|
||||
{
|
||||
runtimeDependencies.Clear();
|
||||
foreach (var step in stepBehaviours.Keys)
|
||||
{
|
||||
runtimeDependencies[step] = new List<PuzzleStepSO>();
|
||||
}
|
||||
foreach (var step in stepBehaviours.Keys)
|
||||
{
|
||||
foreach (var unlocked in step.unlocks)
|
||||
{
|
||||
if (!runtimeDependencies.ContainsKey(unlocked))
|
||||
runtimeDependencies[unlocked] = new List<PuzzleStepSO>();
|
||||
runtimeDependencies[unlocked].Add(step);
|
||||
Debug.Log($"[Puzzles] Step {unlocked.stepId} depends on {step.stepId}");
|
||||
}
|
||||
}
|
||||
Debug.Log($"[Puzzles] Runtime dependencies built. Total steps: {stepBehaviours.Count}");
|
||||
}
|
||||
|
||||
private void UnlockInitialSteps()
|
||||
{
|
||||
foreach (var step in stepBehaviours.Keys)
|
||||
{
|
||||
if (runtimeDependencies[step].Count == 0)
|
||||
{
|
||||
Debug.Log($"[Puzzles] Initial step unlocked: {step.stepId}");
|
||||
UnlockStep(step);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnStepCompleted(PuzzleStepSO step)
|
||||
{
|
||||
if (completedSteps.Contains(step)) return;
|
||||
completedSteps.Add(step);
|
||||
Debug.Log($"[Puzzles] Step completed: {step.stepId}");
|
||||
foreach (var unlock in step.unlocks)
|
||||
{
|
||||
if (AreRuntimeDependenciesMet(unlock))
|
||||
{
|
||||
Debug.Log($"[Puzzles] Unlocking step {unlock.stepId} after completing {step.stepId}");
|
||||
UnlockStep(unlock);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"[Puzzles] Step {unlock.stepId} not unlocked yet, waiting for other dependencies");
|
||||
}
|
||||
}
|
||||
CheckPuzzleCompletion();
|
||||
}
|
||||
|
||||
private bool AreRuntimeDependenciesMet(PuzzleStepSO step)
|
||||
{
|
||||
if (!runtimeDependencies.ContainsKey(step) || runtimeDependencies[step].Count == 0) return true;
|
||||
foreach (var dep in runtimeDependencies[step])
|
||||
{
|
||||
if (!completedSteps.Contains(dep)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UnlockStep(PuzzleStepSO step)
|
||||
{
|
||||
if (unlockedSteps.Contains(step)) return;
|
||||
unlockedSteps.Add(step);
|
||||
if (stepBehaviours.TryGetValue(step, out var behaviour))
|
||||
{
|
||||
behaviour.UnlockStep();
|
||||
}
|
||||
Debug.Log($"[Puzzles] Step unlocked: {step.stepId}");
|
||||
}
|
||||
|
||||
private void CheckPuzzleCompletion()
|
||||
{
|
||||
if (completedSteps.Count == stepBehaviours.Count)
|
||||
{
|
||||
Debug.Log("[Puzzles] Puzzle complete! All steps finished.");
|
||||
// TODO: Fire puzzle complete event or trigger outcome logic
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Scripts/PuzzleManager.cs.meta
Normal file
3
Assets/Scripts/PuzzleManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bdc7ceebe82348dba3ad1ca1153e0dba
|
||||
timeCreated: 1756901363
|
||||
14
Assets/Scripts/PuzzleStepSO.cs
Normal file
14
Assets/Scripts/PuzzleStepSO.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
[CreateAssetMenu(fileName = "PuzzleStepSO", menuName = "Puzzle/Step")]
|
||||
public class PuzzleStepSO : ScriptableObject
|
||||
{
|
||||
public string stepId;
|
||||
public string displayName;
|
||||
[TextArea]
|
||||
public string description;
|
||||
public Sprite icon;
|
||||
[Header("Unlocks")]
|
||||
public List<PuzzleStepSO> unlocks = new List<PuzzleStepSO>();
|
||||
}
|
||||
3
Assets/Scripts/PuzzleStepSO.cs.meta
Normal file
3
Assets/Scripts/PuzzleStepSO.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84e39aac66cf4a10a89abc01b04b13af
|
||||
timeCreated: 1756901344
|
||||
Reference in New Issue
Block a user