split into multiple assemblies
This commit is contained in:
63
Assets/Engine/Runtime/Dimensions.cs
Normal file
63
Assets/Engine/Runtime/Dimensions.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime
|
||||
{
|
||||
public struct Dimensions
|
||||
{
|
||||
internal Dimensions(int width, int length, int x, int z)
|
||||
{
|
||||
this.width = width;
|
||||
this.length = length;
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
public int width;
|
||||
public int length;
|
||||
public int x;
|
||||
public int z;
|
||||
|
||||
public Vector2Int Position
|
||||
{
|
||||
get => new(x, z);
|
||||
set
|
||||
{
|
||||
x = value.x;
|
||||
z = value.y;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2Int Size
|
||||
{
|
||||
get => new(width, length);
|
||||
set
|
||||
{
|
||||
width = value.x;
|
||||
length = value.y;
|
||||
}
|
||||
}
|
||||
|
||||
public int Area => width * length;
|
||||
|
||||
public HashSet<Vector2Int> EveryPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
var positions = new HashSet<Vector2Int>();
|
||||
|
||||
for (var zIter = 0; zIter < length; zIter++)
|
||||
{
|
||||
for (var xIter = 0; xIter < width; xIter++)
|
||||
{
|
||||
positions.Add(new Vector2Int(xIter, zIter));
|
||||
}
|
||||
}
|
||||
|
||||
return positions;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => $"({width}, {length}) at ({x}, {z})";
|
||||
}
|
||||
}
|
||||
3
Assets/Engine/Runtime/Dimensions.cs.meta
Normal file
3
Assets/Engine/Runtime/Dimensions.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b70ad3679d5c48768c1d132f56dba4dd
|
||||
timeCreated: 1667222427
|
||||
8
Assets/Engine/Runtime/Editor.meta
Normal file
8
Assets/Engine/Runtime/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 49bc52aa373de5f4a9be95d26b4050ce
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
73
Assets/Engine/Runtime/Editor/EngineEditor.cs
Normal file
73
Assets/Engine/Runtime/Editor/EngineEditor.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Editor
|
||||
{
|
||||
public class EngineEditor : EditorWindow
|
||||
{
|
||||
private bool _registeredUpdateEvent;
|
||||
private Button _passToNextRoomButton, _skipCurrentRoomButton;
|
||||
|
||||
[MenuItem("Window/Engine Editor")]
|
||||
public static void ShowEditor()
|
||||
{
|
||||
var window = GetWindow<EngineEditor>();
|
||||
window.titleContent = new GUIContent("Engine Editor");
|
||||
}
|
||||
|
||||
public void CreateGUI()
|
||||
{
|
||||
_passToNextRoomButton = new Button(PassToNextRoom)
|
||||
{
|
||||
text = "Pass To Next Room"
|
||||
};
|
||||
rootVisualElement.Add(_passToNextRoomButton);
|
||||
_skipCurrentRoomButton = new Button(SkipCurrentRoom)
|
||||
{
|
||||
text = "Skip Current Room"
|
||||
};
|
||||
rootVisualElement.Add(_skipCurrentRoomButton);
|
||||
|
||||
EditorApplication.playModeStateChanged += _ => UpdateUI();
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
private void PassToNextRoom()
|
||||
{
|
||||
if (EditorApplication.isPlaying)
|
||||
{
|
||||
Engine.DefaultEngine.HidePreviousRoom();
|
||||
UpdateUI();
|
||||
}
|
||||
}
|
||||
|
||||
private void SkipCurrentRoom()
|
||||
{
|
||||
if (EditorApplication.isPlaying)
|
||||
{
|
||||
Engine.DefaultEngine.CurrentRoom.Match(some: room => room.SkipRoom());
|
||||
UpdateUI();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateUI()
|
||||
{
|
||||
if (EditorApplication.isPlaying)
|
||||
{
|
||||
if (!_registeredUpdateEvent)
|
||||
{
|
||||
Engine.DefaultEngine.UpdateUIEvent += UpdateUI;
|
||||
_registeredUpdateEvent = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_registeredUpdateEvent = false;
|
||||
}
|
||||
|
||||
_passToNextRoomButton.SetEnabled(EditorApplication.isPlaying && Engine.DefaultEngine.NumberOfRooms > 1);
|
||||
_skipCurrentRoomButton.SetEnabled(EditorApplication.isPlaying && Engine.DefaultEngine.NumberOfRooms > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Engine/Runtime/Editor/EngineEditor.cs.meta
Normal file
11
Assets/Engine/Runtime/Editor/EngineEditor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b053e3376aa6ae646b82182855e23ead
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
16
Assets/Engine/Runtime/Engine.asmdef
Normal file
16
Assets/Engine/Runtime/Engine.asmdef
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "Engine",
|
||||
"rootNamespace": "EscapeRoomEngine",
|
||||
"references": [
|
||||
"GUID:776d03a35f1b52c4a9aed9f56d7b4229"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
3
Assets/Engine/Runtime/Engine.asmdef.meta
Normal file
3
Assets/Engine/Runtime/Engine.asmdef.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d68e204354e44f2a2ecf3cfa9213c5f
|
||||
timeCreated: 1668940905
|
||||
106
Assets/Engine/Runtime/Engine.cs
Normal file
106
Assets/Engine/Runtime/Engine.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using EscapeRoomEngine.Engine.Runtime.Modules;
|
||||
using EscapeRoomEngine.Engine.Runtime.Utilities;
|
||||
using NaughtyAttributes;
|
||||
using UnityEngine;
|
||||
using Logger = EscapeRoomEngine.Engine.Runtime.Utilities.Logger;
|
||||
using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime
|
||||
{
|
||||
public class Engine : MonoBehaviour
|
||||
{
|
||||
public static Engine DefaultEngine
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_foundEngine == null)
|
||||
{
|
||||
_foundEngine = FindObjectOfType<Engine>();
|
||||
}
|
||||
return _foundEngine;
|
||||
}
|
||||
}
|
||||
private static Engine _foundEngine;
|
||||
|
||||
public delegate void UpdateUIHandler();
|
||||
public event UpdateUIHandler UpdateUIEvent;
|
||||
|
||||
[Required] public EngineTheme theme;
|
||||
|
||||
public int NumberOfRooms => _rooms.Count;
|
||||
public IOption<Room> CurrentRoom => NumberOfRooms > 0 ? Some<Room>.Of(_rooms[^1]) : None<Room>.New();
|
||||
|
||||
private readonly List<Room> _rooms = new();
|
||||
private GameObject _playSpaceOrigin, _environment;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_playSpaceOrigin = new GameObject("Play Space Origin");
|
||||
_playSpaceOrigin.transform.SetParent(transform);
|
||||
_playSpaceOrigin.transform.localPosition = new Vector3(-theme.playSpace.x / 2f, 0, -theme.playSpace.y / 2f);
|
||||
|
||||
if (theme.environment)
|
||||
{
|
||||
_environment = Instantiate(theme.environment, _playSpaceOrigin.transform, false);
|
||||
}
|
||||
|
||||
GenerateRoom();
|
||||
}
|
||||
|
||||
public void GenerateRoom()
|
||||
{
|
||||
Logger.Log("Generating room...", LogType.RoomGeneration);
|
||||
|
||||
// get the last entrance from the newest room or create a spawn passage with no entrance door for where the player will start
|
||||
var entrance = NumberOfRooms > 0 ? _rooms.Last().exit : new Passage(new DoorModule(null, theme.spawnDoor), true);
|
||||
|
||||
var room = new Room(entrance);
|
||||
_rooms.Add(room);
|
||||
|
||||
GenerateSpace(room, entrance); // TODO: rooms with more than one space
|
||||
|
||||
room.InstantiateRoom(_playSpaceOrigin.transform, (_rooms.Count - 1).ToString());
|
||||
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
private void GenerateSpace(Room room, Passage entrance)
|
||||
{
|
||||
Logger.Log("Generating space...", LogType.RoomGeneration);
|
||||
|
||||
// create space
|
||||
var space = new Space(room, entrance);
|
||||
|
||||
// add exit
|
||||
var exitDoor = new DoorModule(space, theme.exitDoorTypes.RandomElement());
|
||||
if (!space.AddModuleWithRequirements(exitDoor))
|
||||
throw new Exception("Could not satisfy requirements for exit door.");
|
||||
var exit = new Passage(exitDoor);
|
||||
|
||||
// add puzzles
|
||||
for (var i = 0; i < Utilities.Utilities.RandomInclusive(theme.puzzleCount.x, theme.puzzleCount.y); i++)
|
||||
{
|
||||
space.AddModuleWithRequirements(new PuzzleModule(space, theme.puzzleTypes.RandomElement()));
|
||||
}
|
||||
|
||||
room.AddSpace(space, exit);
|
||||
}
|
||||
|
||||
public void HidePreviousRoom()
|
||||
{
|
||||
if (NumberOfRooms > 1)
|
||||
{
|
||||
_rooms[NumberOfRooms - 2].roomObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateUI() => UpdateUIEvent?.Invoke();
|
||||
|
||||
// ReSharper disable once SuggestBaseTypeForParameter
|
||||
// ReSharper disable once UnusedMember.Local
|
||||
private bool IsNotEmpty(List<DoorModuleDescription> modules) => modules.Count > 0;
|
||||
}
|
||||
}
|
||||
11
Assets/Engine/Runtime/Engine.cs.meta
Normal file
11
Assets/Engine/Runtime/Engine.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a9b6b8b557abbb4ab172444615ebf23
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
55
Assets/Engine/Runtime/EngineTheme.cs
Normal file
55
Assets/Engine/Runtime/EngineTheme.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using EscapeRoomEngine.Engine.Runtime.Modules;
|
||||
using NaughtyAttributes;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime
|
||||
{
|
||||
[CreateAssetMenu(menuName = "Engine Theme")]
|
||||
public class EngineTheme : ScriptableObject
|
||||
{
|
||||
#region Size
|
||||
|
||||
[BoxGroup("Size")] [Tooltip("The minimum size that should be allowed for rooms.")]
|
||||
public Vector2Int minRoomSize;
|
||||
|
||||
[BoxGroup("Size")] [Tooltip("The size of the physical play space available to the engine.")]
|
||||
public Vector2Int playSpace;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Theme
|
||||
|
||||
[BoxGroup("Theme")] [Required]
|
||||
public SpaceTile spaceTile;
|
||||
|
||||
[BoxGroup("Theme")]
|
||||
public GameObject environment;
|
||||
|
||||
[BoxGroup("Theme")]
|
||||
[ColorUsage(false, true)]
|
||||
public Color puzzleColor, solvedColor, activeColor;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Doors
|
||||
|
||||
[BoxGroup("Doors")] [Required]
|
||||
public DoorModuleDescription spawnDoor;
|
||||
|
||||
[BoxGroup("Doors")] [ValidateInput("IsNotEmpty", "At least one exit door type is required")]
|
||||
public List<DoorModuleDescription> exitDoorTypes;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Puzzles
|
||||
|
||||
[BoxGroup("Puzzles")] [MinMaxSlider(0, 10)]
|
||||
public Vector2Int puzzleCount;
|
||||
|
||||
[BoxGroup("Puzzles")]
|
||||
public List<PuzzleModuleDescription> puzzleTypes;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
3
Assets/Engine/Runtime/EngineTheme.cs.meta
Normal file
3
Assets/Engine/Runtime/EngineTheme.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 28d04249c1c4438da94b524e7d4afff2
|
||||
timeCreated: 1668108442
|
||||
129
Assets/Engine/Runtime/GameControl.cs
Normal file
129
Assets/Engine/Runtime/GameControl.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime
|
||||
{
|
||||
public enum GameState
|
||||
{
|
||||
Stopped, Paused, Running
|
||||
}
|
||||
|
||||
public class GameControl : MonoBehaviour
|
||||
{
|
||||
private const int InitialTime = 5 * 60;
|
||||
|
||||
[SerializeField] private Button startButton, stopButton, pauseButton, resumeButton, addMinuteButton, removeMinuteButton;
|
||||
[SerializeField] private Text timeText;
|
||||
|
||||
[HideInInspector] public GameState gameState = GameState.Stopped;
|
||||
|
||||
public float TimeRemaining => _totalTime - _timeElapsed;
|
||||
|
||||
private float _timeElapsed, _totalTime;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
SetGamemasterTimeText(_totalTime);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
// Update time
|
||||
if (gameState == GameState.Running)
|
||||
{
|
||||
if (Time.deltaTime <= TimeRemaining)
|
||||
{
|
||||
_timeElapsed += Time.deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
_timeElapsed = _totalTime;
|
||||
StopGame();
|
||||
}
|
||||
|
||||
SetGamemasterTimeText(TimeRemaining);
|
||||
}
|
||||
|
||||
// Enable or disable buttons
|
||||
startButton.interactable = gameState == GameState.Stopped;
|
||||
stopButton.interactable = gameState != GameState.Stopped;
|
||||
pauseButton.interactable = gameState == GameState.Running;
|
||||
resumeButton.interactable = gameState == GameState.Paused;
|
||||
addMinuteButton.interactable = gameState != GameState.Stopped;
|
||||
removeMinuteButton.interactable = gameState != GameState.Stopped && TimeRemaining >= 60;
|
||||
}
|
||||
|
||||
#region Time Controls
|
||||
|
||||
public void StartGame()
|
||||
{
|
||||
gameState = GameState.Running;
|
||||
|
||||
_totalTime = InitialTime;
|
||||
_timeElapsed = 0;
|
||||
}
|
||||
|
||||
public void StopGame()
|
||||
{
|
||||
gameState = GameState.Stopped;
|
||||
}
|
||||
|
||||
public void PauseGame()
|
||||
{
|
||||
gameState = GameState.Paused;
|
||||
}
|
||||
|
||||
public void ResumeGame()
|
||||
{
|
||||
gameState = GameState.Running;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Change the allowed time by a specified amount of seconds.
|
||||
/// </summary>
|
||||
/// <param name="seconds">The amount of seconds that will be added to the time. Can be negative to remove time.</param>
|
||||
public void ChangeTime(int seconds)
|
||||
{
|
||||
if (_totalTime + seconds >= 0)
|
||||
{
|
||||
_totalTime += seconds;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetGamemasterTimeText(float time)
|
||||
{
|
||||
if (timeText != null)
|
||||
{
|
||||
timeText.text = TimeToText(time);
|
||||
}
|
||||
}
|
||||
|
||||
private static string TimeToText(float time)
|
||||
{
|
||||
var minutes = (int) (time / 60);
|
||||
var seconds = (int) Math.Ceiling(time - minutes * 60);
|
||||
if (seconds == 60)
|
||||
{
|
||||
minutes += 1;
|
||||
seconds = 0;
|
||||
}
|
||||
|
||||
return $"{minutes:D2}:{seconds:D2}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void ExitGame()
|
||||
{
|
||||
StopGame();
|
||||
|
||||
#if UNITY_STANDALONE
|
||||
Application.Quit();
|
||||
#endif
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.EditorApplication.isPlaying = false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Engine/Runtime/GameControl.cs.meta
Normal file
3
Assets/Engine/Runtime/GameControl.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 676ef7e7d34646dbb24b1978563ab63b
|
||||
timeCreated: 1668937602
|
||||
3
Assets/Engine/Runtime/Modules.meta
Normal file
3
Assets/Engine/Runtime/Modules.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cee3c3328d46482ba3f565eeee492f59
|
||||
timeCreated: 1667812846
|
||||
47
Assets/Engine/Runtime/Modules/DoorModule.cs
Normal file
47
Assets/Engine/Runtime/Modules/DoorModule.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Modules
|
||||
{
|
||||
public enum DoorType
|
||||
{
|
||||
Entrance = ModuleType.DoorEntrance, Exit = ModuleType.DoorExit
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class DoorModule : Module
|
||||
{
|
||||
public bool IsEntrance => IsType((ModuleType)DoorType.Entrance);
|
||||
public bool IsExit => IsType((ModuleType)DoorType.Exit);
|
||||
|
||||
internal DoorState DoorState
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State is DoorState doorState)
|
||||
{
|
||||
return doorState;
|
||||
}
|
||||
|
||||
throw new Exception("DoorModule must contain a DoorState");
|
||||
}
|
||||
}
|
||||
|
||||
internal DoorModule(Space space, DoorModuleDescription description) : base(space, description)
|
||||
{
|
||||
srDimensions.Size = Vector2Int.one; // door always has size 1x1
|
||||
}
|
||||
|
||||
internal override void InstantiateModule(Transform parent)
|
||||
{
|
||||
base.InstantiateModule(parent);
|
||||
|
||||
space.room.AddDoor(this);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{(IsEntrance ? "Entrance" : IsExit ? "Exit" : "Unknown")} door";
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Engine/Runtime/Modules/DoorModule.cs.meta
Normal file
3
Assets/Engine/Runtime/Modules/DoorModule.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05af827f50ab469f952abbb62109877d
|
||||
timeCreated: 1667226128
|
||||
14
Assets/Engine/Runtime/Modules/DoorModuleDescription.cs
Normal file
14
Assets/Engine/Runtime/Modules/DoorModuleDescription.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Modules
|
||||
{
|
||||
[CreateAssetMenu(menuName = "Modules/Door")]
|
||||
public class DoorModuleDescription : ModuleDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// The description for the door that should be connected with this one.
|
||||
/// <example>If this is a teleporter entrance, the connected door should be a teleporter exit.</example>
|
||||
/// </summary>
|
||||
public DoorModuleDescription connectedDoorDescription;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f5c1202346c34ebc9c3f701a98b50877
|
||||
timeCreated: 1667833660
|
||||
66
Assets/Engine/Runtime/Modules/DoorState.cs
Normal file
66
Assets/Engine/Runtime/Modules/DoorState.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using EscapeRoomEngine.Engine.Runtime.Utilities;
|
||||
using NaughtyAttributes;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Modules
|
||||
{
|
||||
public enum DoorEventType
|
||||
{
|
||||
Locked, Unlocked
|
||||
}
|
||||
|
||||
public delegate void DoorEventHandler(DoorModule source, DoorEventType e);
|
||||
|
||||
public class DoorState : ModuleState
|
||||
{
|
||||
public event DoorEventHandler DoorEvent;
|
||||
|
||||
private new DoorModule Module { get; set; }
|
||||
public bool Unlocked
|
||||
{
|
||||
get => _unlocked;
|
||||
private set
|
||||
{
|
||||
var type =
|
||||
!_unlocked && value ? Some<DoorEventType>.Of(DoorEventType.Unlocked)
|
||||
: _unlocked && !value ? Some<DoorEventType>.Of(DoorEventType.Locked)
|
||||
: None<DoorEventType>.New();
|
||||
_unlocked = value;
|
||||
type.Match(some: OnDoorEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private bool _unlocked;
|
||||
|
||||
private void OnDoorEvent(DoorEventType type)
|
||||
{
|
||||
Logger.Log($"{Module} has been {type}", LogType.PuzzleFlow);
|
||||
|
||||
DoorEvent?.Invoke(Module, type);
|
||||
}
|
||||
|
||||
public override void SetModule(Module module)
|
||||
{
|
||||
if (module is DoorModule doorModule)
|
||||
{
|
||||
Module = doorModule;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Tried to set wrong type of module.");
|
||||
}
|
||||
}
|
||||
|
||||
[Button(enabledMode: EButtonEnableMode.Playmode)]
|
||||
internal void Unlock()
|
||||
{
|
||||
Unlocked = true;
|
||||
}
|
||||
|
||||
[Button(enabledMode: EButtonEnableMode.Playmode)]
|
||||
internal void Lock()
|
||||
{
|
||||
Unlocked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Engine/Runtime/Modules/DoorState.cs.meta
Normal file
3
Assets/Engine/Runtime/Modules/DoorState.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 696181e3eda449d49d4c1c88b07d7b05
|
||||
timeCreated: 1668337769
|
||||
111
Assets/Engine/Runtime/Modules/Module.cs
Normal file
111
Assets/Engine/Runtime/Modules/Module.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using EscapeRoomEngine.Engine.Runtime.Utilities;
|
||||
using UnityEngine;
|
||||
using Logger = EscapeRoomEngine.Engine.Runtime.Utilities.Logger;
|
||||
using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Modules
|
||||
{
|
||||
public enum Orientation
|
||||
{
|
||||
North = 0, East = 90, South = 180, West = 270
|
||||
}
|
||||
|
||||
public class Module
|
||||
{
|
||||
public static HashSet<Orientation> EveryOrientation => new(new[]
|
||||
{
|
||||
Orientation.North, Orientation.East, Orientation.South, Orientation.West
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Get the space relative (<i>SR</i>) position of this module.
|
||||
/// </summary>
|
||||
internal Vector2Int SrPosition => srDimensions.Position;
|
||||
/// <summary>
|
||||
/// Get the room relative (<i>RR</i>) position of this module.
|
||||
/// </summary>
|
||||
internal Vector2Int RrPosition => space.ToRoomRelative(SrPosition);
|
||||
internal ModuleState State { get; private set; }
|
||||
|
||||
internal readonly ModuleDescription description;
|
||||
internal Orientation orientation;
|
||||
|
||||
/// <summary>
|
||||
/// The space relative (<i>SR</i>) dimensions of this module.
|
||||
/// </summary>
|
||||
protected Dimensions srDimensions;
|
||||
|
||||
protected readonly Space space;
|
||||
|
||||
internal Module(Space space, ModuleDescription description)
|
||||
{
|
||||
this.space = space;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
internal bool IsType(ModuleType type)
|
||||
{
|
||||
return description.types.Contains(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Place this module with a position relative to the room.
|
||||
/// </summary>
|
||||
/// <param name="rrPosition">The room relative (<i>RR</i>) position of this module. Must be inside the space dimensions.</param>
|
||||
/// <exception cref="Exception">If the position is not inside the space dimensions.</exception>
|
||||
internal void PlaceRoomRelative(Vector2Int rrPosition) => Place(space.ToSpaceRelative(rrPosition));
|
||||
/// <summary>
|
||||
/// Place this module with a position relative to the space it is in.
|
||||
/// </summary>
|
||||
/// <param name="srPosition">The space relative (<i>SR</i>) position of this module. Must be inside the space dimensions.</param>
|
||||
/// <exception cref="Exception">If the position is not inside the space dimensions.</exception>
|
||||
internal void Place(Vector2Int srPosition) {
|
||||
if (space != null && !srPosition.IsInsideRelative(space.rrDimensions))
|
||||
{
|
||||
throw new Exception($"Trying to place {this} at {srPosition}, which is outside space dimensions {space.rrDimensions}.");
|
||||
}
|
||||
|
||||
srDimensions.Position = srPosition;
|
||||
|
||||
Logger.Log($"{this} has been placed at {srPosition} (SR)", LogType.ModulePlacement);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a position relative to this module to one relative to its space.
|
||||
/// <example>The module relative position <c>(0, 1)</c> should always be in front of the module, wherever it faces.</example>
|
||||
/// </summary>
|
||||
/// <param name="mrPosition">The module relative (<i>MR</i>) position that should be converted to a space relative (<i>SR</i>) position.</param>
|
||||
/// <returns></returns>
|
||||
internal Vector2Int ToSpaceRelative(Vector2Int mrPosition)
|
||||
{
|
||||
return srDimensions.Position + orientation switch
|
||||
{
|
||||
Orientation.North => mrPosition,
|
||||
Orientation.East => new Vector2Int(mrPosition.y, -mrPosition.x),
|
||||
Orientation.South => -mrPosition,
|
||||
Orientation.West => new Vector2Int(-mrPosition.y, mrPosition.x),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
internal virtual void InstantiateModule(Transform parent)
|
||||
{
|
||||
Logger.Log($"Instantiating {this}", LogType.RoomGeneration);
|
||||
|
||||
State = Object.Instantiate(description.modulePrefab, parent, false);
|
||||
State.transform.localPosition = new Vector3(srDimensions.x + .5f, 0, srDimensions.z + .5f);
|
||||
State.transform.Rotate(Vector3.up, (float)orientation);
|
||||
State.name = ToString();
|
||||
State.SetModule(this);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Module ({string.Join(", ", description.types.ToList().ConvertAll(type => type.ToString()))})";
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Engine/Runtime/Modules/Module.cs.meta
Normal file
11
Assets/Engine/Runtime/Modules/Module.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bef95c0b6e3be5847939fffa4294f99f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
15
Assets/Engine/Runtime/Modules/ModuleDescription.cs
Normal file
15
Assets/Engine/Runtime/Modules/ModuleDescription.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using EscapeRoomEngine.Engine.Runtime.Requirements;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Modules
|
||||
{
|
||||
[CreateAssetMenu(menuName = "Modules/Generic Module")]
|
||||
public class ModuleDescription : ScriptableObject
|
||||
{
|
||||
public List<ModuleType> types = new();
|
||||
public ModuleState modulePrefab;
|
||||
public List<PlacementRequirement> placementRequirements = new();
|
||||
public List<OrientationRequirement> orientationRequirements = new();
|
||||
}
|
||||
}
|
||||
3
Assets/Engine/Runtime/Modules/ModuleDescription.cs.meta
Normal file
3
Assets/Engine/Runtime/Modules/ModuleDescription.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: abf4a405f6c64073995bded39977563e
|
||||
timeCreated: 1667831630
|
||||
11
Assets/Engine/Runtime/Modules/ModuleState.cs
Normal file
11
Assets/Engine/Runtime/Modules/ModuleState.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Modules
|
||||
{
|
||||
public class ModuleState : MonoBehaviour
|
||||
{
|
||||
public Module Module { get; protected set; }
|
||||
|
||||
public virtual void SetModule(Module module) => Module = module;
|
||||
}
|
||||
}
|
||||
3
Assets/Engine/Runtime/Modules/ModuleState.cs.meta
Normal file
3
Assets/Engine/Runtime/Modules/ModuleState.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: efdc32c450f7411385748449459a17b4
|
||||
timeCreated: 1668180361
|
||||
8
Assets/Engine/Runtime/Modules/ModuleType.cs
Normal file
8
Assets/Engine/Runtime/Modules/ModuleType.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Modules
|
||||
{
|
||||
public enum ModuleType
|
||||
{
|
||||
DoorEntrance, DoorExit, // door types
|
||||
Puzzle,
|
||||
}
|
||||
}
|
||||
3
Assets/Engine/Runtime/Modules/ModuleType.cs.meta
Normal file
3
Assets/Engine/Runtime/Modules/ModuleType.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 602fa211234b4068bca5ff38a2f9593f
|
||||
timeCreated: 1667230405
|
||||
33
Assets/Engine/Runtime/Modules/PuzzleModule.cs
Normal file
33
Assets/Engine/Runtime/Modules/PuzzleModule.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Modules
|
||||
{
|
||||
public class PuzzleModule : Module
|
||||
{
|
||||
internal PuzzleState PuzzleState
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State is PuzzleState puzzleState)
|
||||
{
|
||||
return puzzleState;
|
||||
}
|
||||
|
||||
throw new Exception("PuzzleModule must contain a PuzzleState");
|
||||
}
|
||||
}
|
||||
|
||||
internal PuzzleModule(Space space, PuzzleModuleDescription description) : base(space, description)
|
||||
{
|
||||
srDimensions.Size = Vector2Int.one; // TODO: larger modules
|
||||
}
|
||||
|
||||
internal override void InstantiateModule(Transform parent)
|
||||
{
|
||||
base.InstantiateModule(parent);
|
||||
|
||||
space.room.AddPuzzle(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Engine/Runtime/Modules/PuzzleModule.cs.meta
Normal file
3
Assets/Engine/Runtime/Modules/PuzzleModule.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed86ddbc20ae479895ab3c538ea9226f
|
||||
timeCreated: 1667873701
|
||||
9
Assets/Engine/Runtime/Modules/PuzzleModuleDescription.cs
Normal file
9
Assets/Engine/Runtime/Modules/PuzzleModuleDescription.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Modules
|
||||
{
|
||||
[CreateAssetMenu(menuName = "Modules/Puzzle")]
|
||||
public class PuzzleModuleDescription : ModuleDescription
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f928b97941e3469a9015316bb5ac1309
|
||||
timeCreated: 1667873701
|
||||
92
Assets/Engine/Runtime/Modules/PuzzleState.cs
Normal file
92
Assets/Engine/Runtime/Modules/PuzzleState.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using EscapeRoomEngine.Engine.Runtime.Utilities;
|
||||
using NaughtyAttributes;
|
||||
using Logger = EscapeRoomEngine.Engine.Runtime.Utilities.Logger;
|
||||
using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Modules
|
||||
{
|
||||
public enum PuzzleEventType
|
||||
{
|
||||
Restarted, Solved, WrongInput
|
||||
}
|
||||
|
||||
public static class PuzzleEventExtensions
|
||||
{
|
||||
public static string Description(this PuzzleEventType type, PuzzleModule module)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
PuzzleEventType.Restarted => $"{module} has been restarted",
|
||||
PuzzleEventType.Solved => $"{module} has been solved",
|
||||
PuzzleEventType.WrongInput => $"Wrong input for {module}",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void PuzzleEventHandler(PuzzleModule source, PuzzleEventType e);
|
||||
|
||||
public class PuzzleState : ModuleState
|
||||
{
|
||||
public event PuzzleEventHandler PuzzleEvent;
|
||||
public EngineTheme theme;
|
||||
|
||||
private new PuzzleModule Module { get; set; }
|
||||
public bool Solved
|
||||
{
|
||||
get => _solved;
|
||||
private set
|
||||
{
|
||||
var type =
|
||||
!_solved && value ? Some<PuzzleEventType>.Of(PuzzleEventType.Solved)
|
||||
: _solved && !value ? Some<PuzzleEventType>.Of(PuzzleEventType.Restarted)
|
||||
: None<PuzzleEventType>.New();
|
||||
_solved = value;
|
||||
type.Match(some: OnPuzzleEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private bool _solved;
|
||||
|
||||
private void OnPuzzleEvent(PuzzleEventType type)
|
||||
{
|
||||
Logger.Log(type.Description(Module), LogType.PuzzleFlow);
|
||||
|
||||
PuzzleEvent?.Invoke(Module, type);
|
||||
}
|
||||
|
||||
public override void SetModule(Module module)
|
||||
{
|
||||
if (module is PuzzleModule puzzleModule)
|
||||
{
|
||||
Module = puzzleModule;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Tried to set wrong type of module.");
|
||||
}
|
||||
}
|
||||
|
||||
[Button(enabledMode: EButtonEnableMode.Playmode)]
|
||||
public void Solve()
|
||||
{
|
||||
Solved = true;
|
||||
}
|
||||
|
||||
[Button(enabledMode: EButtonEnableMode.Playmode)]
|
||||
public void Restart()
|
||||
{
|
||||
Solved = false;
|
||||
}
|
||||
|
||||
[Button("Trigger Wrong Input", EButtonEnableMode.Playmode)]
|
||||
public void WrongInput()
|
||||
{
|
||||
if (!Solved)
|
||||
{
|
||||
OnPuzzleEvent(PuzzleEventType.WrongInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Engine/Runtime/Modules/PuzzleState.cs.meta
Normal file
3
Assets/Engine/Runtime/Modules/PuzzleState.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 265ea1efb38042b282ea67c50ac3e878
|
||||
timeCreated: 1668180832
|
||||
48
Assets/Engine/Runtime/Passage.cs
Normal file
48
Assets/Engine/Runtime/Passage.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using EscapeRoomEngine.Engine.Runtime.Modules;
|
||||
using UnityEngine;
|
||||
using Logger = EscapeRoomEngine.Engine.Runtime.Utilities.Logger;
|
||||
using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime
|
||||
{
|
||||
public class Passage
|
||||
{
|
||||
internal DoorModule fromOut, toIn;
|
||||
/// <summary>
|
||||
/// The room relative (<i>RR</i>) position of this passage.
|
||||
/// </summary>
|
||||
internal Vector2Int rrPosition;
|
||||
|
||||
internal Passage(DoorModule from, bool spawnPassage = false)
|
||||
{
|
||||
if (spawnPassage)
|
||||
{
|
||||
fromOut = from;
|
||||
rrPosition = Vector2Int.zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
ConnectFrom(from);
|
||||
}
|
||||
}
|
||||
|
||||
internal void ConnectFrom(DoorModule door)
|
||||
{
|
||||
fromOut = door;
|
||||
rrPosition = fromOut.RrPosition;
|
||||
|
||||
Logger.Log($"Connected passage from {door} at {rrPosition} (RR)", LogType.PassageConnection);
|
||||
}
|
||||
|
||||
internal void ConnectTo(DoorModule door)
|
||||
{
|
||||
toIn = door;
|
||||
|
||||
// to make sure the origin of the player doesn't move, the two doors must be placed in the same location in the same orientation
|
||||
toIn.PlaceRoomRelative(rrPosition);
|
||||
toIn.orientation = fromOut.orientation;
|
||||
|
||||
Logger.Log($"Connected passage to {door} at {rrPosition} (RR)", LogType.PassageConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Engine/Runtime/Passage.cs.meta
Normal file
3
Assets/Engine/Runtime/Passage.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58b4bdd93b9843a29cf03b304553ad10
|
||||
timeCreated: 1667222752
|
||||
3
Assets/Engine/Runtime/Requirements.meta
Normal file
3
Assets/Engine/Runtime/Requirements.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 629d8948cbb04005b050a6aa1e66c0f4
|
||||
timeCreated: 1667874124
|
||||
30
Assets/Engine/Runtime/Requirements/FaceSpaceCenter.cs
Normal file
30
Assets/Engine/Runtime/Requirements/FaceSpaceCenter.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
using EscapeRoomEngine.Engine.Runtime.Modules;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Requirements
|
||||
{
|
||||
[CreateAssetMenu(menuName = "Requirements/Face Space Center")]
|
||||
public class FaceSpaceCenter : OrientationRequirement
|
||||
{
|
||||
protected override IEnumerable<Orientation> GenerateCandidates(Module module, Space space)
|
||||
{
|
||||
var orientation = new HashSet<Orientation>(1);
|
||||
float width = space.rrDimensions.width;
|
||||
float length = space.rrDimensions.length;
|
||||
var xRel = module.SrPosition.x / (width - 1);
|
||||
var zRel = module.SrPosition.y / (length - 1);
|
||||
|
||||
if (zRel > xRel)
|
||||
{
|
||||
orientation.Add(zRel > 1 - xRel ? Orientation.South : Orientation.East);
|
||||
}
|
||||
else
|
||||
{
|
||||
orientation.Add(zRel > 1 - xRel ? Orientation.West : Orientation.North);
|
||||
}
|
||||
|
||||
return orientation;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 872da92bb04647e3bd6e741e6bb0a976
|
||||
timeCreated: 1667878978
|
||||
31
Assets/Engine/Runtime/Requirements/NoOverlap.cs
Normal file
31
Assets/Engine/Runtime/Requirements/NoOverlap.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
using EscapeRoomEngine.Engine.Runtime.Modules;
|
||||
using NaughtyAttributes;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Requirements
|
||||
{
|
||||
[CreateAssetMenu(menuName = "Requirements/No Overlap")]
|
||||
public class NoOverlap : PlacementRequirement
|
||||
{
|
||||
[InfoBox("A module relative position will be oriented with the module (e.g. (0, 1) is always in front of the module).")]
|
||||
[Label("Reserved Positions (Module Relative)")]
|
||||
public List<Vector2Int> mrReservedPositions;
|
||||
|
||||
protected override IEnumerable<Vector2Int> GenerateCandidates(Module module, Space space)
|
||||
{
|
||||
var candidates = space.rrDimensions.EveryPosition;
|
||||
|
||||
space.Modules.ForEach(m =>
|
||||
{
|
||||
candidates.Remove(m.SrPosition);
|
||||
m.description.placementRequirements
|
||||
.FindAll(r => r is NoOverlap)
|
||||
.ForEach(r => ((NoOverlap)r).mrReservedPositions
|
||||
.ForEach(p => candidates.Remove(m.ToSpaceRelative(p))));
|
||||
});
|
||||
|
||||
return candidates;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Engine/Runtime/Requirements/NoOverlap.cs.meta
Normal file
3
Assets/Engine/Runtime/Requirements/NoOverlap.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58ae9c09c887475d833d2cd4ee4ccffb
|
||||
timeCreated: 1667881856
|
||||
34
Assets/Engine/Runtime/Requirements/OrientationRequirement.cs
Normal file
34
Assets/Engine/Runtime/Requirements/OrientationRequirement.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using EscapeRoomEngine.Engine.Runtime.Modules;
|
||||
using EscapeRoomEngine.Engine.Runtime.Utilities;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Requirements
|
||||
{
|
||||
public abstract class OrientationRequirement : Requirement<Orientation>
|
||||
{
|
||||
protected abstract override IEnumerable<Orientation> GenerateCandidates(Module module, Space space);
|
||||
|
||||
public static bool TryOrienting(Module module, Space space)
|
||||
{
|
||||
var orientationCandidates = Candidates(
|
||||
Module.EveryOrientation,
|
||||
module.description.orientationRequirements,
|
||||
module, space);
|
||||
|
||||
Logger.Log($"orientation candidates: {string.Join(",", orientationCandidates.ToList().ConvertAll(c => c.ToString()))}", LogType.RequirementResolution);
|
||||
|
||||
if (orientationCandidates.Count > 0)
|
||||
{
|
||||
module.orientation = orientationCandidates.RandomElement();
|
||||
return true;
|
||||
}
|
||||
// ReSharper disable once RedundantIfElseBlock
|
||||
else
|
||||
{
|
||||
Logger.Log("Could not find suitable orientation for module", LogType.ModulePlacement);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0704ea5393394baf921f68d2dcbdfaec
|
||||
timeCreated: 1667878220
|
||||
28
Assets/Engine/Runtime/Requirements/PlaceAlongSpaceEdges.cs
Normal file
28
Assets/Engine/Runtime/Requirements/PlaceAlongSpaceEdges.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using EscapeRoomEngine.Engine.Runtime.Modules;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Requirements
|
||||
{
|
||||
[CreateAssetMenu(menuName = "Requirements/Place Along Space Edges")]
|
||||
public class PlaceAlongSpaceEdges : PlacementRequirement
|
||||
{
|
||||
protected override IEnumerable<Vector2Int> GenerateCandidates(Module module, Space space)
|
||||
{
|
||||
var edgePositions = new HashSet<Vector2Int>();
|
||||
|
||||
for (var x = 0; x < space.rrDimensions.width; x++)
|
||||
{
|
||||
edgePositions.Add(new Vector2Int(x, 0));
|
||||
edgePositions.Add(new Vector2Int(x, space.rrDimensions.length - 1));
|
||||
}
|
||||
for (var z = 0; z < space.rrDimensions.length; z++)
|
||||
{
|
||||
edgePositions.Add(new Vector2Int(0, z));
|
||||
edgePositions.Add(new Vector2Int(space.rrDimensions.width - 1, z));
|
||||
}
|
||||
|
||||
return edgePositions;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ec2cdf0145347e18e7c68221333be2c
|
||||
timeCreated: 1667876484
|
||||
15
Assets/Engine/Runtime/Requirements/PlaceAnywhere.cs
Normal file
15
Assets/Engine/Runtime/Requirements/PlaceAnywhere.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using EscapeRoomEngine.Engine.Runtime.Modules;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Requirements
|
||||
{
|
||||
[CreateAssetMenu(menuName = "Requirements/Place Anywhere")]
|
||||
public class PlaceAnywhere : PlacementRequirement
|
||||
{
|
||||
protected override IEnumerable<Vector2Int> GenerateCandidates(Module module, Space space)
|
||||
{
|
||||
return space.rrDimensions.EveryPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Engine/Runtime/Requirements/PlaceAnywhere.cs.meta
Normal file
3
Assets/Engine/Runtime/Requirements/PlaceAnywhere.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa4ff365b4e844e782cd12d8aeebd3d4
|
||||
timeCreated: 1667876850
|
||||
37
Assets/Engine/Runtime/Requirements/PlacementRequirement.cs
Normal file
37
Assets/Engine/Runtime/Requirements/PlacementRequirement.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using EscapeRoomEngine.Engine.Runtime.Modules;
|
||||
using EscapeRoomEngine.Engine.Runtime.Utilities;
|
||||
using UnityEngine;
|
||||
using Logger = EscapeRoomEngine.Engine.Runtime.Utilities.Logger;
|
||||
using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Requirements
|
||||
{
|
||||
public abstract class PlacementRequirement : Requirement<Vector2Int>
|
||||
{
|
||||
protected abstract override IEnumerable<Vector2Int> GenerateCandidates(Module module, Space space);
|
||||
|
||||
public static bool TryPlacing(Module module, Space space)
|
||||
{
|
||||
var placementCandidates = Candidates(
|
||||
space.rrDimensions.EveryPosition,
|
||||
module.description.placementRequirements,
|
||||
module, space);
|
||||
|
||||
Logger.Log($"placement candidates: {string.Join(", ", placementCandidates.ToList().ConvertAll(c => c.ToString()))}", LogType.RequirementResolution);
|
||||
|
||||
if (placementCandidates.Count > 0)
|
||||
{
|
||||
module.Place(placementCandidates.RandomElement());
|
||||
return true;
|
||||
}
|
||||
// ReSharper disable once RedundantIfElseBlock
|
||||
else
|
||||
{
|
||||
Logger.Log($"Could not find suitable placement for {module}", LogType.ModulePlacement);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65f297b8d32b4f09b89c21b88b43b646
|
||||
timeCreated: 1667876036
|
||||
27
Assets/Engine/Runtime/Requirements/Requirement.cs
Normal file
27
Assets/Engine/Runtime/Requirements/Requirement.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
using EscapeRoomEngine.Engine.Runtime.Modules;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Requirements
|
||||
{
|
||||
public abstract class Requirement<T> : ScriptableObject
|
||||
{
|
||||
protected abstract IEnumerable<T> GenerateCandidates(Module module, Space space);
|
||||
|
||||
public void Restrict(HashSet<T> candidates, Module module, Space space) =>
|
||||
candidates.IntersectWith(GenerateCandidates(module, space));
|
||||
|
||||
public static HashSet<T> Candidates(
|
||||
HashSet<T> initialCandidates,
|
||||
IEnumerable<Requirement<T>> requirements,
|
||||
Module module, Space space)
|
||||
{
|
||||
foreach (var requirement in requirements)
|
||||
{
|
||||
requirement.Restrict(initialCandidates, module, space);
|
||||
}
|
||||
|
||||
return initialCandidates;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Engine/Runtime/Requirements/Requirement.cs.meta
Normal file
3
Assets/Engine/Runtime/Requirements/Requirement.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9123b592f74444c8b6225f725b6407b3
|
||||
timeCreated: 1667874140
|
||||
87
Assets/Engine/Runtime/Room.cs
Normal file
87
Assets/Engine/Runtime/Room.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using EscapeRoomEngine.Engine.Runtime.Modules;
|
||||
using UnityEngine;
|
||||
using Logger = EscapeRoomEngine.Engine.Runtime.Utilities.Logger;
|
||||
using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime
|
||||
{
|
||||
public class Room
|
||||
{
|
||||
internal Passage entrance, exit;
|
||||
internal GameObject roomObject;
|
||||
|
||||
private readonly List<Space> _spaces = new();
|
||||
private readonly List<PuzzleModule> _puzzles = new();
|
||||
private readonly List<DoorModule> _doors = new();
|
||||
|
||||
internal Room(Passage entrance)
|
||||
{
|
||||
this.entrance = entrance;
|
||||
}
|
||||
|
||||
internal void AddSpace(Space space, Passage spaceExit)
|
||||
{
|
||||
_spaces.Add(space);
|
||||
exit = spaceExit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Solves all puzzles in this room.
|
||||
/// </summary>
|
||||
public void SkipRoom()
|
||||
{
|
||||
Logger.Log($"Skipping {this}...", LogType.PuzzleFlow);
|
||||
|
||||
_puzzles.ForEach(puzzle => puzzle.PuzzleState.Solve());
|
||||
}
|
||||
|
||||
internal void AddPuzzle(PuzzleModule puzzle)
|
||||
{
|
||||
_puzzles.Add(puzzle);
|
||||
puzzle.PuzzleState.PuzzleEvent += OnPuzzleEvent;
|
||||
}
|
||||
|
||||
private void OnPuzzleEvent(PuzzleModule puzzle, PuzzleEventType type)
|
||||
{
|
||||
if (type == PuzzleEventType.Solved)
|
||||
{
|
||||
if (_puzzles.All(p => p.PuzzleState.Solved))
|
||||
{
|
||||
exit.fromOut.DoorState.Unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddDoor(DoorModule door)
|
||||
{
|
||||
_doors.Add(door);
|
||||
door.DoorState.DoorEvent += OnDoorEvent;
|
||||
}
|
||||
|
||||
private void OnDoorEvent(DoorModule door, DoorEventType type)
|
||||
{
|
||||
if (type == DoorEventType.Unlocked && door.Equals(exit.fromOut))
|
||||
{
|
||||
Engine.DefaultEngine.GenerateRoom();
|
||||
}
|
||||
}
|
||||
|
||||
internal void InstantiateRoom(Transform parent, string name)
|
||||
{
|
||||
roomObject = new GameObject($"Room {name}");
|
||||
roomObject.transform.SetParent(parent, false);
|
||||
|
||||
for (var i = 0; i < _spaces.Count; i++)
|
||||
{
|
||||
_spaces[i].InstantiateSpace(roomObject.transform, i.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return roomObject.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Engine/Runtime/Room.cs.meta
Normal file
11
Assets/Engine/Runtime/Room.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca3aaaca7b04a5049b5d327832fe6f0a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
157
Assets/Engine/Runtime/Space.cs
Normal file
157
Assets/Engine/Runtime/Space.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using EscapeRoomEngine.Engine.Runtime.Modules;
|
||||
using EscapeRoomEngine.Engine.Runtime.Requirements;
|
||||
using UnityEngine;
|
||||
using Logger = EscapeRoomEngine.Engine.Runtime.Utilities.Logger;
|
||||
using LogType = EscapeRoomEngine.Engine.Runtime.Utilities.LogType;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime
|
||||
{
|
||||
public class Space
|
||||
{
|
||||
/// <summary>
|
||||
/// The room relative (<i>RR</i>) dimensions of this space.
|
||||
/// </summary>
|
||||
internal readonly Dimensions rrDimensions;
|
||||
internal List<Module> Modules { get; } = new(2);
|
||||
internal readonly Room room;
|
||||
|
||||
private GameObject _spaceObject, _spaceTiles;
|
||||
|
||||
internal Space(Room room, Passage entrance)
|
||||
{
|
||||
this.room = room;
|
||||
rrDimensions = GenerateSpaceDimensions(
|
||||
entrance,
|
||||
Engine.DefaultEngine.theme.minRoomSize,
|
||||
Engine.DefaultEngine.theme.playSpace);
|
||||
|
||||
// connect the space to its passage
|
||||
entrance.ConnectTo(new DoorModule(this,
|
||||
((DoorModuleDescription)entrance.fromOut.description).connectedDoorDescription));
|
||||
AddModule(entrance.toIn);
|
||||
}
|
||||
|
||||
internal void AddModule(Module module)
|
||||
{
|
||||
Modules.Add(module);
|
||||
}
|
||||
|
||||
internal bool AddModuleWithRequirements(Module module)
|
||||
{
|
||||
var requirementsFulfilled =
|
||||
PlacementRequirement.TryPlacing(module, this) &&
|
||||
OrientationRequirement.TryOrienting(module, this);
|
||||
|
||||
if (requirementsFulfilled)
|
||||
{
|
||||
AddModule(module);
|
||||
}
|
||||
|
||||
return requirementsFulfilled;
|
||||
}
|
||||
|
||||
internal void InstantiateSpace(Transform parent, string name)
|
||||
{
|
||||
_spaceObject = new GameObject($"Space {name}");
|
||||
_spaceObject.transform.SetParent(parent, false);
|
||||
_spaceObject.transform.localPosition = new Vector3(rrDimensions.x, 0, rrDimensions.z);
|
||||
|
||||
// build the space floor out of tiles
|
||||
_spaceTiles = new GameObject($"Space Geometry");
|
||||
_spaceTiles.transform.SetParent(_spaceObject.transform, false);
|
||||
_spaceTiles.isStatic = true;
|
||||
for (var z = 0; z < rrDimensions.length; z++)
|
||||
{
|
||||
for (var x = 0; x < rrDimensions.width; x++)
|
||||
{
|
||||
var left = x == 0;
|
||||
var right = x == rrDimensions.width - 1;
|
||||
var bottom = z == 0;
|
||||
var top = z == rrDimensions.length - 1;
|
||||
|
||||
TileLocation location;
|
||||
if (bottom)
|
||||
location = left ? TileLocation.SW : right ? TileLocation.SE : TileLocation.S;
|
||||
else if (top)
|
||||
location = left ? TileLocation.NW : right ? TileLocation.NE : TileLocation.N;
|
||||
else
|
||||
location = left ? TileLocation.W : right ? TileLocation.E : TileLocation.C;
|
||||
|
||||
var tileObject = Object.Instantiate(Engine.DefaultEngine.theme.spaceTile, _spaceTiles.transform, false);
|
||||
tileObject.transform.localPosition = new Vector3(x, 0, z);
|
||||
tileObject.showTile = location;
|
||||
}
|
||||
}
|
||||
|
||||
// instantiate all modules inside this space
|
||||
Modules.ForEach(module => module.InstantiateModule(_spaceObject.transform));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a position relative to this space to one relative to the room.
|
||||
/// </summary>
|
||||
/// <param name="srPosition">The space relative (<i>SR</i>) position that should be converted to a room relative (<i>RR</i>) position.</param>
|
||||
internal Vector2Int ToRoomRelative(Vector2Int srPosition) => srPosition + rrDimensions.Position;
|
||||
/// <summary>
|
||||
/// Convert a position relative to the room to one relative to this space.
|
||||
/// </summary>
|
||||
/// <param name="rrPosition">The room relative (<i>RR</i>) position that should be converted to a space relative (<i>SR</i>) position.</param>
|
||||
internal Vector2Int ToSpaceRelative(Vector2Int rrPosition) => rrPosition - rrDimensions.Position;
|
||||
|
||||
/// <summary>
|
||||
/// Generate space dimensions that fit the required size constraints and cover the position of the entrance.
|
||||
/// </summary>
|
||||
/// <returns>The generated room relative (<i>RR</i>) dimensions.</returns>
|
||||
private static Dimensions GenerateSpaceDimensions(Passage entrance, Vector2Int minSize, Vector2Int availableSpace)
|
||||
{
|
||||
var xMin = -1;
|
||||
var xMax = -1;
|
||||
var zMin = -1;
|
||||
var zMax = -1;
|
||||
var position = entrance.rrPosition;
|
||||
var door = entrance.fromOut;
|
||||
|
||||
// fix the side the door is facing away from
|
||||
switch (door.orientation)
|
||||
{
|
||||
case Orientation.North:
|
||||
zMin = position.y;
|
||||
zMax = Utilities.Utilities.RandomInclusive(zMin + minSize.y, availableSpace.y);
|
||||
break;
|
||||
case Orientation.East:
|
||||
xMin = position.x;
|
||||
xMax = Utilities.Utilities.RandomInclusive(xMin + minSize.x, availableSpace.x);
|
||||
break;
|
||||
case Orientation.South:
|
||||
zMax = position.y + 1;
|
||||
zMin = Utilities.Utilities.RandomInclusive(0, zMax - minSize.y);
|
||||
break;
|
||||
case Orientation.West:
|
||||
xMax = position.x + 1;
|
||||
xMin = Utilities.Utilities.RandomInclusive(0, xMax - minSize.x);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
// calculate remaining values if they haven't been covered by the switch statement yet
|
||||
if(xMin == -1)
|
||||
xMin = Utilities.Utilities.RandomInclusive(0, Math.Min(position.x, availableSpace.x - minSize.x));
|
||||
if(xMax == -1)
|
||||
xMax = Utilities.Utilities.RandomInclusive(Math.Max(position.x + 1, xMin + minSize.x), availableSpace.x);
|
||||
if(zMin == -1)
|
||||
zMin = Utilities.Utilities.RandomInclusive(0, Math.Min(position.y, availableSpace.y - minSize.y));
|
||||
if(zMax == -1)
|
||||
zMax = Utilities.Utilities.RandomInclusive(Math.Max(position.y + 1, zMin + minSize.y), availableSpace.y);
|
||||
|
||||
var dimensions = new Dimensions(xMax - xMin, zMax - zMin, xMin, zMin);
|
||||
|
||||
Logger.Log($"Generated space dimensions {dimensions} from entrance position {position}", LogType.RoomGeneration);
|
||||
|
||||
return dimensions;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Engine/Runtime/Space.cs.meta
Normal file
11
Assets/Engine/Runtime/Space.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: acd43d28cfff2b640a01dd6f51f7393f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
63
Assets/Engine/Runtime/SpaceTile.cs
Normal file
63
Assets/Engine/Runtime/SpaceTile.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using EscapeRoomEngine.Engine.Runtime.Utilities;
|
||||
using NaughtyAttributes;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime
|
||||
{
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public enum TileLocation
|
||||
{
|
||||
NW, N, NE,
|
||||
W, C, E,
|
||||
SW, S, SE
|
||||
}
|
||||
|
||||
public class SpaceTile : MonoBehaviour
|
||||
{
|
||||
public static HashSet<TileLocation> EveryTileLocation => new(new[]
|
||||
{
|
||||
TileLocation.NW, TileLocation.N, TileLocation.NE,
|
||||
TileLocation.W, TileLocation.C, TileLocation.E,
|
||||
TileLocation.SW, TileLocation.S, TileLocation.SE
|
||||
});
|
||||
|
||||
[BoxGroup("Style Prefabs")] [SerializeField]
|
||||
private UDictionary<TileLocation, GameObject> tilePrefabs;
|
||||
[BoxGroup("Style Prefabs")] [SerializeField]
|
||||
private Material material;
|
||||
|
||||
public TileLocation showTile;
|
||||
|
||||
private GameObject _tile;
|
||||
private TileLocation _showTile;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
SetTile();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_showTile != showTile)
|
||||
{
|
||||
SetTile();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetTile()
|
||||
{
|
||||
if (_tile)
|
||||
{
|
||||
Destroy(_tile);
|
||||
}
|
||||
|
||||
_tile = Instantiate(tilePrefabs[showTile], transform);
|
||||
_tile.isStatic = true;
|
||||
_tile.GetComponent<MeshRenderer>().material = material;
|
||||
|
||||
_showTile = showTile;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Engine/Runtime/SpaceTile.cs.meta
Normal file
11
Assets/Engine/Runtime/SpaceTile.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01a963b0c69d73e488ebc193ea70326b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
Assets/Engine/Runtime/Utilities.meta
Normal file
3
Assets/Engine/Runtime/Utilities.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec385c62be0244e78c6a13de6c7df71f
|
||||
timeCreated: 1667815385
|
||||
42
Assets/Engine/Runtime/Utilities/Logger.cs
Normal file
42
Assets/Engine/Runtime/Utilities/Logger.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Utilities
|
||||
{
|
||||
public enum LogType
|
||||
{
|
||||
Important,
|
||||
ModulePlacement,
|
||||
PassageConnection,
|
||||
RoomGeneration,
|
||||
RequirementResolution,
|
||||
PuzzleFlow
|
||||
}
|
||||
|
||||
public class Logger : MonoBehaviour
|
||||
{
|
||||
public static Logger DefaultLogger
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_foundLogger == null)
|
||||
{
|
||||
_foundLogger = FindObjectOfType<Logger>();
|
||||
}
|
||||
return _foundLogger;
|
||||
}
|
||||
}
|
||||
private static Logger _foundLogger;
|
||||
|
||||
public bool loggingEnabled;
|
||||
public List<LogType> typeFilter;
|
||||
|
||||
public static void Log(string message, LogType type = LogType.Important)
|
||||
{
|
||||
if (DefaultLogger.loggingEnabled && DefaultLogger.typeFilter.Contains(type))
|
||||
{
|
||||
Debug.Log($"<b>[{type}]</b> {message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Engine/Runtime/Utilities/Logger.cs.meta
Normal file
3
Assets/Engine/Runtime/Utilities/Logger.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: debf8d65bda642bc938a94c7639e01a4
|
||||
timeCreated: 1667815421
|
||||
66
Assets/Engine/Runtime/Utilities/Option.cs
Normal file
66
Assets/Engine/Runtime/Utilities/Option.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Optional type with a subset of the functionality of the one described in the Rust documentation at https://doc.rust-lang.org/std/option/enum.Option.html.
|
||||
/// </summary>
|
||||
public interface IOption<T>
|
||||
{
|
||||
public bool IsSome();
|
||||
public bool IsNone();
|
||||
public T Expect(string message);
|
||||
public T Unwrap();
|
||||
public T UnwrapOr(T def);
|
||||
public T UnwrapOrElse(Func<T> f);
|
||||
public IOption<T> And(IOption<T> other);
|
||||
public IOption<T> Or(IOption<T> other);
|
||||
public bool Contains(T value);
|
||||
public T Match(Func<T, T> some, Func<T> none);
|
||||
public void Match(Action<T> some, Action none);
|
||||
public void Match(Action<T> some);
|
||||
public void Match(Action none);
|
||||
}
|
||||
|
||||
public class Some<T> : IOption<T>
|
||||
{
|
||||
private readonly T _value;
|
||||
|
||||
private Some(T value) => _value = value;
|
||||
|
||||
public bool IsSome() => true;
|
||||
public bool IsNone() => false;
|
||||
public T Expect(string message) => _value;
|
||||
public T Unwrap() => _value;
|
||||
public T UnwrapOr(T def) => _value;
|
||||
public T UnwrapOrElse(Func<T> f) => _value;
|
||||
public IOption<T> And(IOption<T> other) => other;
|
||||
public IOption<T> Or(IOption<T> other) => this;
|
||||
public bool Contains(T value) => _value.Equals(value);
|
||||
public T Match(Func<T, T> some, Func<T> none) => some(_value);
|
||||
public void Match(Action<T> some, Action none) => some(_value);
|
||||
public void Match(Action<T> some) => some(_value);
|
||||
public void Match(Action none) {}
|
||||
|
||||
public static IOption<T> Of(T value) => new Some<T>(value);
|
||||
}
|
||||
|
||||
public class None<T> : IOption<T>
|
||||
{
|
||||
public bool IsSome() => false;
|
||||
public bool IsNone() => true;
|
||||
public T Expect(string message) => throw new Exception(message);
|
||||
public T Unwrap() => throw new Exception("Tried to unwrap None.");
|
||||
public T UnwrapOr(T def) => def;
|
||||
public T UnwrapOrElse(Func<T> f) => f();
|
||||
public IOption<T> And(IOption<T> other) => this;
|
||||
public IOption<T> Or(IOption<T> other) => other;
|
||||
public bool Contains(T value) => false;
|
||||
public T Match(Func<T, T> some, Func<T> none) => none();
|
||||
public void Match(Action<T> some, Action none) => none();
|
||||
public void Match(Action<T> some) {}
|
||||
public void Match(Action none) => none();
|
||||
|
||||
public static IOption<T> New() => new None<T>();
|
||||
}
|
||||
}
|
||||
3
Assets/Engine/Runtime/Utilities/Option.cs.meta
Normal file
3
Assets/Engine/Runtime/Utilities/Option.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2782bb7baf73403a9cd9fc8cbc8d09a3
|
||||
timeCreated: 1668169918
|
||||
449
Assets/Engine/Runtime/Utilities/UDictionary.cs
Normal file
449
Assets/Engine/Runtime/Utilities/UDictionary.cs
Normal file
@@ -0,0 +1,449 @@
|
||||
// https://gist.github.com/Moe-Baker/e36610361012d586b1393994febeb5d2
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Utilities
|
||||
{
|
||||
[Serializable]
|
||||
public class UDictionary
|
||||
{
|
||||
public class SplitAttribute : PropertyAttribute
|
||||
{
|
||||
public float Key { get; protected set; }
|
||||
public float Value { get; protected set; }
|
||||
|
||||
public SplitAttribute(float key, float value)
|
||||
{
|
||||
this.Key = key;
|
||||
this.Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomPropertyDrawer(typeof(SplitAttribute), true)]
|
||||
[CustomPropertyDrawer(typeof(UDictionary), true)]
|
||||
public class Drawer : PropertyDrawer
|
||||
{
|
||||
SerializedProperty property;
|
||||
|
||||
public bool IsExpanded
|
||||
{
|
||||
get => property.isExpanded;
|
||||
set => property.isExpanded = value;
|
||||
}
|
||||
|
||||
SerializedProperty keys;
|
||||
SerializedProperty values;
|
||||
|
||||
public bool IsAligned => keys.arraySize == values.arraySize;
|
||||
|
||||
ReorderableList list;
|
||||
|
||||
GUIContent label;
|
||||
|
||||
SplitAttribute split;
|
||||
|
||||
public float KeySplit => split == null ? 30f : split.Key;
|
||||
public float ValueSplit => split == null ? 70f : split.Value;
|
||||
|
||||
public static float SingleLineHeight => EditorGUIUtility.singleLineHeight;
|
||||
|
||||
public const float ElementHeightPadding = 6f;
|
||||
public const float ElementSpacing = 10f;
|
||||
public const float ElementFoldoutPadding = 20f;
|
||||
|
||||
public const float TopPadding = 5f;
|
||||
public const float BottomPadding = 5f;
|
||||
|
||||
void Init(SerializedProperty value)
|
||||
{
|
||||
if (SerializedProperty.EqualContents(value, property)) return;
|
||||
|
||||
property = value;
|
||||
|
||||
keys = property.FindPropertyRelative(nameof(keys));
|
||||
values = property.FindPropertyRelative(nameof(values));
|
||||
|
||||
split = attribute as SplitAttribute;
|
||||
|
||||
list = new ReorderableList(property.serializedObject, keys, true, true, true, true);
|
||||
|
||||
list.drawHeaderCallback = DrawHeader;
|
||||
|
||||
list.onAddCallback = Add;
|
||||
list.onRemoveCallback = Remove;
|
||||
|
||||
list.elementHeightCallback = GetElementHeight;
|
||||
|
||||
list.drawElementCallback = DrawElement;
|
||||
|
||||
list.onReorderCallbackWithDetails += Reorder;
|
||||
}
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
|
||||
{
|
||||
Init(property);
|
||||
|
||||
var height = TopPadding + BottomPadding;
|
||||
|
||||
if (IsAligned)
|
||||
height += IsExpanded ? list.GetHeight() : list.headerHeight;
|
||||
else
|
||||
height += SingleLineHeight;
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
label.text = $" {label.text}";
|
||||
|
||||
this.label = label;
|
||||
|
||||
Init(property);
|
||||
|
||||
rect = EditorGUI.IndentedRect(rect);
|
||||
|
||||
rect.y += TopPadding;
|
||||
rect.height -= TopPadding + BottomPadding;
|
||||
|
||||
if (IsAligned == false)
|
||||
{
|
||||
DrawAlignmentWarning(ref rect);
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsExpanded)
|
||||
DrawList(ref rect);
|
||||
else
|
||||
DrawCompleteHeader(ref rect);
|
||||
}
|
||||
|
||||
void DrawList(ref Rect rect)
|
||||
{
|
||||
EditorGUIUtility.labelWidth = 80f;
|
||||
EditorGUIUtility.fieldWidth = 80f;
|
||||
|
||||
list.DoList(rect);
|
||||
}
|
||||
|
||||
void DrawAlignmentWarning(ref Rect rect)
|
||||
{
|
||||
var width = 80f;
|
||||
var spacing = 5f;
|
||||
|
||||
rect.width -= width;
|
||||
|
||||
EditorGUI.HelpBox(rect, " Misalignment Detected", MessageType.Error);
|
||||
|
||||
rect.x += rect.width + spacing;
|
||||
rect.width = width - spacing;
|
||||
|
||||
if (GUI.Button(rect, "Fix"))
|
||||
{
|
||||
if (keys.arraySize > values.arraySize)
|
||||
{
|
||||
var difference = keys.arraySize - values.arraySize;
|
||||
|
||||
for (int i = 0; i < difference; i++)
|
||||
keys.DeleteArrayElementAtIndex(keys.arraySize - 1);
|
||||
}
|
||||
else if (keys.arraySize < values.arraySize)
|
||||
{
|
||||
var difference = values.arraySize - keys.arraySize;
|
||||
|
||||
for (int i = 0; i < difference; i++)
|
||||
values.DeleteArrayElementAtIndex(values.arraySize - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Draw Header
|
||||
|
||||
void DrawHeader(Rect rect)
|
||||
{
|
||||
rect.x += 10f;
|
||||
|
||||
IsExpanded = EditorGUI.Foldout(rect, IsExpanded, label, true);
|
||||
}
|
||||
|
||||
void DrawCompleteHeader(ref Rect rect)
|
||||
{
|
||||
ReorderableList.defaultBehaviours.DrawHeaderBackground(rect);
|
||||
|
||||
rect.x += 6;
|
||||
rect.y += 0;
|
||||
|
||||
DrawHeader(rect);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
float GetElementHeight(int index)
|
||||
{
|
||||
SerializedProperty key = keys.GetArrayElementAtIndex(index);
|
||||
SerializedProperty value = values.GetArrayElementAtIndex(index);
|
||||
|
||||
var kHeight = GetChildernSingleHeight(key);
|
||||
var vHeight = GetChildernSingleHeight(value);
|
||||
|
||||
var max = Math.Max(kHeight, vHeight);
|
||||
|
||||
if (max < SingleLineHeight) max = SingleLineHeight;
|
||||
|
||||
return max + ElementHeightPadding;
|
||||
}
|
||||
|
||||
#region Draw Element
|
||||
|
||||
void DrawElement(Rect rect, int index, bool isActive, bool isFocused)
|
||||
{
|
||||
rect.height -= ElementHeightPadding;
|
||||
rect.y += ElementHeightPadding / 2;
|
||||
|
||||
var areas = Split(rect, KeySplit, ValueSplit);
|
||||
|
||||
DrawKey(areas[0], index);
|
||||
DrawValue(areas[1], index);
|
||||
}
|
||||
|
||||
void DrawKey(Rect rect, int index)
|
||||
{
|
||||
var property = keys.GetArrayElementAtIndex(index);
|
||||
|
||||
rect.x += ElementSpacing / 2f;
|
||||
rect.width -= ElementSpacing;
|
||||
|
||||
DrawField(rect, property);
|
||||
}
|
||||
|
||||
void DrawValue(Rect rect, int index)
|
||||
{
|
||||
var property = values.GetArrayElementAtIndex(index);
|
||||
|
||||
rect.x += ElementSpacing / 2f;
|
||||
rect.width -= ElementSpacing;
|
||||
|
||||
DrawField(rect, property);
|
||||
}
|
||||
|
||||
void DrawField(Rect rect, SerializedProperty property)
|
||||
{
|
||||
rect.height = SingleLineHeight;
|
||||
|
||||
if (IsInline(property))
|
||||
{
|
||||
EditorGUI.PropertyField(rect, property, GUIContent.none);
|
||||
}
|
||||
else
|
||||
{
|
||||
rect.x += ElementSpacing / 2f;
|
||||
rect.width -= ElementSpacing;
|
||||
|
||||
foreach (var child in IterateChildern(property))
|
||||
{
|
||||
EditorGUI.PropertyField(rect, child, false);
|
||||
|
||||
rect.y += SingleLineHeight + +2f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
void Reorder(ReorderableList list, int oldIndex, int newIndex)
|
||||
{
|
||||
values.MoveArrayElement(oldIndex, newIndex);
|
||||
}
|
||||
|
||||
void Add(ReorderableList list)
|
||||
{
|
||||
values.InsertArrayElementAtIndex(values.arraySize);
|
||||
|
||||
ReorderableList.defaultBehaviours.DoAddButton(list);
|
||||
}
|
||||
|
||||
void Remove(ReorderableList list)
|
||||
{
|
||||
values.DeleteArrayElementAtIndex(list.index);
|
||||
|
||||
ReorderableList.defaultBehaviours.DoRemoveButton(list);
|
||||
}
|
||||
|
||||
//Static Utility
|
||||
static Rect[] Split(Rect source, params float[] cuts)
|
||||
{
|
||||
var rects = new Rect[cuts.Length];
|
||||
|
||||
var x = 0f;
|
||||
|
||||
for (int i = 0; i < cuts.Length; i++)
|
||||
{
|
||||
rects[i] = new Rect(source);
|
||||
|
||||
rects[i].x += x;
|
||||
rects[i].width *= cuts[i] / 100;
|
||||
|
||||
x += rects[i].width;
|
||||
}
|
||||
|
||||
return rects;
|
||||
}
|
||||
|
||||
static bool IsInline(SerializedProperty property)
|
||||
{
|
||||
switch (property.propertyType)
|
||||
{
|
||||
case SerializedPropertyType.Generic:
|
||||
return property.hasVisibleChildren == false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static IEnumerable<SerializedProperty> IterateChildern(SerializedProperty property)
|
||||
{
|
||||
var path = property.propertyPath;
|
||||
|
||||
property.Next(true);
|
||||
|
||||
while (true)
|
||||
{
|
||||
yield return property;
|
||||
|
||||
if (property.NextVisible(false) == false) break;
|
||||
if (property.propertyPath.StartsWith(path) == false) break;
|
||||
}
|
||||
}
|
||||
|
||||
float GetChildernSingleHeight(SerializedProperty property)
|
||||
{
|
||||
if (IsInline(property)) return SingleLineHeight;
|
||||
|
||||
var height = 0f;
|
||||
|
||||
foreach (var child in IterateChildern(property))
|
||||
height += SingleLineHeight + 2f;
|
||||
|
||||
return height;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class UDictionary<TKey, TValue> : UDictionary, IDictionary<TKey, TValue>
|
||||
{
|
||||
[SerializeField] List<TKey> keys;
|
||||
public List<TKey> Keys => keys;
|
||||
ICollection<TKey> IDictionary<TKey, TValue>.Keys => keys;
|
||||
|
||||
[SerializeField] List<TValue> values;
|
||||
public List<TValue> Values => values;
|
||||
ICollection<TValue> IDictionary<TKey, TValue>.Values => values;
|
||||
|
||||
public int Count => keys.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
Dictionary<TKey, TValue> cache;
|
||||
|
||||
public bool Cached => cache != null;
|
||||
|
||||
public Dictionary<TKey, TValue> Dictionary
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cache == null)
|
||||
{
|
||||
cache = new Dictionary<TKey, TValue>();
|
||||
|
||||
for (int i = 0; i < keys.Count; i++)
|
||||
{
|
||||
if (keys[i] == null) continue;
|
||||
if (cache.ContainsKey(keys[i])) continue;
|
||||
|
||||
cache.Add(keys[i], values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get => Dictionary[key];
|
||||
set
|
||||
{
|
||||
var index = keys.IndexOf(key);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
Add(key, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
values[index] = value;
|
||||
if (Cached) Dictionary[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, out TValue value) => Dictionary.TryGetValue(key, out value);
|
||||
|
||||
public bool ContainsKey(TKey key) => Dictionary.ContainsKey(key);
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item) => ContainsKey(item.Key);
|
||||
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
keys.Add(key);
|
||||
values.Add(value);
|
||||
|
||||
if (Cached) Dictionary.Add(key, value);
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
|
||||
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
var index = keys.IndexOf(key);
|
||||
|
||||
if (index < 0) return false;
|
||||
|
||||
keys.RemoveAt(index);
|
||||
values.RemoveAt(index);
|
||||
|
||||
if (Cached) Dictionary.Remove(key);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item) => Remove(item.Key);
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
keys.Clear();
|
||||
values.Clear();
|
||||
|
||||
if (Cached) Dictionary.Clear();
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) =>
|
||||
(Dictionary as IDictionary).CopyTo(array, arrayIndex);
|
||||
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => Dictionary.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => Dictionary.GetEnumerator();
|
||||
|
||||
public UDictionary()
|
||||
{
|
||||
values = new List<TValue>();
|
||||
keys = new List<TKey>();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Engine/Runtime/Utilities/UDictionary.cs.meta
Normal file
11
Assets/Engine/Runtime/Utilities/UDictionary.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 238265e493429c44ebcd511153a19322
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
32
Assets/Engine/Runtime/Utilities/Utilities.cs
Normal file
32
Assets/Engine/Runtime/Utilities/Utilities.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EscapeRoomEngine.Engine.Runtime.Utilities
|
||||
{
|
||||
public static class Utilities
|
||||
{
|
||||
#region Math
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether a position relative to some dimensions is inside those dimensions.
|
||||
/// </summary>
|
||||
/// <param name="position">The position to check, relative to the dimensions.</param>
|
||||
/// <param name="dimensions">The dimensions to check the position against.</param>
|
||||
public static bool IsInsideRelative(this Vector2Int position, Dimensions dimensions)
|
||||
{
|
||||
return position.x >= 0 && position.y >= 0 && position.x < dimensions.width && position.y < dimensions.length;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Randomness
|
||||
|
||||
public static int RandomInclusive(int from, int to) => Random.Range(from, to + 1);
|
||||
|
||||
public static T RandomElement<T>(this List<T> list) => list[Random.Range(0, list.Count)];
|
||||
public static T RandomElement<T>(this HashSet<T> set) => set.ElementAt(Random.Range(0, set.Count));
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
3
Assets/Engine/Runtime/Utilities/Utilities.cs.meta
Normal file
3
Assets/Engine/Runtime/Utilities/Utilities.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 092cc048ba0e4bb7a7862083fb702884
|
||||
timeCreated: 1667812284
|
||||
Reference in New Issue
Block a user