[Puzzles] Add basic framework for ScriptableObject puzzle steps and puzzle solving.

This commit is contained in:
Michal Pikulski
2025-09-03 15:43:47 +02:00
parent d8f792c8e5
commit 93242b2702
30 changed files with 1039 additions and 169 deletions

View 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;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5878ce6b61524c18b7cbdf0f55f143ea
timeCreated: 1756903750

View File

@@ -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

View File

@@ -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
}
}

View 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);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1101f6c4eb04423b89dc78dc7c9f1aae
timeCreated: 1756901353

View File

@@ -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);
}

View 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
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bdc7ceebe82348dba3ad1ca1153e0dba
timeCreated: 1756901363

View 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>();
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 84e39aac66cf4a10a89abc01b04b13af
timeCreated: 1756901344