comment pass

This commit is contained in:
2022-12-29 16:16:49 +01:00
parent 643e03192a
commit ff01a700bd
75 changed files with 634 additions and 65 deletions

View File

@@ -1,4 +1,5 @@
using System;
using JetBrains.Annotations;
using NaughtyAttributes;
using UnityEngine;
using Logger = EscapeRoomEngine.Engine.Runtime.Utilities.Logger;
@@ -30,10 +31,16 @@ namespace EscapeRoomEngine.Desert.Runtime
public delegate void ButtonEventHandler(Button source, ButtonEventType e);
/// <summary>
/// A general component for buttons that handles button events.
/// </summary>
public class Button : MonoBehaviour
{
public event ButtonEventHandler ButtonEvent;
/// <summary>
/// Whether this button accepts input.
/// </summary>
[ShowNativeProperty]
protected bool Active
{
@@ -50,6 +57,9 @@ namespace EscapeRoomEngine.Desert.Runtime
}
}
/// <summary>
/// Whether this button is currently pressed.
/// </summary>
[ShowNativeProperty]
protected bool Pressed
{
@@ -88,11 +98,15 @@ namespace EscapeRoomEngine.Desert.Runtime
#region Debug Buttons
[Button(enabledMode: EButtonEnableMode.Playmode)] public void Enable() => Active = true;
[Button(enabledMode: EButtonEnableMode.Playmode)] public void Disable() => Active = false;
[Button(enabledMode: EButtonEnableMode.Playmode)] public void Press() => Pressed = true;
[Button(enabledMode: EButtonEnableMode.Playmode)] public void Release() => Pressed = false;
[Button(enabledMode: EButtonEnableMode.Playmode)]
public void Enable() => Active = true;
[Button(enabledMode: EButtonEnableMode.Playmode)]
public void Disable() => Active = false;
[Button(enabledMode: EButtonEnableMode.Playmode)]
public void Press() => Pressed = true;
[Button(enabledMode: EButtonEnableMode.Playmode)]
public void Release() => Pressed = false;
[UsedImplicitly]
[Button(enabledMode: EButtonEnableMode.Playmode)]
public void PressAndRelease()
{

View File

@@ -4,11 +4,19 @@ using UnityEngine;
namespace EscapeRoomEngine.Desert.Runtime
{
/// <summary>
/// A rotating crystal that can change colour.
/// </summary>
[RequireComponent(typeof(Emission))]
public class Crystal : MonoBehaviour
{
[Required] public Light crystalLight;
[Required]
[BoxGroup("Internal")]
public Light crystalLight;
/// <summary>
/// Turns the crystal light on or off.
/// </summary>
public bool Active
{
set
@@ -18,6 +26,9 @@ namespace EscapeRoomEngine.Desert.Runtime
}
}
/// <summary>
/// The crystal light and emission colours.
/// </summary>
public DynamicColor Color
{
set

View File

@@ -6,13 +6,18 @@ using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;
namespace EscapeRoomEngine.Desert.Runtime.Dispenser
{
/// <summary>
/// The main component for the dispenser module.
/// </summary>
[SelectionBase]
[RequireComponent(typeof(Animator), typeof(Emission))]
public class Dispenser : ModuleState
{
private static readonly int Open = Animator.StringToHash("Open");
[Tooltip("The object this dispenser should produce.")]
public Transform dispensable;
[Tooltip("The time in seconds to wait until another object can be produced.")]
[SerializeField] [Min(0)]
private float cooldown;
[BoxGroup("Internal")] [SerializeField]
@@ -20,6 +25,9 @@ namespace EscapeRoomEngine.Desert.Runtime.Dispenser
[BoxGroup("Internal")] [SerializeField]
private Button dispenseButton;
/// <summary>
/// The dispenser does not produce any more objects when it is solved and changes colour.
/// </summary>
public bool Solved
{
set

View File

@@ -10,8 +10,12 @@ namespace EscapeRoomEngine.Desert.Runtime.Dispenser
public bool[] lights;
}
/// <summary>
/// This component controls the grid of lights on the dispenser.
/// </summary>
public class DispenserLights : MonoBehaviour
{
[Tooltip("Control the pattern of lights on a dispenser.")]
[SerializeField]
private DispenserLightRow[] lights;
[BoxGroup("Internal")] [SerializeField]
@@ -23,7 +27,6 @@ namespace EscapeRoomEngine.Desert.Runtime.Dispenser
private void Start()
{
var position = transform.localPosition;
for (var y = 0; y < gridDimensions.y; y++)
{
var row = lights[y].lights;

View File

@@ -3,13 +3,23 @@ using UnityEngine;
namespace EscapeRoomEngine.Desert.Runtime
{
/// <summary>
/// A general component for controlling the emission of an object.
/// </summary>
public class Emission : MonoBehaviour
{
private static readonly int EmissionColorNameID = Shader.PropertyToID("_EmissionColor");
[Tooltip("The colour of the emission.")]
[ColorUsage(false, true)]
public Color color;
[BoxGroup("Internal")] [Required]
public MeshRenderer emissionRenderer;
/// <summary>
/// Whether the emission is on.
/// </summary>
internal bool active;
[ColorUsage(false, true)] public Color color;
[BoxGroup("Internal")] [Required] public MeshRenderer emissionRenderer;
private bool _previousActive;
private Color _previousColor;

View File

@@ -3,6 +3,9 @@ using UnityEngine;
namespace EscapeRoomEngine.Desert.Runtime
{
/// <summary>
/// A holographic <see cref="Button"/> that changes its colour when it is pressed.
/// </summary>
public class HoloButton : Button
{
private static readonly int FresnelColor = Shader.PropertyToID("_FresnelColor");

View File

@@ -8,9 +8,13 @@ using UnityEngine;
namespace EscapeRoomEngine.Desert.Runtime
{
/// <summary>
/// The main component for the orb throwing module.
/// </summary>
[RequireComponent(typeof(Collider))]
public class Hoop : StatePuzzle
{
[Tooltip("How long to wait in seconds until the state is updated. Orbs that do not stay in the hoop should not be counted.")]
public float updateDelay;
[BoxGroup("Internal")] [SerializeField]
private List<Emission> rings;
@@ -84,6 +88,7 @@ namespace EscapeRoomEngine.Desert.Runtime
{
base.SetModule(module);
// The hoop requires a related dispenser module
var firstRelatedModule = Module.relatedModules[0];
if (firstRelatedModule.State is Dispenser.Dispenser dispenser)
{

View File

@@ -3,6 +3,9 @@ using UnityEngine;
namespace EscapeRoomEngine.Desert.Runtime.Portal
{
/// <summary>
/// The desert theme includes its own version of a portal that changes colour when it is unlocked.
/// </summary>
[RequireComponent(typeof(Emission))]
public class DesertPortal : EscapeRoomEngine.Portal.Runtime.Portal
{

View File

@@ -3,10 +3,15 @@ using NaughtyAttributes;
namespace EscapeRoomEngine.Desert.Runtime.Puzzle_A
{
/// <summary>
/// The main component for the symbol ball module.
/// </summary>
public class Ball : ModuleState
{
[BoxGroup("Internal")] [Required] public Emission statusLight;
[BoxGroup("Internal")] [Required] public Ring ring;
[BoxGroup("Internal")] [Required]
public Emission statusLight;
[BoxGroup("Internal")] [Required]
public Ring ring;
public bool StatusLight
{

View File

@@ -6,12 +6,20 @@ using UnityEngine;
namespace EscapeRoomEngine.Desert.Runtime.Puzzle_A
{
/// <summary>
/// The rotating ring in the symbol ball module.
/// </summary>
public class Ring : MonoBehaviour
{
[Tooltip("Whether the ring is rotating.")]
public bool rotating = true;
[Tooltip("The current angle of the rotation.")]
public float rotationAngle;
[MinMaxSlider(-180, 180)] public Vector2 activeRange;
[BoxGroup("Internal")] [Required] public Crystal crystal;
[Tooltip("The range in degrees from the front of the module where a symbol should be lit up.")]
[MinMaxSlider(-180, 180)]
public Vector2 activeRange;
[BoxGroup("Internal")] [Required]
public Crystal crystal;
[BoxGroup("Internal")]
[ValidateInput("SymbolCount", "Must have same amount of symbols and slots.")]
public List<Symbol> symbols;

View File

@@ -1,4 +1,5 @@
using System;
using NaughtyAttributes;
using UnityEngine;
using Logger = EscapeRoomEngine.Engine.Runtime.Utilities.Logger;
using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;
@@ -27,13 +28,23 @@ namespace EscapeRoomEngine.Desert.Runtime.Puzzle_A
public delegate void SymbolEventHandler(SymbolEventType e);
/// <summary>
/// The main component for the symbols on the symbol ring module.
/// </summary>
[RequireComponent(typeof(Emission))]
public class Symbol : MonoBehaviour
{
public int symbolNumber, symbolPosition;
[Tooltip("The number of the symbol in the puzzle solution.")]
public int symbolNumber;
[BoxGroup("Internal")]
public int symbolPosition;
[BoxGroup("Internal")]
public float anglePosition;
public event SymbolEventHandler SymbolEvent;
/// <summary>
/// Whether the symbol is lit up. This is controlled by the <see cref="Ring"/>.
/// </summary>
public bool Active
{
set

View File

@@ -1,7 +1,13 @@
namespace EscapeRoomEngine.Desert.Runtime.Puzzle_A
using UnityEngine;
namespace EscapeRoomEngine.Desert.Runtime.Puzzle_A
{
/// <summary>
/// A <see cref="HoloButton"/> that includes the number of its assigned symbol.
/// </summary>
public class SymbolButton : HoloButton
{
[Tooltip("The number of the symbol assigned to this button.")]
public int symbolNumber;
}
}

View File

@@ -10,12 +10,16 @@ using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;
namespace EscapeRoomEngine.Desert.Runtime.Puzzle_A
{
/// <summary>
/// The main component for the terminal module.
/// </summary>
[RequireComponent(typeof(Animator))]
public class Terminal : CyclicStepPuzzle
{
private static readonly int LightFlash = Animator.StringToHash("Light Flash");
[BoxGroup("Internal")] [Required] public Emission terminalLight;
[BoxGroup("Internal")] [Required]
public Emission terminalLight;
[BoxGroup("Internal")]
[ValidateInput("SymbolCount", "Must have same amount of symbols and slots.")]
public List<SymbolButton> symbols;
@@ -82,6 +86,7 @@ namespace EscapeRoomEngine.Desert.Runtime.Puzzle_A
{
base.SetModule(module);
// The terminal requires a related symbol ball module
var firstRelatedModule = Module.relatedModules[0];
if (firstRelatedModule.State is Ball ball)
{
@@ -95,15 +100,15 @@ namespace EscapeRoomEngine.Desert.Runtime.Puzzle_A
TurnOnLights();
}
// required in animation
[UsedImplicitly] internal void TurnOnLights()
[UsedImplicitly] // required in animation
internal void TurnOnLights()
{
terminalLight.active = true;
_ball.StatusLight = true;
}
// required in animation
[UsedImplicitly] internal void TurnOffLights()
[UsedImplicitly] // required in animation
internal void TurnOffLights()
{
terminalLight.active = false;
_ball.StatusLight = false;
@@ -131,12 +136,19 @@ namespace EscapeRoomEngine.Desert.Runtime.Puzzle_A
#region Debug Buttons
[Button(enabledMode: EButtonEnableMode.Playmode)] public void PressSymbol0() => PressedSymbol(0);
[Button(enabledMode: EButtonEnableMode.Playmode)] public void PressSymbol1() => PressedSymbol(1);
[Button(enabledMode: EButtonEnableMode.Playmode)] public void PressSymbol2() => PressedSymbol(2);
[UsedImplicitly]
[Button(enabledMode: EButtonEnableMode.Playmode)]
public void PressSymbol0() => PressedSymbol(0);
[UsedImplicitly]
[Button(enabledMode: EButtonEnableMode.Playmode)]
public void PressSymbol1() => PressedSymbol(1);
[UsedImplicitly]
[Button(enabledMode: EButtonEnableMode.Playmode)]
public void PressSymbol2() => PressedSymbol(2);
#endregion
[UsedImplicitly] private bool SymbolCount(List<SymbolButton> list) => list.Count == symbolSlots.Count;
[UsedImplicitly] // used for field validation
private bool SymbolCount(List<SymbolButton> list) => list.Count == symbolSlots.Count;
}
}

View File

@@ -16,8 +16,12 @@ namespace EscapeRoomEngine.Desert.Runtime.Puzzle_B
public List<int> stateIndices;
}
/// <summary>
/// The main component for the hexagon module.
/// </summary>
public class PuzzleB : StatePuzzle
{
[Tooltip("How many seconds the buttons should be disabled until another input is accepted.")]
[SerializeField] [Min(0)]
private float buttonCooldown;
[ValidateInput("CorrectRotatorCount")]
@@ -86,6 +90,9 @@ namespace EscapeRoomEngine.Desert.Runtime.Puzzle_B
}
}
/// <summary>
/// Switch all rotators specified by a given action.
/// </summary>
private void Switch(ButtonAction action)
{
for (var i = 0; i < action.stateIndices.Count; i++)
@@ -99,7 +106,7 @@ namespace EscapeRoomEngine.Desert.Runtime.Puzzle_B
}
}
[UsedImplicitly]
[UsedImplicitly] // used for field validation
private bool CorrectRotatorCount(List<Rotator> list) => list != null && list.Count == stateCount;
}
}

View File

@@ -3,11 +3,18 @@ using UnityEngine;
namespace EscapeRoomEngine.Desert.Runtime.Puzzle_B
{
/// <summary>
/// The rotator component used by the hexagon module.
/// </summary>
[RequireComponent(typeof(Emission))]
public class Rotator : MonoBehaviour
{
[Range(0, 359)] public int angle;
[Range(0, 1)] public float speed;
[Tooltip("The angle this rotator should rotate towards.")]
[Range(0, 359)]
public int angle;
[Tooltip("How quickly to rotate towards the target angle.")]
[Range(0, 1)]
public float speed;
public Emission Emission { get; private set; }

View File

@@ -3,9 +3,13 @@ using UnityEngine;
namespace EscapeRoomEngine.Desert.Runtime.Puzzle_C
{
/// <summary>
/// A hole used in the <see cref="Holes"/> module.
/// </summary>
[RequireComponent(typeof(Emission), typeof(Collider))]
public class Hole : Button
{
[Tooltip("The number of this hole, starting from the bottom left.")]
[Min(0)]
public int number;

View File

@@ -6,6 +6,9 @@ using UnityEngine;
namespace EscapeRoomEngine.Desert.Runtime.Puzzle_C
{
/// <summary>
/// The main component for the orb grid module.
/// </summary>
public class Holes : StatePuzzle
{
[BoxGroup("Internal")] [SerializeField]
@@ -75,6 +78,7 @@ namespace EscapeRoomEngine.Desert.Runtime.Puzzle_C
{
base.SetModule(module);
// The holes require a related dispenser module
var firstRelatedModule = Module.relatedModules[0];
if (firstRelatedModule.State is Dispenser.Dispenser dispenser)
{

View File

@@ -5,6 +5,9 @@ using UnityEngine.UIElements;
namespace EscapeRoomEngine.Engine.Runtime.Editor
{
#if UNITY_EDITOR
/// <summary>
/// An <see cref="EditorWindow"/> used to test the <see cref="Engine"/>.
/// </summary>
public class EngineEditor : EditorWindow
{
private bool _registeredUpdateEvent;

View File

@@ -11,9 +11,18 @@ using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;
namespace EscapeRoomEngine.Engine.Runtime
{
/// <summary>
/// The engine controls the whole escape room. It generates rooms and manages the puzzle plan.
/// </summary>
public class Engine : MonoBehaviour
{
/// <summary>
/// The active theme of the engine.
/// </summary>
public static EngineTheme Theme => Instance.theme;
/// <summary>
/// The active instance of the engine.
/// </summary>
public static Engine Instance { get; private set; }
public delegate void UpdateUIHandler();
@@ -23,11 +32,16 @@ namespace EscapeRoomEngine.Engine.Runtime
public int maxSpaceGenerationTries = 1000;
[Tooltip("The engine will try to generate a room that takes approximately this many seconds to complete.")]
public float initialTargetTime = 10 * 60;
[Tooltip("The offset each room will have from the previous one.")]
public Vector3 roomOffset = new(0, 1000, 0);
[Tooltip("The theme of the engine that decides the available puzzles, doors and more.")]
[Required] public EngineTheme theme;
public int NumberOfRooms => _rooms.Count;
public IOption<Room> CurrentRoom => NumberOfRooms > 0 ? Some<Room>.Of(_rooms[^1]) : None<Room>.New();
/// <summary>
/// The currendly estimated time for the player to finish the experience.
/// </summary>
public float EstimatedTimeRemaining { get; private set; }
private readonly List<Room> _rooms = new();
@@ -169,6 +183,10 @@ namespace EscapeRoomEngine.Engine.Runtime
GameControl.Instance.PlannedPuzzles = _plannedPuzzles;
}
/// <summary>
/// Hide or destroy the room two rooms ago. The actual previous room is kept for the player to be able to backtrack one room.
/// </summary>
/// <param name="destroy"></param>
public void HidePreviousRoom(bool destroy = true)
{
if (NumberOfRooms > 2)

View File

@@ -7,6 +7,9 @@ using UnityEngine;
namespace EscapeRoomEngine.Engine.Runtime
{
/// <summary>
/// An engine theme decides the room size, look and available modules for the engine.
/// </summary>
[CreateAssetMenu(menuName = "Engine Theme")]
public class EngineTheme : ScriptableObject
{
@@ -23,9 +26,11 @@ namespace EscapeRoomEngine.Engine.Runtime
#region Theme
[BoxGroup("Theme")] [Required]
[Tooltip("The tile that rooms are generated from.")]
public SpaceTile spaceTile;
[BoxGroup("Theme")]
[Tooltip("The environment that is placed around every generated room.")]
public GameObject environment;
[BoxGroup("Theme")]
@@ -39,6 +44,7 @@ namespace EscapeRoomEngine.Engine.Runtime
public DoorModuleDescription spawnDoor;
[BoxGroup("Doors")] [ValidateInput("IsNotEmpty", "At least one exit door type is required.")]
[Tooltip("The types of exit doors this theme provides. Entrance doors are connected to the exit doors and don't need to be specified here.")]
public List<DoorModuleDescription> exitDoorTypes;
#endregion

View File

@@ -9,6 +9,9 @@ using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;
namespace EscapeRoomEngine.Engine.Runtime.Measurements
{
/// <summary>
/// This class is the main way to interact with measurements.
/// </summary>
public static class Measure
{
/// <summary>
@@ -19,20 +22,46 @@ namespace EscapeRoomEngine.Engine.Runtime.Measurements
private static Dictionary<int, PuzzleMeasurement> _runningMeasurements;
private static Session _currentSession;
/// <summary>
/// The average time to solve a specific puzzle.
/// </summary>
public static float AverageTime(PuzzleModuleDescription puzzle) =>
PuzzleStorage.Instance.Load(puzzle).AverageTimeToSolve;
/// <summary>
/// The average time to solve a group of puzzles.
/// </summary>
public static float AverageTime(IEnumerable<PuzzleModuleDescription> puzzles) => puzzles.Sum(AverageTime);
/// <summary>
/// The average time to solve a specific room.
/// </summary>
/// <remarks>This only counts puzzles already placed in a room.</remarks>
public static float AverageTime(Room room) =>
room.puzzles.Sum(puzzle => AverageTime((PuzzleModuleDescription)puzzle.description));
/// <summary>
/// Estimate the time a specific puzzle will take to be solved by the current player.
/// </summary>
public static float EstimateTime(PuzzleModuleDescription puzzle) =>
PuzzleStorage.Instance.Load(puzzle).EstimateTimeToSolve(SessionPercentile());
/// <summary>
/// Estimate the time a group of puzzles will take to be solved by the current player.
/// </summary>
public static float EstimateTime(IEnumerable<PuzzleModuleDescription> puzzles) => puzzles.Sum(EstimateTime);
/// <summary>
/// Estimate the time a room will take to be solved by the current player.
/// </summary>
/// <remarks>This only counts puzzles already placed in a room.</remarks>
public static float EstimateTime(Room room) =>
room.puzzles.Sum(puzzle => EstimateTime((PuzzleModuleDescription)puzzle.description));
/// <summary>
/// The estimated percentile the current player is placed in.
/// </summary>
public static float SessionPercentile() => _currentSession?.MeanPercentile ?? 0.5f;
/// <summary>
/// Start counting the time to solve a specific puzzle.
/// </summary>
public static void StartMeasuring(PuzzleModuleDescription puzzle)
{
_runningMeasurements[puzzle.Id] = new PuzzleMeasurement
@@ -44,6 +73,9 @@ namespace EscapeRoomEngine.Engine.Runtime.Measurements
Logger.Log($"Started measuring {puzzle}", LogType.Measuring);
}
/// <summary>
/// A puzzle has been solved and the measurement should be stopped. This will also store the finished measurement in the database.
/// </summary>
public static void Solve(PuzzleModuleDescription puzzle)
{
if (_currentSession == null)
@@ -62,6 +94,9 @@ namespace EscapeRoomEngine.Engine.Runtime.Measurements
Logger.Log($"Solved {puzzle} with measurement {measurement}", LogType.Measuring);
}
/// <summary>
/// Start a new session and place the current player at the 50th percentile.
/// </summary>
public static void StartSession()
{
_currentSession = new Session();
@@ -75,6 +110,9 @@ namespace EscapeRoomEngine.Engine.Runtime.Measurements
Logger.Log($"Started {_currentSession}", LogType.Measuring);
}
/// <summary>
/// End a session and store it in the database.
/// </summary>
public static void EndSession(float time)
{
if (Store)

View File

@@ -3,10 +3,22 @@ using Realms;
namespace EscapeRoomEngine.Engine.Runtime.Measurements
{
/// <summary>
/// The target and estimated time, along with the percentile since the last section, that will be stored in the database.
/// </summary>
public class PlanResult : EmbeddedObject
{
/// <summary>
/// The target time set by the game master when this section ended.
/// </summary>
public float TargetTime { get; set; }
/// <summary>
/// The percentile of the current player during this section.
/// </summary>
public float SectionPercentile { get; set; }
/// <summary>
/// The estimated time to complete the whole puzzle plan when this section ended.
/// </summary>
public float TimeEstimation { get; set; }
[UsedImplicitly]

View File

@@ -8,15 +8,30 @@ using Realms;
namespace EscapeRoomEngine.Engine.Runtime.Measurements
{
/// <summary>
/// The representation of a specific puzzle in the database. This includes all measurements for this puzzle.
/// </summary>
[SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")]
public class Puzzle : RealmObject
{
[PrimaryKey]
public int ID { get; set; }
/// <summary>
/// All puzzle measurements recorded for this puzzle.
/// </summary>
public IList<PuzzleMeasurement> Measurements { get; }
/// <summary>
/// How much time has been spent on this puzzle in total. This is used to calculate the average time to solve this puzzle.
/// </summary>
public float TotalTimeSpentOnPuzzle => Measurements.Sum(measurement => measurement.Time);
/// <summary>
/// The average time to solve this puzzle using all measurements recorded for it.
/// </summary>
public float AverageTimeToSolve => Measurements.Count > 0 ? TotalTimeSpentOnPuzzle / Measurements.Count : 0f;
/// <summary>
/// The normal distribution of all recorded measurements.
/// </summary>
public NormalDistribution Distribution => new(TimesAsSamples());
[UsedImplicitly]

View File

@@ -3,11 +3,23 @@ using Realms;
namespace EscapeRoomEngine.Engine.Runtime.Measurements
{
/// <summary>
/// A single measurement, consisting of when a puzzle was started and solved, stored in the database.
/// </summary>
public class PuzzleMeasurement : EmbeddedObject
{
/// <summary>
/// The relative time since the engine was initialised of when this puzzle was started.
/// </summary>
public float TimeStarted { get; set; }
/// <summary>
/// The relative time since the engine was initialised of when this puzzle was solved.
/// </summary>
public float TimeSolved { get; set; }
/// <summary>
/// The total time taken to solve this puzzle during this measurement.
/// </summary>
public float Time => TimeSolved - TimeStarted;
public override string ToString()

View File

@@ -7,12 +7,22 @@ using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;
namespace EscapeRoomEngine.Engine.Runtime.Measurements
{
/// <summary>
/// The storage engine that manages the data in a realm database.
/// </summary>
public class PuzzleStorage : MonoBehaviour
{
/// <summary>
/// The schema version must be updated whenever the schema in the database changed.
/// </summary>
private const int SchemaVersion = 2;
/// <summary>
/// The active instance of the storage engine.
/// </summary>
public static PuzzleStorage Instance { get; private set; }
[Tooltip("The path of the database relative to where the engine stores persistent data.")]
[SerializeField]
private string databasePath = "measurements.realm";
@@ -20,6 +30,7 @@ namespace EscapeRoomEngine.Engine.Runtime.Measurements
private void OnEnable()
{
// configure special actions during specific migrations of the database
var config = new RealmConfiguration
{
SchemaVersion = SchemaVersion,
@@ -59,6 +70,9 @@ namespace EscapeRoomEngine.Engine.Runtime.Measurements
#region Session
/// <summary>
/// End a session and store it to the database.
/// </summary>
public void EndSession(Session session, float time)
{
session.Time = time;
@@ -70,6 +84,10 @@ namespace EscapeRoomEngine.Engine.Runtime.Measurements
#region Puzzles
/// <summary>
/// Create a new puzzle for a specific description and store it in the database.
/// </summary>
/// <remarks>This requires that the puzzle does not yet exist in the database.</remarks>
private Puzzle New(PuzzleModuleDescription puzzle)
{
Puzzle created = null;
@@ -79,10 +97,20 @@ namespace EscapeRoomEngine.Engine.Runtime.Measurements
return created;
}
/// <summary>
/// Load a specific puzzle from the database or create it if it wasn't found.
/// </summary>
private Puzzle LoadOrNew(PuzzleModuleDescription puzzle) => _realm.Find<Puzzle>(puzzle.Id) ?? New(puzzle);
/// <summary>
/// Load a specific puzzle from the database.
/// </summary>
/// <returns>The puzzle or null if it wasn't found in the database.</returns>
public Puzzle Load(PuzzleModuleDescription puzzle) => _realm.Find<Puzzle>(puzzle.Id);
/// <summary>
/// Delete all records concerning a specific puzzle from the database irreversibly.
/// </summary>
public void Delete(PuzzleModuleDescription puzzle)
{
var found = Load(puzzle);
@@ -92,6 +120,9 @@ namespace EscapeRoomEngine.Engine.Runtime.Measurements
}
}
/// <summary>
/// End the measurement for a specific puzzle and store it in the database. Also stores the results of the current puzzle plan.
/// </summary>
public void EndMeasurement(Session session, PuzzleModuleDescription puzzle, PuzzleMeasurement measurement)
{
var found = LoadOrNew(puzzle);

View File

@@ -8,16 +8,33 @@ using Realms;
namespace EscapeRoomEngine.Engine.Runtime.Measurements
{
/// <summary>
/// The representation of a session in the database. This stores all plan results during this session and what puzzles were solved.
/// </summary>
[SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")]
public class Session : RealmObject
{
[PrimaryKey]
public ObjectId ID { get; set; }
public float Time { get; set; }
/// <summary>
/// The plan results for each section during this section.
/// </summary>
/// <remarks>The order of the plan results corresponds with the order of the solved puzzles.</remarks>
public IList<PlanResult> PlanResults { get; }
/// <summary>
/// The puzzles solved during this session.
/// </summary>
/// <remarks>The order of the solved puzzles corresponds with the order of the plan results.</remarks>
public IList<Puzzle> PuzzlesSolved { get; }
/// <summary>
/// The section percentiles the current player has been placed in.
/// </summary>
public IEnumerable<float> Percentiles => PlanResults.Select(result => result.SectionPercentile);
/// <summary>
/// The average of all section percentiles.
/// </summary>
public float MeanPercentile => PlanResults.Count == 0 ? .5f : Probability.Mean(Percentiles.ToArray());
[UsedImplicitly]

View File

@@ -3,9 +3,14 @@ using UnityEngine;
namespace EscapeRoomEngine.Engine.Runtime.Modules
{
/// <summary>
/// A <see cref="StepPuzzle"/> whose steps are considered a cycle that can be started anywhere and must be stepped through once.
/// </summary>
public class CyclicStepPuzzle : StepPuzzle
{
[BoxGroup("Step Puzzle")] [Min(0)] public int cycleStep;
[Tooltip("The current step in the cycle.")]
[BoxGroup("Step Puzzle")] [Min(0)]
public int cycleStep;
protected override void CheckStep(int step)
{

View File

@@ -9,13 +9,22 @@ namespace EscapeRoomEngine.Engine.Runtime.Modules
Entrance = ModuleType.DoorEntrance, Exit = ModuleType.DoorExit
}
/// <summary>
/// The main component of any door module.
/// </summary>
[Serializable]
public class DoorModule : Module
{
public bool IsEntrance => IsType((ModuleType)DoorType.Entrance);
public bool IsExit => IsType((ModuleType)DoorType.Exit);
/// <summary>
/// The module state of this door as a <see cref="DoorState"/>.
/// </summary>
public DoorState DoorState => DoorState.FromState(State);
/// <summary>
/// The module state of the connected door as a <see cref="DoorState"/>.
/// </summary>
public DoorState ConnectedDoorState => Passage.Other(this).DoorState;
/// <summary>
@@ -47,6 +56,7 @@ namespace EscapeRoomEngine.Engine.Runtime.Modules
{
base.InstantiateModule(parent);
// the room needs to know about this door
space.room.AddDoor(this);
}

View File

@@ -2,6 +2,9 @@
namespace EscapeRoomEngine.Engine.Runtime.Modules
{
/// <summary>
/// The <see cref="ModuleDescription"/> for a <see cref="DoorModule"/>. Includes the description of the connected door.
/// </summary>
[CreateAssetMenu(menuName = "Modules/Door")]
public class DoorModuleDescription : ModuleDescription
{

View File

@@ -10,10 +10,19 @@ namespace EscapeRoomEngine.Engine.Runtime.Modules
public delegate void DoorEventHandler(DoorModule source, DoorEventType e);
/// <summary>
/// The <see cref="ModuleState"/> of a <see cref="DoorModule"/>. Handles all events that can occur for a door.
/// </summary>
public class DoorState : ModuleState
{
/// <summary>
/// Add event handlers to this hook to receive all events concerning this door.
/// </summary>
public event DoorEventHandler DoorEvent;
/// <summary>
/// The door module this state belongs to.
/// </summary>
protected DoorModule Module { get; set; }
public bool Unlocked
{

View File

@@ -10,6 +10,9 @@ using Object = UnityEngine.Object;
namespace EscapeRoomEngine.Engine.Runtime.Modules
{
/// <summary>
/// The main representation of a module. Contains references to the module description, state and handles its placement.
/// </summary>
public class Module
{
public readonly List<Module> relatedModules = new();

View File

@@ -5,9 +5,13 @@ using UnityEngine;
namespace EscapeRoomEngine.Engine.Runtime.Modules
{
/// <summary>
/// The description of a specific module variant. Includes any requirements, the types and the module state that should be initialised with the module.
/// </summary>
[CreateAssetMenu(menuName = "Modules/Generic Module")]
public class ModuleDescription : ScriptableObject
{
[Tooltip("The module types decide how this module can be used.")]
public List<ModuleType> types = new();
public ModuleState modulePrefab;
[BoxGroup("Requirements")]

View File

@@ -2,8 +2,12 @@
namespace EscapeRoomEngine.Engine.Runtime.Modules
{
/// <summary>
/// An abstract module state. Example implementations are <see cref="DoorState"/> and <see cref="PuzzleState"/>.
/// </summary>
public abstract class ModuleState : MonoBehaviour
{
[Tooltip("The size of this module in meters.")]
public Vector2Int size = Vector2Int.one;
public abstract void SetModule(Module module);

View File

@@ -1,5 +1,8 @@
namespace EscapeRoomEngine.Engine.Runtime.Modules
{
/// <summary>
/// A module can have one or multiple types.
/// </summary>
public enum ModuleType
{
DoorEntrance, DoorExit, // door types

View File

@@ -3,8 +3,14 @@ using UnityEngine;
namespace EscapeRoomEngine.Engine.Runtime.Modules
{
/// <summary>
/// The main component of any puzzle module.
/// </summary>
public class PuzzleModule : Module
{
/// <summary>
/// The module state of this puzzle as a <see cref="PuzzleState"/>.
/// </summary>
internal PuzzleState PuzzleState => PuzzleState.FromState(State);
internal PuzzleModule(Space space, PuzzleModuleDescription description) : base(space, description) {}
@@ -13,6 +19,7 @@ namespace EscapeRoomEngine.Engine.Runtime.Modules
{
base.InstantiateModule(parent);
// the room needs to know about this puzzle
space.room.AddPuzzle(this);
}

View File

@@ -3,13 +3,21 @@ using UnityEngine;
namespace EscapeRoomEngine.Engine.Runtime.Modules
{
/// <summary>
/// The <see cref="ModuleDescription"/> for a <see cref="DoorModule"/>. Includes the description of the connected door.
/// </summary>
[CreateAssetMenu(menuName = "Modules/Puzzle")]
public class PuzzleModuleDescription : ModuleDescription
{
[Tooltip("Each puzzle has a name that will be used to display it to the game master.")]
[InfoBox("Changes to the name or version of the puzzle change its ID, which means it will not be connected with any of its previous measures any more.", EInfoBoxType.Warning)]
public string puzzleName;
[InfoBox("Puzzle measurements will only be combined for the same puzzle with the same version. If large changes are made to the puzzle that might influence its measurements, the version should be incremented.")]
public int puzzleVersion = 1;
/// <summary>
/// The ID of the puzzle is used in the database.
/// </summary>
public int Id => puzzleName.GetHashCode() + puzzleVersion;
public override string ToString()

View File

@@ -1,5 +1,6 @@
using System;
using EscapeRoomEngine.Engine.Runtime.Utilities;
using JetBrains.Annotations;
using NaughtyAttributes;
using UnityEngine;
using Logger = EscapeRoomEngine.Engine.Runtime.Utilities.Logger;
@@ -28,11 +29,20 @@ namespace EscapeRoomEngine.Engine.Runtime.Modules
public delegate void PuzzleEventHandler(PuzzleModule source, PuzzleEventType e);
/// <summary>
/// The <see cref="ModuleState"/> of a <see cref="PuzzleModule"/>. Handles all events that can occur for a door.
/// </summary>
[SelectionBase]
public class PuzzleState : ModuleState
{
/// <summary>
/// Add event handlers to this hook to receive all events concerning this puzzle.
/// </summary>
public event PuzzleEventHandler PuzzleEvent;
/// <summary>
/// The puzzle module this state belongs to.
/// </summary>
protected PuzzleModule Module { get; private set; }
public bool Solved
{
@@ -69,8 +79,11 @@ namespace EscapeRoomEngine.Engine.Runtime.Modules
#region Debug Buttons
[Button(enabledMode: EButtonEnableMode.Playmode)] public void Solve() => Solved = true;
[Button(enabledMode: EButtonEnableMode.Playmode)] public void Restart() => Solved = false;
[Button(enabledMode: EButtonEnableMode.Playmode)]
public void Solve() => Solved = true;
[UsedImplicitly]
[Button(enabledMode: EButtonEnableMode.Playmode)]
public void Restart() => Solved = false;
[Button("Trigger Wrong Input", EButtonEnableMode.Playmode)]
public void WrongInput()
{

View File

@@ -1,5 +1,8 @@
namespace EscapeRoomEngine.Engine.Runtime.Modules
{
/// <summary>
/// The spawn door is used in the first room.
/// </summary>
internal class Spawn : DoorState
{
private void Start()

View File

@@ -6,11 +6,20 @@ using UnityEngine;
namespace EscapeRoomEngine.Engine.Runtime.Modules
{
/// <summary>
/// The <see cref="PuzzleState"/> of a puzzle that requires a specific state to be solved.
/// </summary>
public class StatePuzzle : PuzzleState
{
[Tooltip("The state this puzzle starts at.")]
[BoxGroup("State Puzzle")]
[SerializeField]
protected List<int> states, solution;
protected List<int> states;
[Tooltip("The state that needs to be reached for this puzzle to be solved.")]
[BoxGroup("State Puzzle")]
[SerializeField]
protected List<int> solution;
[Tooltip("Set this to the total number of states this puzzle has.")]
[BoxGroup("State Puzzle")]
[SerializeField]
[ValidateInput("CorrectStateCount", "States count must be equal to the number of states and the length of the solution.")]
@@ -76,7 +85,7 @@ namespace EscapeRoomEngine.Engine.Runtime.Modules
}
}
[UsedImplicitly]
[UsedImplicitly] // used for field validation
private bool CorrectStateCount(int count) =>
states != null && count == states.Count &&
solution != null && count == solution.Count;

View File

@@ -6,12 +6,16 @@ using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;
namespace EscapeRoomEngine.Engine.Runtime.Modules
{
/// <summary>
/// The <see cref="PuzzleState"/> of a puzzle that includes a number of steps that need to be completed to solve it.
/// </summary>
public abstract class StepPuzzle : PuzzleState
{
[BoxGroup("Step Puzzle")]
[InfoBox("In easy mode, the step puzzle will not reset if a wrong input is made.")]
public bool easyMode;
[BoxGroup("Step Puzzle")] [Min(0)] public int totalSteps;
[BoxGroup("Step Puzzle")] [Min(0)]
public int totalSteps;
[BoxGroup("Step Puzzle")] [ProgressBar("Step", "totalSteps", EColor.Orange)]
public int currentStep;

View File

@@ -14,12 +14,24 @@ namespace EscapeRoomEngine.Engine.Runtime
Orientation.North, Orientation.East, Orientation.South, Orientation.West
});
/// <summary>
/// Return the orientation corresponding to an angle, starting at 0 degrees North and going clockwise around the compass.
/// </summary>
public static Orientation FromAngle(int angle) => (Orientation)angle;
/// <summary>
/// Turn an orientation into its corresponding angle.
/// </summary>
public static float Angle(this Orientation orientation) => (float)orientation;
/// <summary>
/// Turn an orientation into its corresponding angle.
/// </summary>
public static int AngleInt(this Orientation orientation) => (int)orientation;
/// <summary>
/// Rotate an orientation by a specific amount of quarter rotations. Rotating <c>East</c> thrice would return <c>North</c>.
/// </summary>
public static Orientation Rotated(this Orientation orientation, int quarterRotations) =>
orientation + quarterRotations * 90;
}

View File

@@ -5,9 +5,18 @@ using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;
namespace EscapeRoomEngine.Engine.Runtime
{
/// <summary>
/// The passage handles the two door modules that connect two spaces.
/// </summary>
public class Passage
{
/// <summary>
/// The exit door in the previous room.
/// </summary>
internal readonly DoorModule fromOut;
/// <summary>
/// The entrance door in the next room.
/// </summary>
internal DoorModule toIn;
internal Passage(DoorModule from)
@@ -20,6 +29,9 @@ namespace EscapeRoomEngine.Engine.Runtime
fromOut = from;
}
/// <summary>
/// Place an entrance door in the same position relative to the room origin as the exit door.
/// </summary>
internal void PlaceEntrance(DoorModule door)
{
if (!door.IsEntrance)
@@ -35,6 +47,9 @@ namespace EscapeRoomEngine.Engine.Runtime
Logger.Log($"Placed entrance {toIn} at {toIn.RrPosition} (RR)", LogType.ModulePlacement);
}
/// <summary>
/// Connect the two doors in this passage.
/// </summary>
internal void ConnectDoors()
{
toIn.Passage = this;
@@ -43,6 +58,9 @@ namespace EscapeRoomEngine.Engine.Runtime
Logger.Log($"Connected passage from {fromOut} to {toIn}", LogType.PassageConnection);
}
/// <summary>
/// Return the door connected to a given door in this passage.
/// </summary>
internal DoorModule Other(DoorModule of)
{
if (of.Equals(fromOut))

View File

@@ -5,6 +5,9 @@ using Range = EscapeRoomEngine.Engine.Runtime.Utilities.Range;
namespace EscapeRoomEngine.Engine.Runtime
{
/// <summary>
/// A placement defines the position, size and orientation of a module or space.
/// </summary>
public struct Placement
{
/// <summary>
@@ -16,6 +19,9 @@ namespace EscapeRoomEngine.Engine.Runtime
public Vector2Int size;
public Orientation orientation;
/// <summary>
/// Return the bottom left corner position of the module.
/// </summary>
public Vector3Int BottomLeft {
get
{

View File

@@ -4,6 +4,9 @@ using UnityEngine;
namespace EscapeRoomEngine.Engine.Runtime.Requirements
{
/// <summary>
/// This requirement guarantees that the module faces the center of the space.
/// </summary>
[CreateAssetMenu(menuName = "Requirements/Face Space Center")]
public class FaceSpaceCenter : PlacementRequirement
{

View File

@@ -4,6 +4,9 @@ using UnityEngine;
namespace EscapeRoomEngine.Engine.Runtime.Requirements
{
/// <summary>
/// This requirement forces a specific orientation.
/// </summary>
[CreateAssetMenu(menuName = "Requirements/Lock Orientation")]
public class LockOrientation : PlacementRequirement
{

View File

@@ -4,6 +4,9 @@ using UnityEngine;
namespace EscapeRoomEngine.Engine.Runtime.Requirements
{
/// <summary>
/// This requirement prevents modules from overlapping. For two models not to overlap, both of them need this requirement.
/// </summary>
[CreateAssetMenu(menuName = "Requirements/No Overlap")]
public class NoOverlap : PlacementRequirement
{

View File

@@ -4,6 +4,9 @@ using UnityEngine;
namespace EscapeRoomEngine.Engine.Runtime.Requirements
{
/// <summary>
/// This requirement guarantees that the back side of the module is placed at the edge of the space.
/// </summary>
[CreateAssetMenu(menuName = "Requirements/Place Along Space Edges")]
public class PlaceAlongSpaceEdges : PlacementRequirement
{

View File

@@ -4,6 +4,9 @@ using UnityEngine;
namespace EscapeRoomEngine.Engine.Runtime.Requirements
{
/// <summary>
/// This requirement allows any placement. Used for testing purposes.
/// </summary>
[CreateAssetMenu(menuName = "Requirements/Place Anywhere")]
public class PlaceAnywhere : PlacementRequirement
{

View File

@@ -4,6 +4,9 @@ using UnityEngine;
namespace EscapeRoomEngine.Engine.Runtime.Requirements
{
/// <summary>
/// This requirement places a module exactly in the same position as its first related module.
/// </summary>
[CreateAssetMenu(menuName = "Requirements/Place With Related Module")]
public class PlaceWithRelatedModule : PlacementRequirement
{

View File

@@ -5,6 +5,9 @@ using EscapeRoomEngine.Engine.Runtime.Utilities;
namespace EscapeRoomEngine.Engine.Runtime.Requirements
{
/// <summary>
/// The main class any placement requirement inherits from. To place a module, every possible placement inside the space is put into a list of candidates that is then filtered by all placement requirements.
/// </summary>
public abstract class PlacementRequirement : Requirement<Placement>
{
protected abstract override List<Placement> FilterCandidates(List<Placement> candidates, Module module, Space space);

View File

@@ -5,6 +5,9 @@ using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;
namespace EscapeRoomEngine.Engine.Runtime.Requirements
{
/// <summary>
/// The main class any precondition requirement inherits from. To place a module, each precondition requirement must return the true-set.
/// </summary>
public abstract class PreconditionRequirement : Requirement<bool>
{
private static readonly List<bool> TrueSet = new(new[] { true });

View File

@@ -5,6 +5,9 @@ using UnityEngine;
namespace EscapeRoomEngine.Engine.Runtime.Requirements
{
/// <summary>
/// This requirement guarantees that a module is placed in the same space as its related module.
/// </summary>
[CreateAssetMenu(menuName = "Requirements/Related Module")]
class RelatedModule : PreconditionRequirement
{

View File

@@ -5,6 +5,9 @@ using UnityEngine;
namespace EscapeRoomEngine.Engine.Runtime.Requirements
{
/// <summary>
/// A generic requirement class that defines how requirements filter candidates.
/// </summary>
public abstract class Requirement<T> : ScriptableObject
{
protected abstract List<T> FilterCandidates(List<T> candidates, Module module, Space space);

View File

@@ -8,12 +8,18 @@ using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;
namespace EscapeRoomEngine.Engine.Runtime
{
/// <summary>
/// A room manages an entrance to its first space and exit from its last space. It is also responsible for instantiating all spaces in it.
/// </summary>
public class Room
{
internal Passage entrance, exit;
internal GameObject roomObject;
internal readonly List<PuzzleModule> puzzles = new();
/// <summary>
/// Return whether this room is the last room of the experience, determined by whether this room has an exit.
/// </summary>
internal bool LastRoom => exit == null;
private readonly List<Space> _spaces = new();

View File

@@ -10,12 +10,18 @@ using Range = EscapeRoomEngine.Engine.Runtime.Utilities.Range;
namespace EscapeRoomEngine.Engine.Runtime
{
/// <summary>
/// The space is responsible for placing and instantiating any modules in it.
/// </summary>
public class Space
{
/// <summary>
/// The room relative (<i>RR</i>) dimensions of this space.
/// </summary>
internal readonly Placement rrPlacement;
/// <summary>
/// The room this space is in.
/// </summary>
internal readonly Room room;
/// <summary>

View File

@@ -21,6 +21,9 @@ namespace EscapeRoomEngine.Engine.Runtime
public GameObject prefab;
}
/// <summary>
/// A space tile contains all game objects necessary to create a space from 2m x 2m up to any size.
/// </summary>
public class SpaceTile : MonoBehaviour
{
public static HashSet<TileLocation> EveryTileLocation => new(new[]

View File

@@ -13,12 +13,22 @@ namespace EscapeRoomEngine.Engine.Runtime.UI
Stopped, Paused, Running
}
/// <summary>
/// The component that manages the gamemaster UI and the time.
/// </summary>
public class GameControl : MonoBehaviour
{
/// <summary>
/// The active instance of the game control.
/// </summary>
public static GameControl Instance { get; private set; }
[Tooltip("How much time in seconds should be between UI updates.")]
[SerializeField]
private float uiUpdateInterval = 1, planUpdateInterval = 1;
private float uiUpdateInterval = 1;
[Tooltip("How much time in seconds should be between puzzle plan updates.")]
[SerializeField]
private float planUpdateInterval = 1;
[BoxGroup("Internal")] [SerializeField]
private Button startButton, stopButton, pauseButton, addTimeButton, removeTimeButton;
[BoxGroup("Internal")] [SerializeField]
@@ -36,10 +46,25 @@ namespace EscapeRoomEngine.Engine.Runtime.UI
set => puzzlePlan.Puzzles = value;
}
/// <summary>
/// The time that elapsed since the game was started.
/// </summary>
public float TimeElapsed { get; private set; }
/// <summary>
/// The time the player has spent in this room.
/// </summary>
public float TimeInRoom { get; set; }
/// <summary>
/// The target time set by the game master.
/// </summary>
public float TargetTime { get; private set; }
/// <summary>
/// The estimated total time the player will have spent when they finish this room.
/// </summary>
public float EstimatedTimeRoom { get; private set; }
/// <summary>
/// The estimated total time the player will spend in the experience.
/// </summary>
public float EstimatedTime { get; private set; }
private float _previousUIUpdate, _previousPlanUpdate;

View File

@@ -4,6 +4,9 @@ using UnityEngine.UI;
namespace EscapeRoomEngine.Engine.Runtime.UI
{
/// <summary>
/// This component is responsible to change the icon of the pause button depending on the game state.
/// </summary>
public class PauseButton : MonoBehaviour
{
[BoxGroup("Internal")] [SerializeField]

View File

@@ -2,12 +2,17 @@ using System.Collections.Generic;
using EscapeRoomEngine.Engine.Runtime.Modules;
using NaughtyAttributes;
using UnityEngine;
using UnityEngine.UI;
namespace EscapeRoomEngine.Engine.Runtime.UI
{
/// <summary>
/// The puzzle plan UI component that displays the current puzzle plan in the game master window.
/// </summary>
public class PuzzlePlan : MonoBehaviour
{
/// <summary>
/// The distance from one entry to the next.
/// </summary>
[SerializeField]
private Vector2 entryOffset;
[BoxGroup("Internal")] [SerializeField]

View File

@@ -1,4 +1,3 @@
using System;
using EscapeRoomEngine.Engine.Runtime.Measurements;
using EscapeRoomEngine.Engine.Runtime.Modules;
using EscapeRoomEngine.Engine.Runtime.Utilities;
@@ -8,6 +7,9 @@ using UnityEngine.UI;
namespace EscapeRoomEngine.Engine.Runtime.UI
{
/// <summary>
/// An entry in the <see cref="PuzzlePlan"/> UI component that displays the name and estimated time for a puzzle.
/// </summary>
public class PuzzlePlanEntry : MonoBehaviour
{
[BoxGroup("Internal")] [Required] [SerializeField]

View File

@@ -2,9 +2,11 @@ using System;
// ReSharper disable ReturnTypeCanBeEnumerable.Global
// ReSharper disable ParameterTypeCanBeEnumerable.Local
namespace EscapeRoomEngine.Engine.Runtime.Utilities
{
/// <summary>
/// The backtrack algorithm can calculate the subset of a set of samples with the sum closest to a target value.
/// </summary>
public struct Backtrack
{
private int[] indices;
@@ -95,6 +97,9 @@ namespace EscapeRoomEngine.Engine.Runtime.Utilities
return Sum(leave) < Sum(pick) ? leave : pick;
}
/// <summary>
/// Combine the two brute force algorithms to calculate the optimal subset.
/// </summary>
public int[] BruteForce()
{
var lower = BruteForceLower();

View File

@@ -1,8 +1,11 @@
using UnityEditor;
using UnityEngine;
namespace EscapeRoomEngine.Desert.Runtime
namespace EscapeRoomEngine.Engine.Runtime.Utilities
{
/// <summary>
/// This component disables a certain game object from being selectable in the scene view.
/// </summary>
public class DisablePicking : MonoBehaviour
{
public GameObject objectToDisable;

View File

@@ -3,6 +3,9 @@ using UnityEngine;
namespace EscapeRoomEngine.Engine.Runtime.Utilities
{
/// <summary>
/// Stores both the hdr and ldr version of a colour.
/// </summary>
[Serializable]
public struct DynamicColor
{

View File

@@ -19,25 +19,24 @@ namespace EscapeRoomEngine.Engine.Runtime.Utilities
public class Logger : MonoBehaviour
{
public static Logger DefaultLogger
{
get
{
if (_foundLogger == null)
{
_foundLogger = FindObjectOfType<Logger>();
}
return _foundLogger;
}
}
private static Logger _foundLogger;
/// <summary>
/// The active instance of the logger.
/// </summary>
private static Logger Instance { get; set; }
[Tooltip("Toggle logging on or off globally.")]
public bool loggingEnabled;
[Tooltip("This list determines what types of log messages should be displayed.")]
public List<LogType> typeFilter;
private void Awake()
{
Instance = this;
}
public static void Log(string message, LogType type)
{
if (DefaultLogger.loggingEnabled && DefaultLogger.typeFilter.Contains(type))
if (Instance.loggingEnabled && Instance.typeFilter.Contains(type))
{
Debug.Log($"<b>[{type}]</b> {message}");
}

View File

@@ -6,12 +6,25 @@ using Random = System.Random;
namespace EscapeRoomEngine.Engine.Runtime.Utilities
{
/// <summary>
/// The representation of a normal distribution with a certain mean μ and standard deviation σ.
/// </summary>
[Serializable]
public struct NormalDistribution
{
public float μ, σ;
/// <summary>
/// The mean of this distribution.
/// </summary>
public float μ;
/// <summary>
/// The standard deviation of this distribution.
/// </summary>
public float σ;
public static NormalDistribution Standard => new NormalDistribution { μ = 0, σ = 1 };
/// <summary>
/// Generate a standard normal distribution.
/// </summary>
public static NormalDistribution Standard => new() { μ = 0, σ = 1 };
public NormalDistribution(float[] samples) : this()
{
@@ -19,13 +32,25 @@ namespace EscapeRoomEngine.Engine.Runtime.Utilities
σ = Probability.StandardDeviation(samples, μ);
}
/// <summary>
/// Sample a random value from this distribution.
/// </summary>
public float Sample() => σ * Probability.Normal() + μ;
/// <summary>
/// Sample the CDF of this distribution.
/// </summary>
public float Cumulative(float x) => (float)new Normal(μ, σ).CumulativeDistribution(x);
/// <summary>
/// Sample the inverse CDF of this distribution.
/// </summary>
public float InverseCumulative(float x) => (float)new Normal(μ, σ).InverseCumulativeDistribution(x);
}
/// <summary>
/// This class is used for probability calculations.
/// </summary>
public static class Probability
{
private static readonly Random _random = new();
@@ -50,6 +75,9 @@ namespace EscapeRoomEngine.Engine.Runtime.Utilities
return u1 * Mathf.Sqrt(-2 * Mathf.Log(square) / square);
}
/// <summary>
/// Calculate the mean of a list of samples.
/// </summary>
public static float Mean(float[] samples)
{
if (samples.Length == 0)
@@ -60,7 +88,13 @@ namespace EscapeRoomEngine.Engine.Runtime.Utilities
return samples.Sum() / samples.Length;
}
/// <summary>
/// Calculate the standard deviation of a list of samples.
/// </summary>
public static float StandardDeviation(float[] samples) => StandardDeviation(samples, Mean(samples));
/// <summary>
/// Calculate the standard deviation of a list of samples without recalculating the mean.
/// </summary>
public static float StandardDeviation(float[] samples, float mean)
{
var deviations = new float[samples.Length];

View File

@@ -1,9 +1,15 @@
namespace EscapeRoomEngine.Engine.Runtime.Utilities
{
/// <summary>
/// This struct represents an integer range.
/// </summary>
public struct Range
{
public int min, max;
/// <summary>
/// The length of the range, excluding the maximum value.
/// </summary>
public int Length => max - min;
public Range(int min, int max)
@@ -12,6 +18,9 @@
this.max = max;
}
/// <summary>
/// Create an array of every value in this range.
/// </summary>
public int[] ToArray(bool includeMax = false)
{
var count = includeMax ? Length + 1 : Length;

View File

@@ -23,6 +23,9 @@ namespace EscapeRoomEngine.Engine.Runtime.Utilities
#region Randomness
/// <summary>
/// Return a random value in a range, including the last value.
/// </summary>
public static int RandomInclusive(int from, int to) => Random.Range(from, to + 1);
#endregion
@@ -41,6 +44,9 @@ namespace EscapeRoomEngine.Engine.Runtime.Utilities
return element;
}
/// <summary>
/// Return a random value from a list.
/// </summary>
public static T RandomElement<T>(this List<T> list) => list[Random.Range(0, list.Count)];
/// <summary>
@@ -59,8 +65,14 @@ namespace EscapeRoomEngine.Engine.Runtime.Utilities
public static class Vector2IntExtensions
{
/// <summary>
/// Project a 2D vector onto the floor in 3D space.
/// </summary>
public static Vector3Int ProjectAtFloor(this Vector2Int vector) => vector.ProjectAtHeight(0);
/// <summary>
/// Project a 2D vector onto a specific height in 3D space.
/// </summary>
public static Vector3Int ProjectAtHeight(this Vector2Int vector, int height) =>
new Vector3Int(vector.x, height, vector.y);
}

View File

@@ -7,6 +7,9 @@ using UnityEngine;
namespace EscapeRoomEngine.Portal.Runtime
{
/// <summary>
/// The portal is a specific type of <see cref="DoorState"/> for seamless transitions between spaces.
/// </summary>
public class Portal : DoorState
{
public static readonly Matrix4x4 HalfRotation = Matrix4x4.Rotate(Quaternion.Euler(0, 180, 0));
@@ -24,6 +27,9 @@ namespace EscapeRoomEngine.Portal.Runtime
/// </summary>
[BoxGroup("Internal")] public Transform portalTransform;
/// <summary>
/// Whether this portal is connected is determined by whether the reference to its linked portal is set.
/// </summary>
internal bool Connected => linkedPortal != null;
internal readonly List<PortalDriver> closePortalDrivers = new();
@@ -70,6 +76,9 @@ namespace EscapeRoomEngine.Portal.Runtime
}
}
/// <summary>
/// Begin tracking a portal driver that came close to this portal and might need to be teleported.
/// </summary>
internal void StartTrackingDriver(PortalDriver portalDriver, int entrySide)
{
closePortalDrivers.Add(portalDriver);
@@ -77,12 +86,18 @@ namespace EscapeRoomEngine.Portal.Runtime
portalDriver.entrySide = entrySide;
}
/// <summary>
/// End tracking a portal driver that distanced itself from this portal or was teleported to the linked portal.
/// </summary>
internal void StopTrackingDriver(PortalDriver portalDriver)
{
closePortalDrivers.Remove(portalDriver);
portalDriver.DisableClone(linkedPortal);
}
/// <summary>
/// Calculate which side of the portal plane a transform is.
/// </summary>
internal int CalculateSide(Transform portalDriverTransform)
{
return Math.Sign(Vector3.Dot(portalTransform.forward, portalDriverTransform.position - portalTransform.position));

View File

@@ -38,7 +38,7 @@ namespace EscapeRoomEngine.Portal.Runtime
private void Awake()
{
// get player camera
_player = Player.Current;
_player = Player.Instance;
// get portal camera
_camera = GetComponent<Camera>();

View File

@@ -4,6 +4,9 @@ using UnityEngine;
namespace EscapeRoomEngine.Portal.Runtime
{
/// <summary>
/// The portal collider is responsible for detecting <see cref="PortalDriver"/>s that come close to this portal.
/// </summary>
[RequireComponent(typeof(Collider))]
public class PortalCollider : MonoBehaviour
{

View File

@@ -4,6 +4,9 @@ using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;
namespace EscapeRoomEngine.Portal.Runtime
{
/// <summary>
/// A portal driver is any object that can be teleported by a portal like the player, their hands or objects.
/// </summary>
[RequireComponent(typeof(Collider), typeof(Rigidbody))]
public class PortalDriver : MonoBehaviour
{

View File

@@ -4,6 +4,9 @@ using UnityEngine.XR.Interaction.Toolkit;
namespace EscapeRoomEngine.Portal.Runtime
{
/// <summary>
/// The clone of a <see cref="PortalDriver"/> is a copy of it that was stripped of most of its components. Its position is updated by the driver.
/// </summary>
public class PortalDriverClone : MonoBehaviour
{
/// <summary>

View File

@@ -5,6 +5,9 @@ using UnityEngine;
namespace Test_Assets
{
/// <summary>
/// This class is used to test the <see cref="Backtrack"/> struct.
/// </summary>
public class BacktrackingTest : MonoBehaviour
{
public int[] values;

View File

@@ -5,6 +5,9 @@ using UnityEngine;
namespace Test_Assets
{
/// <summary>
/// This class is used to test the <<see cref="Probability"/> class.
/// </summary>
public class ProbabilityTest : MonoBehaviour
{
public NormalDistribution distribution = NormalDistribution.Standard;

View File

@@ -3,20 +3,20 @@ using UnityEngine;
namespace EscapeRoomEngine.VR.Runtime
{
/// <summary>
/// The player component contains references to the main camera, eye positions and hand positions of the VR player.
/// </summary>
public class Player : MonoBehaviour
{
public static Player Current
/// <summary>
/// The active player instance.
/// </summary>
public static Player Instance { get; private set; }
private void Awake()
{
get
{
if (_foundPlayer == null)
{
_foundPlayer = FindObjectOfType<Player>();
}
return _foundPlayer;
}
Instance = this;
}
private static Player _foundPlayer;
[BoxGroup("Internal")] public new Camera camera;
[BoxGroup("Internal")] [SerializeField] private Transform leftEye, rightEye;