feat: add throwable boxs and set it as prefab
This commit is contained in:
8
Assets/Code/Scripts/DevRoom.meta
Normal file
8
Assets/Code/Scripts/DevRoom.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f4bfa7e49bca4e0439e2c2c04bd801fe
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
90
Assets/Code/Scripts/DevRoom/DevRoomHUD.cs
Normal file
90
Assets/Code/Scripts/DevRoom/DevRoomHUD.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Displays a controls legend in the top-left corner of the screen.
|
||||
/// Attach to any active GameObject in the DevRoom scene.
|
||||
/// </summary>
|
||||
public class DevRoomHUD : MonoBehaviour
|
||||
{
|
||||
[Header("Layout")]
|
||||
[SerializeField] private float paddingX = 16f;
|
||||
[SerializeField] private float paddingY = 16f;
|
||||
[SerializeField] private float lineHeight = 22f;
|
||||
[SerializeField] private int fontSize = 14;
|
||||
|
||||
[Header("Style")]
|
||||
[SerializeField] private Color backgroundColor = new Color(0f, 0f, 0f, 0.55f);
|
||||
[SerializeField] private Color keyColor = new Color(1f, 0.85f, 0.2f);
|
||||
[SerializeField] private Color labelColor = Color.white;
|
||||
|
||||
private static readonly (string key, string action)[] k_Controls =
|
||||
{
|
||||
("WASD / Arrows", "Move"),
|
||||
("Mouse", "Look"),
|
||||
("Space", "Jump"),
|
||||
("Left Shift", "Sprint"),
|
||||
("C", "Crouch"),
|
||||
("LMB", "Throw head"),
|
||||
("E (hold)", "Retrieve head"),
|
||||
("1 / 2", "Previous / Next"),
|
||||
};
|
||||
|
||||
private GUIStyle m_keyStyle;
|
||||
private GUIStyle m_labelStyle;
|
||||
private GUIStyle m_boxStyle;
|
||||
private Texture2D m_bgTexture;
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
EnsureStyles();
|
||||
|
||||
float keyColWidth = 110f;
|
||||
float labelColWidth = 130f;
|
||||
float totalWidth = paddingX * 2 + keyColWidth + 8f + labelColWidth;
|
||||
float totalHeight = paddingY * 2 + k_Controls.Length * lineHeight;
|
||||
|
||||
Rect bgRect = new Rect(8f, 8f, totalWidth, totalHeight);
|
||||
GUI.DrawTexture(bgRect, m_bgTexture);
|
||||
|
||||
float x = bgRect.x + paddingX;
|
||||
float y = bgRect.y + paddingY;
|
||||
|
||||
for (int i = 0; i < k_Controls.Length; i++)
|
||||
{
|
||||
float rowY = y + i * lineHeight;
|
||||
|
||||
GUI.Label(new Rect(x, rowY, keyColWidth, lineHeight), k_Controls[i].key, m_keyStyle);
|
||||
GUI.Label(new Rect(x + keyColWidth + 8f, rowY, labelColWidth, lineHeight), k_Controls[i].action, m_labelStyle);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureStyles()
|
||||
{
|
||||
if (m_keyStyle != null)
|
||||
return;
|
||||
|
||||
m_bgTexture = new Texture2D(1, 1);
|
||||
m_bgTexture.SetPixel(0, 0, backgroundColor);
|
||||
m_bgTexture.Apply();
|
||||
|
||||
m_keyStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
fontSize = fontSize,
|
||||
fontStyle = FontStyle.Bold,
|
||||
};
|
||||
m_keyStyle.normal.textColor = keyColor;
|
||||
|
||||
m_labelStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
fontSize = fontSize,
|
||||
fontStyle = FontStyle.Normal,
|
||||
};
|
||||
m_labelStyle.normal.textColor = labelColor;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (m_bgTexture != null)
|
||||
Destroy(m_bgTexture);
|
||||
}
|
||||
}
|
||||
2
Assets/Code/Scripts/DevRoom/DevRoomHUD.cs.meta
Normal file
2
Assets/Code/Scripts/DevRoom/DevRoomHUD.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c34284eb72c0cc841b21e6425e27606a
|
||||
8
Assets/Code/Scripts/Interaction.meta
Normal file
8
Assets/Code/Scripts/Interaction.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 303f7f8e61ca68143b9597e9d31f0e02
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
189
Assets/Code/Scripts/Interaction/ButtonSequenceDoorPuzzle.cs
Normal file
189
Assets/Code/Scripts/Interaction/ButtonSequenceDoorPuzzle.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
public class ButtonSequenceDoorPuzzle : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[Tooltip("All available buttons for this puzzle.")]
|
||||
[SerializeField] private WallInteractButton[] buttons;
|
||||
|
||||
[Tooltip("Door to open when the sequence is correct.")]
|
||||
[SerializeField] private SlidingDoor targetDoor;
|
||||
|
||||
[Tooltip("Optional blocks controlled by this puzzle (reset with SetOff on start/wrong input).")]
|
||||
[SerializeField] private TestBlock[] puzzleBlocks;
|
||||
|
||||
[Header("Sequence")]
|
||||
[Tooltip("Button indices (from the buttons array) that must be pressed in order. Example: 2,0,3")]
|
||||
[SerializeField] private int[] requiredSequence = { 0, 1, 2 };
|
||||
|
||||
[Tooltip("If true, wrong input resets progress back to 0.")]
|
||||
[SerializeField] private bool resetOnWrongPress = true;
|
||||
|
||||
[Tooltip("If true, puzzle can only be solved once.")]
|
||||
[SerializeField] private bool lockAfterSolved = true;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] private bool enableDebugLogs = true;
|
||||
|
||||
private int m_progress;
|
||||
private bool m_isSolved;
|
||||
private UnityAction[] m_cachedListeners;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
SetAllBlocksOff();
|
||||
RegisterAllButtons();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
UnregisterAllButtons();
|
||||
}
|
||||
|
||||
private void RegisterAllButtons()
|
||||
{
|
||||
if (buttons == null)
|
||||
return;
|
||||
|
||||
m_cachedListeners = new UnityAction[buttons.Length];
|
||||
|
||||
for (int i = 0; i < buttons.Length; i++)
|
||||
{
|
||||
WallInteractButton button = buttons[i];
|
||||
if (button == null)
|
||||
continue;
|
||||
|
||||
int buttonIndex = i;
|
||||
UnityAction action = () => OnButtonPressed(buttonIndex);
|
||||
m_cachedListeners[i] = action;
|
||||
button.OnInteract.AddListener(action);
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterAllButtons()
|
||||
{
|
||||
if (buttons == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < buttons.Length; i++)
|
||||
{
|
||||
WallInteractButton button = buttons[i];
|
||||
if (button == null)
|
||||
continue;
|
||||
|
||||
if (m_cachedListeners != null && i < m_cachedListeners.Length && m_cachedListeners[i] != null)
|
||||
button.OnInteract.RemoveListener(m_cachedListeners[i]);
|
||||
}
|
||||
|
||||
m_cachedListeners = null;
|
||||
m_progress = 0;
|
||||
}
|
||||
|
||||
private void OnButtonPressed(int buttonIndex)
|
||||
{
|
||||
Log($"Button pressed: index {buttonIndex}");
|
||||
|
||||
if (m_isSolved && lockAfterSolved)
|
||||
{
|
||||
Log("Puzzle already solved and locked.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsSequenceValid())
|
||||
{
|
||||
Log("Invalid sequence configuration.");
|
||||
return;
|
||||
}
|
||||
|
||||
int expectedIndex = requiredSequence[m_progress];
|
||||
Log($"Expected button index: {expectedIndex} (step {m_progress + 1}/{requiredSequence.Length})");
|
||||
|
||||
if (buttonIndex == expectedIndex)
|
||||
{
|
||||
m_progress++;
|
||||
Log($"Correct input. Progress: {m_progress}/{requiredSequence.Length}");
|
||||
|
||||
if (m_progress >= requiredSequence.Length)
|
||||
{
|
||||
SolvePuzzle();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (resetOnWrongPress)
|
||||
{
|
||||
Log("Wrong input. Resetting sequence and turning puzzle blocks OFF.");
|
||||
m_progress = 0;
|
||||
SetAllBlocksOff();
|
||||
return;
|
||||
}
|
||||
|
||||
Log("Wrong input, but resetOnWrongPress is disabled.");
|
||||
}
|
||||
|
||||
private bool IsSequenceValid()
|
||||
{
|
||||
if (requiredSequence == null || requiredSequence.Length == 0)
|
||||
return false;
|
||||
|
||||
if (buttons == null || buttons.Length == 0)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < requiredSequence.Length; i++)
|
||||
{
|
||||
int index = requiredSequence[i];
|
||||
if (index < 0 || index >= buttons.Length)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SolvePuzzle()
|
||||
{
|
||||
m_isSolved = true;
|
||||
m_progress = 0;
|
||||
Log("Sequence completed. Opening door.");
|
||||
|
||||
if (targetDoor != null)
|
||||
targetDoor.Open();
|
||||
}
|
||||
|
||||
private void SetAllBlocksOff()
|
||||
{
|
||||
if (puzzleBlocks == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < puzzleBlocks.Length; i++)
|
||||
{
|
||||
if (puzzleBlocks[i] == null)
|
||||
continue;
|
||||
|
||||
puzzleBlocks[i].SetOff();
|
||||
}
|
||||
}
|
||||
|
||||
private void Log(string message)
|
||||
{
|
||||
if (!enableDebugLogs)
|
||||
return;
|
||||
|
||||
Debug.Log($"[{nameof(ButtonSequenceDoorPuzzle)}] {message}", this);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
if (requiredSequence == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < requiredSequence.Length; i++)
|
||||
{
|
||||
requiredSequence[i] = Math.Max(0, requiredSequence[i]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 920802292ba9a49d2bee3519a905717d
|
||||
72
Assets/Code/Scripts/Interaction/PlayerBoxController.cs
Normal file
72
Assets/Code/Scripts/Interaction/PlayerBoxController.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class BoxPickup : MonoBehaviour
|
||||
{
|
||||
public Transform PlayerTransform;
|
||||
public Transform CameraTransform;
|
||||
public Transform HandTransform;
|
||||
public float ThrowForce = 10f;
|
||||
public float PickupDistance = 5f;
|
||||
|
||||
private bool isHeld;
|
||||
private Rigidbody m_rigidbody;
|
||||
private PlayerInputController input;
|
||||
|
||||
void Start()
|
||||
{
|
||||
m_rigidbody = GetComponent<Rigidbody>();
|
||||
input = PlayerTransform.GetComponent<PlayerInputController>();
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (input.InteractPressed)
|
||||
{
|
||||
if (!isHeld)
|
||||
TryPickup();
|
||||
else
|
||||
Drop();
|
||||
}
|
||||
|
||||
if (input.ThrowPressed)
|
||||
Throw();
|
||||
}
|
||||
|
||||
private void TryPickup()
|
||||
{
|
||||
Collider[] hits = Physics.OverlapSphere(PlayerTransform.position, PickupDistance);
|
||||
|
||||
foreach (Collider hit in hits)
|
||||
{
|
||||
if (hit.transform == transform) {
|
||||
Pickup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Pickup()
|
||||
{
|
||||
isHeld = true;
|
||||
m_rigidbody.isKinematic = true;
|
||||
transform.SetParent(HandTransform);
|
||||
transform.localPosition = Vector3.zero;
|
||||
transform.localRotation = Quaternion.identity;
|
||||
}
|
||||
|
||||
private void Drop()
|
||||
{
|
||||
isHeld = false;
|
||||
transform.SetParent(null);
|
||||
m_rigidbody.isKinematic = false;
|
||||
}
|
||||
|
||||
private void Throw()
|
||||
{
|
||||
if (!isHeld)
|
||||
return;
|
||||
|
||||
Drop();
|
||||
m_rigidbody.AddForce(PlayerTransform.forward * ThrowForce, ForceMode.Impulse);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a36af2e55a3732eb2abc110ae2365702
|
||||
76
Assets/Code/Scripts/Interaction/PressurePlateButton.cs
Normal file
76
Assets/Code/Scripts/Interaction/PressurePlateButton.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public class PressurePlateButton : MonoBehaviour
|
||||
{
|
||||
[Header("Detection")]
|
||||
[Tooltip("Layers that can activate the plate. Keep default to allow everything.")]
|
||||
[SerializeField] private LayerMask detectionMask = ~0;
|
||||
|
||||
[Tooltip("If true, rigidbody objects can activate the plate.")]
|
||||
[SerializeField] private bool allowRigidbodies = true;
|
||||
|
||||
[Tooltip("If true, the Player can activate the plate (tag Player or PlayerMovement component).")]
|
||||
[SerializeField] private bool allowPlayer = true;
|
||||
|
||||
[Header("Events")]
|
||||
public UnityEvent OnPressed;
|
||||
public UnityEvent OnReleased;
|
||||
|
||||
private readonly HashSet<Collider> m_validCollidersOnPlate = new HashSet<Collider>();
|
||||
private bool m_isPressed;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
Collider col = GetComponent<Collider>();
|
||||
col.isTrigger = true;
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (!IsValidActivator(other))
|
||||
return;
|
||||
|
||||
m_validCollidersOnPlate.Add(other);
|
||||
|
||||
if (!m_isPressed)
|
||||
{
|
||||
m_isPressed = true;
|
||||
OnPressed?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerExit(Collider other)
|
||||
{
|
||||
if (!m_validCollidersOnPlate.Remove(other))
|
||||
return;
|
||||
|
||||
if (m_validCollidersOnPlate.Count == 0 && m_isPressed)
|
||||
{
|
||||
m_isPressed = false;
|
||||
OnReleased?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidActivator(Collider other)
|
||||
{
|
||||
if (((1 << other.gameObject.layer) & detectionMask) == 0)
|
||||
return false;
|
||||
|
||||
if (allowPlayer)
|
||||
{
|
||||
if (other.CompareTag("Player"))
|
||||
return true;
|
||||
|
||||
if (other.GetComponentInParent<PlayerMovement>() != null)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (allowRigidbodies && other.attachedRigidbody != null)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69374de63bb12844d97acb7a794b7b40
|
||||
114
Assets/Code/Scripts/Interaction/SlidingDoor.cs
Normal file
114
Assets/Code/Scripts/Interaction/SlidingDoor.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
public class SlidingDoor : MonoBehaviour
|
||||
{
|
||||
public enum SlideAxis { X, Y, Z }
|
||||
public enum SlideDirection { Positive = 1, Negative = -1 }
|
||||
|
||||
[Header("Slide Settings")]
|
||||
[Tooltip("Local axis the door slides along.")]
|
||||
[SerializeField] private SlideAxis axis = SlideAxis.X;
|
||||
|
||||
[Tooltip("Which way along the axis the door opens.")]
|
||||
[SerializeField] private SlideDirection direction = SlideDirection.Positive;
|
||||
|
||||
[Tooltip("How far the door travels when fully open.")]
|
||||
[SerializeField] private float slideDistance = 2f;
|
||||
|
||||
[Tooltip("Movement speed (units per second).")]
|
||||
[SerializeField] private float speed = 3f;
|
||||
|
||||
[Header("Start State")]
|
||||
[SerializeField] private bool startOpen = false;
|
||||
|
||||
[Header("Events")]
|
||||
public UnityEvent OnOpened;
|
||||
public UnityEvent OnClosed;
|
||||
|
||||
private Vector3 m_closedPos;
|
||||
private Vector3 m_openPos;
|
||||
private Vector3 m_targetPos;
|
||||
private bool m_isOpen;
|
||||
private bool m_eventFiredOpen;
|
||||
private bool m_eventFiredClosed;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_closedPos = transform.localPosition;
|
||||
m_openPos = m_closedPos + GetSlideVector() * slideDistance;
|
||||
|
||||
if (startOpen)
|
||||
{
|
||||
transform.localPosition = m_openPos;
|
||||
m_isOpen = true;
|
||||
}
|
||||
|
||||
m_targetPos = m_isOpen ? m_openPos : m_closedPos;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
transform.localPosition = Vector3.MoveTowards(
|
||||
transform.localPosition, m_targetPos, speed * Time.deltaTime);
|
||||
|
||||
if (Vector3.Distance(transform.localPosition, m_openPos) < 0.01f && !m_eventFiredOpen)
|
||||
{
|
||||
m_eventFiredOpen = true;
|
||||
m_eventFiredClosed = false;
|
||||
OnOpened?.Invoke();
|
||||
}
|
||||
else if (Vector3.Distance(transform.localPosition, m_closedPos) < 0.01f && !m_eventFiredClosed)
|
||||
{
|
||||
m_eventFiredClosed = true;
|
||||
m_eventFiredOpen = false;
|
||||
OnClosed?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public void Open()
|
||||
{
|
||||
m_isOpen = true;
|
||||
m_targetPos = m_openPos;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
m_isOpen = false;
|
||||
m_targetPos = m_closedPos;
|
||||
}
|
||||
|
||||
public void Toggle()
|
||||
{
|
||||
if (m_isOpen)
|
||||
Close();
|
||||
else
|
||||
Open();
|
||||
}
|
||||
|
||||
private Vector3 GetSlideVector()
|
||||
{
|
||||
float sign = (float)direction;
|
||||
return axis switch
|
||||
{
|
||||
SlideAxis.X => new Vector3(sign, 0f, 0f),
|
||||
SlideAxis.Y => new Vector3(0f, sign, 0f),
|
||||
SlideAxis.Z => new Vector3(0f, 0f, sign),
|
||||
_ => Vector3.right,
|
||||
};
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
Vector3 worldClosed = transform.parent != null
|
||||
? transform.parent.TransformPoint(transform.localPosition)
|
||||
: transform.position;
|
||||
|
||||
Vector3 slideVec = transform.TransformDirection(GetSlideVector()) * slideDistance;
|
||||
Gizmos.color = Color.cyan;
|
||||
Gizmos.DrawLine(worldClosed, worldClosed + slideVec);
|
||||
Gizmos.DrawWireSphere(worldClosed + slideVec, 0.08f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
2
Assets/Code/Scripts/Interaction/SlidingDoor.cs.meta
Normal file
2
Assets/Code/Scripts/Interaction/SlidingDoor.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0cc6c36a261296f4c82e315da147ba93
|
||||
148
Assets/Code/Scripts/Interaction/TestBlock.cs
Normal file
148
Assets/Code/Scripts/Interaction/TestBlock.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
public class TestBlock : MonoBehaviour
|
||||
{
|
||||
[Header("Visual")]
|
||||
[SerializeField] private Renderer targetRenderer;
|
||||
[SerializeField] private Color offColor = Color.red;
|
||||
[SerializeField] private Color onColor = Color.green;
|
||||
|
||||
[Header("Material")]
|
||||
[SerializeField] private string colorPropertyName = "_BaseColor";
|
||||
|
||||
[Header("Optional Auto Wiring")]
|
||||
[Tooltip("If assigned, block turns ON while plate is pressed and OFF when released.")]
|
||||
[SerializeField] private PressurePlateButton[] pressurePlates;
|
||||
|
||||
[Tooltip("If assigned, block toggles every time the wall button is interacted with.")]
|
||||
[SerializeField] private WallInteractButton[] wallButtons;
|
||||
|
||||
private MaterialPropertyBlock m_propertyBlock;
|
||||
private bool m_isOn;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (targetRenderer == null)
|
||||
{
|
||||
targetRenderer = GetComponentInChildren<Renderer>();
|
||||
}
|
||||
|
||||
m_propertyBlock = new MaterialPropertyBlock();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
SetOff();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
RegisterEvents();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
UnregisterEvents();
|
||||
}
|
||||
|
||||
public void SetOn()
|
||||
{
|
||||
m_isOn = true;
|
||||
Debug.Log($"{gameObject.name} turned ON");
|
||||
ApplyColor(onColor);
|
||||
}
|
||||
|
||||
public void SetOff()
|
||||
{
|
||||
m_isOn = false;
|
||||
Debug.Log($"{gameObject.name} turned OFF");
|
||||
ApplyColor(offColor);
|
||||
}
|
||||
|
||||
public void Toggle()
|
||||
{
|
||||
if (m_isOn)
|
||||
SetOff();
|
||||
else
|
||||
SetOn();
|
||||
}
|
||||
|
||||
// Generic interaction entry point for UnityEvents from any interactable.
|
||||
public void TriggerInteraction()
|
||||
{
|
||||
Toggle();
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
SetOn();
|
||||
}
|
||||
|
||||
public void Deactivate()
|
||||
{
|
||||
SetOff();
|
||||
}
|
||||
|
||||
private void ApplyColor(Color color)
|
||||
{
|
||||
if (targetRenderer == null)
|
||||
return;
|
||||
|
||||
targetRenderer.GetPropertyBlock(m_propertyBlock);
|
||||
m_propertyBlock.SetColor(colorPropertyName, color);
|
||||
targetRenderer.SetPropertyBlock(m_propertyBlock);
|
||||
}
|
||||
|
||||
private void RegisterEvents()
|
||||
{
|
||||
if (pressurePlates != null)
|
||||
{
|
||||
for (int i = 0; i < pressurePlates.Length; i++)
|
||||
{
|
||||
if (pressurePlates[i] == null)
|
||||
continue;
|
||||
|
||||
pressurePlates[i].OnPressed.AddListener(SetOn);
|
||||
pressurePlates[i].OnReleased.AddListener(SetOff);
|
||||
}
|
||||
}
|
||||
|
||||
if (wallButtons != null)
|
||||
{
|
||||
for (int i = 0; i < wallButtons.Length; i++)
|
||||
{
|
||||
if (wallButtons[i] == null)
|
||||
continue;
|
||||
|
||||
wallButtons[i].OnInteract.AddListener(Toggle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UnregisterEvents()
|
||||
{
|
||||
if (pressurePlates != null)
|
||||
{
|
||||
for (int i = 0; i < pressurePlates.Length; i++)
|
||||
{
|
||||
if (pressurePlates[i] == null)
|
||||
continue;
|
||||
|
||||
pressurePlates[i].OnPressed.RemoveListener(SetOn);
|
||||
pressurePlates[i].OnReleased.RemoveListener(SetOff);
|
||||
}
|
||||
}
|
||||
|
||||
if (wallButtons != null)
|
||||
{
|
||||
for (int i = 0; i < wallButtons.Length; i++)
|
||||
{
|
||||
if (wallButtons[i] == null)
|
||||
continue;
|
||||
|
||||
wallButtons[i].OnInteract.RemoveListener(Toggle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Code/Scripts/Interaction/TestBlock.cs.meta
Normal file
2
Assets/Code/Scripts/Interaction/TestBlock.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b93699191d7b58146b2c38540cbed40f
|
||||
138
Assets/Code/Scripts/Interaction/WallInteractButton.cs
Normal file
138
Assets/Code/Scripts/Interaction/WallInteractButton.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public class WallInteractButton : MonoBehaviour
|
||||
{
|
||||
[Header("Interaction")]
|
||||
[Tooltip("Key the player must press to interact (keyboard fallback).")]
|
||||
[SerializeField] private Key interactKey = Key.E;
|
||||
|
||||
[Tooltip("If true, the button can only be triggered once.")]
|
||||
[SerializeField] private bool oneShot = false;
|
||||
|
||||
[Header("Physical Press")]
|
||||
[Tooltip("Transform that moves to simulate a physical press (optional).")]
|
||||
[SerializeField] private Transform buttonMesh;
|
||||
|
||||
[Tooltip("How far the button moves when pressed.")]
|
||||
[SerializeField] private float pressDepth = 0.05f;
|
||||
|
||||
[Tooltip("Speed of the press/release animation.")]
|
||||
[SerializeField] private float pressSpeed = 10f;
|
||||
|
||||
[Header("Events")]
|
||||
public UnityEvent OnInteract;
|
||||
|
||||
private bool m_playerInRange;
|
||||
private bool m_used;
|
||||
|
||||
private Vector3 m_buttonRestPos;
|
||||
private Vector3 m_buttonPressedPos;
|
||||
private bool m_isVisuallyPressed;
|
||||
|
||||
public PlayerHeadController headController;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
Collider col = GetComponent<Collider>();
|
||||
col.isTrigger = true;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (buttonMesh != null)
|
||||
{
|
||||
m_buttonRestPos = buttonMesh.localPosition;
|
||||
m_buttonPressedPos = m_buttonRestPos - buttonMesh.localRotation * Vector3.forward * pressDepth;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!headController.isHoldingHead && m_playerInRange && Keyboard.current != null && Keyboard.current[interactKey].wasPressedThisFrame)
|
||||
{
|
||||
TryInteract();
|
||||
}
|
||||
|
||||
AnimateButton();
|
||||
}
|
||||
|
||||
private void TryInteract()
|
||||
{
|
||||
if (oneShot && m_used)
|
||||
return;
|
||||
|
||||
m_used = true;
|
||||
m_isVisuallyPressed = true;
|
||||
OnInteract?.Invoke();
|
||||
|
||||
if (!oneShot)
|
||||
Invoke(nameof(ReleaseVisual), 0.15f);
|
||||
}
|
||||
|
||||
private void ReleaseVisual()
|
||||
{
|
||||
m_isVisuallyPressed = false;
|
||||
}
|
||||
|
||||
private void AnimateButton()
|
||||
{
|
||||
if (buttonMesh == null)
|
||||
return;
|
||||
|
||||
Vector3 target = m_isVisuallyPressed ? m_buttonPressedPos : m_buttonRestPos;
|
||||
buttonMesh.localPosition = Vector3.Lerp(buttonMesh.localPosition, target, Time.deltaTime * pressSpeed);
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (IsPlayer(other))
|
||||
m_playerInRange = true;
|
||||
}
|
||||
|
||||
private void OnTriggerExit(Collider other)
|
||||
{
|
||||
if (IsPlayer(other))
|
||||
m_playerInRange = false;
|
||||
}
|
||||
|
||||
private bool IsPlayer(Collider other)
|
||||
{
|
||||
if (other.CompareTag("Player"))
|
||||
return true;
|
||||
|
||||
if (other.GetComponentInParent<PlayerMovement>() != null)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
Collider col = GetComponent<Collider>();
|
||||
if (col == null)
|
||||
return;
|
||||
|
||||
Gizmos.matrix = transform.localToWorldMatrix;
|
||||
Gizmos.color = m_playerInRange
|
||||
? new Color(0f, 1f, 0f, 0.25f)
|
||||
: new Color(1f, 0.9f, 0f, 0.15f);
|
||||
|
||||
if (col is SphereCollider sphere)
|
||||
{
|
||||
Gizmos.DrawSphere(sphere.center, sphere.radius);
|
||||
Gizmos.color = m_playerInRange ? Color.green : Color.yellow;
|
||||
Gizmos.DrawWireSphere(sphere.center, sphere.radius);
|
||||
}
|
||||
else if (col is BoxCollider box)
|
||||
{
|
||||
Gizmos.DrawCube(box.center, box.size);
|
||||
Gizmos.color = m_playerInRange ? Color.green : Color.yellow;
|
||||
Gizmos.DrawWireCube(box.center, box.size);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96a3f628ff18ea34788167cc398180ca
|
||||
8
Assets/Code/Scripts/Level.meta
Normal file
8
Assets/Code/Scripts/Level.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e7b9c54377674993a7922f84e9cfcce
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
262
Assets/Code/Scripts/Level/SubtitleSequencePlayer.cs
Normal file
262
Assets/Code/Scripts/Level/SubtitleSequencePlayer.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Reusable subtitle player that renders and plays subtitle lines loaded from JSON.
|
||||
/// </summary>
|
||||
public class SubtitleSequencePlayer : MonoBehaviour
|
||||
{
|
||||
[Serializable]
|
||||
private struct SubtitleLine
|
||||
{
|
||||
public string speaker;
|
||||
public string text;
|
||||
public float duration;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private struct SubtitleFile
|
||||
{
|
||||
public SubtitleLine[] lines;
|
||||
}
|
||||
|
||||
[Header("Optional Default Data")]
|
||||
[Tooltip("Used only if trigger zone calls PlayDefault().")]
|
||||
[SerializeField] private TextAsset defaultSubtitleJson;
|
||||
|
||||
[SerializeField] private float typewriterCharsPerSecond = 40f;
|
||||
[SerializeField] private float fadeDuration = 0.2f;
|
||||
[SerializeField] private float gapBetweenLines = 0.15f;
|
||||
|
||||
[Header("Visual")]
|
||||
[SerializeField] private int fontSize = 28;
|
||||
[SerializeField] private int speakerFontSize = 18;
|
||||
[SerializeField] private float horizontalPadding = 28f;
|
||||
[SerializeField] private float bottomOffset = 56f;
|
||||
[SerializeField] private Color textColor = new Color(1f, 1f, 1f, 1f);
|
||||
[SerializeField] private Color speakerColor = new Color(1f, 0.85f, 0.35f, 1f);
|
||||
[SerializeField] private Color backgroundColor = new Color(0f, 0f, 0f, 0.62f);
|
||||
|
||||
private string m_currentSpeaker;
|
||||
private string m_currentText;
|
||||
private GUIStyle m_textStyle;
|
||||
private GUIStyle m_speakerStyle;
|
||||
private Texture2D m_background;
|
||||
private bool m_isShowing;
|
||||
private bool m_isPlaying;
|
||||
private float m_alpha;
|
||||
private SubtitleLine[] m_runtimeLines = Array.Empty<SubtitleLine>();
|
||||
|
||||
public bool IsPlaying => m_isPlaying;
|
||||
|
||||
public bool TryPlay(TextAsset subtitleJson, float initialDelay = 0f)
|
||||
{
|
||||
if (m_isPlaying)
|
||||
return false;
|
||||
|
||||
if (!TryReadLinesFromJson(subtitleJson, out SubtitleLine[] parsedLines))
|
||||
return false;
|
||||
|
||||
m_runtimeLines = parsedLines;
|
||||
StartCoroutine(PlaySequence(initialDelay));
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool PlayDefault(float initialDelay = 0f)
|
||||
{
|
||||
return TryPlay(defaultSubtitleJson, initialDelay);
|
||||
}
|
||||
|
||||
private IEnumerator PlaySequence(float initialDelay)
|
||||
{
|
||||
m_isPlaying = true;
|
||||
|
||||
if (m_runtimeLines == null || m_runtimeLines.Length == 0)
|
||||
{
|
||||
m_isPlaying = false;
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (initialDelay > 0f)
|
||||
yield return new WaitForSeconds(initialDelay);
|
||||
|
||||
for (int i = 0; i < m_runtimeLines.Length; i++)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(m_runtimeLines[i].text) || m_runtimeLines[i].duration <= 0f)
|
||||
continue;
|
||||
|
||||
yield return StartCoroutine(ShowLine(m_runtimeLines[i]));
|
||||
|
||||
if (gapBetweenLines > 0f)
|
||||
yield return new WaitForSeconds(gapBetweenLines);
|
||||
}
|
||||
|
||||
m_currentSpeaker = string.Empty;
|
||||
m_currentText = string.Empty;
|
||||
m_isPlaying = false;
|
||||
}
|
||||
|
||||
private bool TryReadLinesFromJson(TextAsset subtitleJson, out SubtitleLine[] parsedLines)
|
||||
{
|
||||
parsedLines = Array.Empty<SubtitleLine>();
|
||||
|
||||
if (subtitleJson == null || string.IsNullOrWhiteSpace(subtitleJson.text))
|
||||
return false;
|
||||
|
||||
SubtitleFile file;
|
||||
try
|
||||
{
|
||||
file = JsonUtility.FromJson<SubtitleFile>(subtitleJson.text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.lines == null || file.lines.Length == 0)
|
||||
return false;
|
||||
|
||||
parsedLines = file.lines;
|
||||
return true;
|
||||
}
|
||||
|
||||
private IEnumerator ShowLine(SubtitleLine line)
|
||||
{
|
||||
m_currentSpeaker = line.speaker;
|
||||
m_currentText = string.Empty;
|
||||
m_isShowing = true;
|
||||
|
||||
if (fadeDuration > 0f)
|
||||
{
|
||||
float fadeIn = 0f;
|
||||
while (fadeIn < fadeDuration)
|
||||
{
|
||||
fadeIn += Time.deltaTime;
|
||||
m_alpha = Mathf.Clamp01(fadeIn / fadeDuration);
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_alpha = 1f;
|
||||
}
|
||||
|
||||
float typeTime = 0f;
|
||||
int totalChars = line.text.Length;
|
||||
if (typewriterCharsPerSecond > 0f)
|
||||
{
|
||||
while (m_currentText.Length < totalChars)
|
||||
{
|
||||
typeTime += Time.deltaTime;
|
||||
int visibleChars = Mathf.Clamp(Mathf.FloorToInt(typeTime * typewriterCharsPerSecond), 0, totalChars);
|
||||
m_currentText = line.text.Substring(0, visibleChars);
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_currentText = line.text;
|
||||
}
|
||||
|
||||
float holdDuration = Mathf.Max(0f, line.duration - (typewriterCharsPerSecond > 0f ? typeTime : 0f));
|
||||
if (holdDuration > 0f)
|
||||
yield return new WaitForSeconds(holdDuration);
|
||||
|
||||
if (fadeDuration > 0f)
|
||||
{
|
||||
float fadeOut = fadeDuration;
|
||||
while (fadeOut > 0f)
|
||||
{
|
||||
fadeOut -= Time.deltaTime;
|
||||
m_alpha = Mathf.Clamp01(fadeOut / fadeDuration);
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
m_alpha = 0f;
|
||||
m_isShowing = false;
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
if (!m_isShowing || string.IsNullOrEmpty(m_currentText))
|
||||
return;
|
||||
|
||||
EnsureStyles();
|
||||
|
||||
float maxWidth = Mathf.Min(Screen.width - 24f, 940f);
|
||||
float textWidth = maxWidth - horizontalPadding * 2f;
|
||||
float speakerHeight = string.IsNullOrEmpty(m_currentSpeaker)
|
||||
? 0f
|
||||
: m_speakerStyle.CalcHeight(new GUIContent(m_currentSpeaker), textWidth);
|
||||
float textHeight = m_textStyle.CalcHeight(new GUIContent(string.IsNullOrEmpty(m_currentText) ? " " : m_currentText), textWidth);
|
||||
|
||||
float boxWidth = maxWidth;
|
||||
float boxHeight = speakerHeight + textHeight + 28f;
|
||||
float boxX = (Screen.width - boxWidth) * 0.5f;
|
||||
float boxY = Screen.height - bottomOffset - boxHeight;
|
||||
|
||||
Rect boxRect = new Rect(boxX, boxY, boxWidth, boxHeight);
|
||||
Color previousColor = GUI.color;
|
||||
GUI.color = new Color(1f, 1f, 1f, m_alpha);
|
||||
GUI.DrawTexture(boxRect, m_background);
|
||||
|
||||
float yOffset = boxRect.y + 10f;
|
||||
if (!string.IsNullOrEmpty(m_currentSpeaker))
|
||||
{
|
||||
Rect speakerRect = new Rect(
|
||||
boxRect.x + horizontalPadding,
|
||||
yOffset,
|
||||
textWidth,
|
||||
speakerHeight);
|
||||
GUI.Label(speakerRect, m_currentSpeaker, m_speakerStyle);
|
||||
yOffset += speakerHeight + 2f;
|
||||
}
|
||||
|
||||
Rect textRect = new Rect(
|
||||
boxRect.x + horizontalPadding,
|
||||
yOffset,
|
||||
textWidth,
|
||||
textHeight);
|
||||
|
||||
GUI.Label(textRect, m_currentText, m_textStyle);
|
||||
GUI.color = previousColor;
|
||||
}
|
||||
|
||||
private void EnsureStyles()
|
||||
{
|
||||
if (m_textStyle != null)
|
||||
return;
|
||||
|
||||
m_background = new Texture2D(1, 1);
|
||||
m_background.SetPixel(0, 0, backgroundColor);
|
||||
m_background.Apply();
|
||||
|
||||
m_textStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
fontSize = fontSize,
|
||||
wordWrap = true,
|
||||
richText = false,
|
||||
clipping = TextClipping.Clip,
|
||||
};
|
||||
m_textStyle.normal.textColor = textColor;
|
||||
|
||||
m_speakerStyle = new GUIStyle(GUI.skin.label)
|
||||
{
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
fontSize = speakerFontSize,
|
||||
fontStyle = FontStyle.Bold,
|
||||
wordWrap = false,
|
||||
clipping = TextClipping.Clip,
|
||||
};
|
||||
m_speakerStyle.normal.textColor = speakerColor;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (m_background != null)
|
||||
Destroy(m_background);
|
||||
}
|
||||
}
|
||||
2
Assets/Code/Scripts/Level/SubtitleSequencePlayer.cs.meta
Normal file
2
Assets/Code/Scripts/Level/SubtitleSequencePlayer.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4947743d7bc9b4589b9932d429517d3a
|
||||
51
Assets/Code/Scripts/Level/SubtitleTriggerZone.cs
Normal file
51
Assets/Code/Scripts/Level/SubtitleTriggerZone.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Trigger zone that starts a subtitle JSON sequence on a linked SubtitleSequencePlayer.
|
||||
/// Put this on the zone collider object, and link the player on your empty object.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public class SubtitleTriggerZone : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[SerializeField] private SubtitleSequencePlayer subtitlePlayer;
|
||||
[SerializeField] private TextAsset subtitleJson;
|
||||
|
||||
[Header("Playback")]
|
||||
[SerializeField] private float initialDelay = 0f;
|
||||
[SerializeField] private bool oneShot = true;
|
||||
|
||||
private bool m_hasPlayed;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
Collider col = GetComponent<Collider>();
|
||||
col.isTrigger = true;
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (!IsPlayer(other))
|
||||
return;
|
||||
|
||||
if (oneShot && m_hasPlayed)
|
||||
return;
|
||||
|
||||
if (subtitlePlayer == null)
|
||||
return;
|
||||
|
||||
if (subtitlePlayer.TryPlay(subtitleJson, initialDelay))
|
||||
m_hasPlayed = true;
|
||||
}
|
||||
|
||||
private bool IsPlayer(Collider other)
|
||||
{
|
||||
if (other.CompareTag("Player"))
|
||||
return true;
|
||||
|
||||
if (other.GetComponentInParent<PlayerMovement>() != null)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
2
Assets/Code/Scripts/Level/SubtitleTriggerZone.cs.meta
Normal file
2
Assets/Code/Scripts/Level/SubtitleTriggerZone.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33a33c65f75e2443383c2e29bd6bf5f1
|
||||
8
Assets/Code/Scripts/Player.meta
Normal file
8
Assets/Code/Scripts/Player.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35fd21e5cd1e88f45ac493add1565094
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
38
Assets/Code/Scripts/Player/InputSystem.inputsettings.asset
Normal file
38
Assets/Code/Scripts/Player/InputSystem.inputsettings.asset
Normal file
@@ -0,0 +1,38 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c46f07b5ed07e4e92aa78254188d3d10, type: 3}
|
||||
m_Name: InputSystem.inputsettings
|
||||
m_EditorClassIdentifier: Unity.InputSystem::UnityEngine.InputSystem.InputSettings
|
||||
m_SupportedDevices: []
|
||||
m_UpdateMode: 1
|
||||
m_ScrollDeltaBehavior: 0
|
||||
m_MaxEventBytesPerUpdate: 5242880
|
||||
m_MaxQueuedEventsPerUpdate: 1000
|
||||
m_CompensateForScreenOrientation: 1
|
||||
m_BackgroundBehavior: 0
|
||||
m_EditorInputBehaviorInPlayMode: 0
|
||||
m_InputActionPropertyDrawerMode: 0
|
||||
m_DefaultDeadzoneMin: 0.125
|
||||
m_DefaultDeadzoneMax: 0.925
|
||||
m_DefaultButtonPressPoint: 0.5
|
||||
m_ButtonReleaseThreshold: 0.75
|
||||
m_DefaultTapTime: 0.2
|
||||
m_DefaultSlowTapTime: 0.5
|
||||
m_DefaultHoldTime: 0.4
|
||||
m_TapRadius: 5
|
||||
m_MultiTapDelayTime: 0.75
|
||||
m_DisableRedundantEventsMerging: 0
|
||||
m_ShortcutKeysConsumeInputs: 0
|
||||
m_iOSSettings:
|
||||
m_MotionUsage:
|
||||
m_Enabled: 0
|
||||
m_Description:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23cbff887a25b3802a937fef9deab3ec
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1118
Assets/Code/Scripts/Player/InputSystem_Actions.inputactions
Normal file
1118
Assets/Code/Scripts/Player/InputSystem_Actions.inputactions
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b319948d6750538498f201a24c05aef3
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3}
|
||||
generateWrapperCode: 0
|
||||
wrapperCodePath:
|
||||
wrapperClassName:
|
||||
wrapperCodeNamespace:
|
||||
6
Assets/Code/Scripts/Player/PlayerAnimation.cs
Normal file
6
Assets/Code/Scripts/Player/PlayerAnimation.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class PlayerAnimation
|
||||
{
|
||||
|
||||
}
|
||||
2
Assets/Code/Scripts/Player/PlayerAnimation.cs.meta
Normal file
2
Assets/Code/Scripts/Player/PlayerAnimation.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c207b98ba2f6229b84db151dd7b4255
|
||||
120
Assets/Code/Scripts/Player/PlayerHeadControll.cs
Normal file
120
Assets/Code/Scripts/Player/PlayerHeadControll.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class PlayerHeadController : MonoBehaviour
|
||||
{
|
||||
public Transform Head;
|
||||
public Transform CameraTransform;
|
||||
|
||||
public Transform BodyTransform;
|
||||
|
||||
|
||||
public float ThrowForce;
|
||||
public float PickupDistance;
|
||||
public bool isHoldingHead;
|
||||
|
||||
private Rigidbody m_headRigidbody;
|
||||
|
||||
private Vector3 m_headInitialLocalPos;
|
||||
private Quaternion m_headInitialLocalRot;
|
||||
|
||||
private Animator animator;
|
||||
private PlayerInputController input;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
animator = GetComponent<Animator>();
|
||||
input = GetComponent<PlayerInputController>();
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
Cursor.lockState = CursorLockMode.Locked;
|
||||
|
||||
Vector3 offset = new Vector3(0f, -0.5f, 0.5f);
|
||||
|
||||
m_headInitialLocalPos = BodyTransform.localPosition + offset;
|
||||
m_headInitialLocalRot = BodyTransform.localRotation;
|
||||
m_headRigidbody = Head.GetComponent<Rigidbody>();
|
||||
|
||||
Head.SetParent(null);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (input.HeadInteractionPressed)
|
||||
{
|
||||
InteractHead();
|
||||
}
|
||||
|
||||
if (input.ThrowPressed)
|
||||
{
|
||||
ThrowHead();
|
||||
}
|
||||
}
|
||||
|
||||
private void InteractHead()
|
||||
{
|
||||
if (!isHoldingHead)
|
||||
TryPickupHead();
|
||||
else
|
||||
DropHead();
|
||||
}
|
||||
|
||||
private void DropHead()
|
||||
{
|
||||
Debug.Log("DropHead");
|
||||
animator.SetTrigger("Throw");
|
||||
|
||||
isHoldingHead = false;
|
||||
|
||||
Head.SetParent(null);
|
||||
|
||||
m_headRigidbody = Head.gameObject.AddComponent<Rigidbody>();
|
||||
m_headRigidbody.mass = 1f;
|
||||
|
||||
m_headRigidbody.constraints =
|
||||
RigidbodyConstraints.FreezeRotationX |
|
||||
RigidbodyConstraints.FreezeRotationZ |
|
||||
RigidbodyConstraints.FreezeRotationY;
|
||||
}
|
||||
|
||||
private void ThrowHead()
|
||||
{
|
||||
Debug.Log("ThrowHead");
|
||||
if (!isHoldingHead)
|
||||
return;
|
||||
|
||||
DropHead();
|
||||
|
||||
m_headRigidbody.AddForce(CameraTransform.forward * ThrowForce, ForceMode.Impulse);
|
||||
}
|
||||
|
||||
private void TryPickupHead()
|
||||
{
|
||||
if (isHoldingHead)
|
||||
return;
|
||||
|
||||
float distance = Vector3.Distance(transform.position, Head.position);
|
||||
|
||||
if (distance <= PickupDistance)
|
||||
{
|
||||
PickupHead();
|
||||
}
|
||||
}
|
||||
|
||||
private void PickupHead()
|
||||
{
|
||||
Debug.Log("PickupHead");
|
||||
isHoldingHead = true;
|
||||
|
||||
if (m_headRigidbody != null)
|
||||
{
|
||||
Destroy(m_headRigidbody);
|
||||
}
|
||||
|
||||
Head.SetParent(transform);
|
||||
|
||||
Head.localPosition = m_headInitialLocalPos;
|
||||
Head.localRotation = m_headInitialLocalRot;
|
||||
}
|
||||
}
|
||||
2
Assets/Code/Scripts/Player/PlayerHeadControll.cs.meta
Normal file
2
Assets/Code/Scripts/Player/PlayerHeadControll.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2da51dfecccc45b469912e3bb3f1953b
|
||||
80
Assets/Code/Scripts/Player/PlayerInputHandler.cs
Normal file
80
Assets/Code/Scripts/Player/PlayerInputHandler.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
public class PlayerInputController : MonoBehaviour
|
||||
{
|
||||
public InputActionAsset InputActions;
|
||||
public bool InputEnabled { get; private set; } = true;
|
||||
|
||||
private InputAction m_moveAction;
|
||||
private InputAction m_lookAction;
|
||||
private InputAction m_jumpAction;
|
||||
private InputAction m_throwAction;
|
||||
private InputAction m_shiftAction;
|
||||
private InputAction m_interactAction;
|
||||
private InputAction m_headInteractAction;
|
||||
|
||||
|
||||
public Vector2 MoveAmount { get; private set; }
|
||||
public Vector2 LookAmount { get; private set; }
|
||||
|
||||
public bool JumpPressed { get; private set; }
|
||||
public bool ShiftPressed { get; private set; }
|
||||
public bool ThrowPressed { get; private set; }
|
||||
public bool InteractPressed { get; private set; }
|
||||
public bool HeadInteractionPressed { get; private set; }
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
var map = InputActions.FindActionMap("Player");
|
||||
|
||||
m_moveAction = map.FindAction("Move");
|
||||
m_lookAction = map.FindAction("Look");
|
||||
m_jumpAction = map.FindAction("Jump");
|
||||
m_shiftAction = map.FindAction("Shift");
|
||||
m_throwAction = map.FindAction("Throw");
|
||||
m_interactAction = map.FindAction("Interact");
|
||||
m_headInteractAction = map.FindAction("HeadInteract");
|
||||
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
InputActions.FindActionMap("Player").Enable();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
InputActions.FindActionMap("Player").Disable();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!InputEnabled)
|
||||
{
|
||||
MoveAmount = Vector2.zero;
|
||||
LookAmount = Vector2.zero;
|
||||
ShiftPressed = false;
|
||||
JumpPressed = false;
|
||||
ThrowPressed = false;
|
||||
InteractPressed = false;
|
||||
HeadInteractionPressed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
MoveAmount = m_moveAction.ReadValue<Vector2>();
|
||||
LookAmount = m_lookAction.ReadValue<Vector2>();
|
||||
|
||||
ShiftPressed = m_shiftAction.IsPressed();
|
||||
JumpPressed = m_jumpAction.WasPressedThisFrame();
|
||||
ThrowPressed = m_throwAction.WasPressedThisFrame();
|
||||
InteractPressed = m_interactAction.WasPressedThisFrame();
|
||||
HeadInteractionPressed = m_headInteractAction.WasPressedThisFrame();
|
||||
}
|
||||
|
||||
public void SetInputEnabled(bool enabled)
|
||||
{
|
||||
InputEnabled = enabled;
|
||||
}
|
||||
}
|
||||
2
Assets/Code/Scripts/Player/PlayerInputHandler.cs.meta
Normal file
2
Assets/Code/Scripts/Player/PlayerInputHandler.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d8f349ed7dc088a4a6e2690ee87094a
|
||||
34
Assets/Code/Scripts/Player/PlayerJump.cs
Normal file
34
Assets/Code/Scripts/Player/PlayerJump.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class PlayerJump : MonoBehaviour
|
||||
{
|
||||
public float JumpForce = 5;
|
||||
|
||||
public Transform GroundCheck;
|
||||
public float GroundCheckRadius = 0.2f;
|
||||
|
||||
private Rigidbody m_rigidbody;
|
||||
private PlayerInputController input;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_rigidbody = GetComponent<Rigidbody>();
|
||||
input = GetComponent<PlayerInputController>();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (input.JumpPressed)
|
||||
{
|
||||
Jump();
|
||||
}
|
||||
}
|
||||
|
||||
public void Jump()
|
||||
{
|
||||
if (Physics.CheckSphere(GroundCheck.position, GroundCheckRadius, LayerMask.GetMask("Ground")))
|
||||
{
|
||||
m_rigidbody.AddForce(Vector3.up * JumpForce, ForceMode.Impulse);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Code/Scripts/Player/PlayerJump.cs.meta
Normal file
2
Assets/Code/Scripts/Player/PlayerJump.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c524d12bc1668e42a00cbd8050107f6
|
||||
57
Assets/Code/Scripts/Player/PlayerLook.cs
Normal file
57
Assets/Code/Scripts/Player/PlayerLook.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class PlayerLook : MonoBehaviour
|
||||
{
|
||||
public Transform CameraTransform;
|
||||
public Transform Head;
|
||||
|
||||
public float RotateSpeed = 5;
|
||||
public float MaxLookAngle = 90f;
|
||||
|
||||
private float m_verticalRotation = 0f;
|
||||
|
||||
private Rigidbody m_rigidbody;
|
||||
private PlayerInputController input;
|
||||
private PlayerHeadController headController;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_rigidbody = GetComponent<Rigidbody>();
|
||||
input = GetComponent<PlayerInputController>();
|
||||
headController = GetComponent<PlayerHeadController>();
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
Vector2 m_lookAmt = input.LookAmount;
|
||||
|
||||
if (m_lookAmt.magnitude <= 0.01f)
|
||||
return;
|
||||
|
||||
if (!headController.isHoldingHead || input.ShiftPressed && headController.isHoldingHead)
|
||||
{
|
||||
float headRotation = m_lookAmt.x * RotateSpeed * Time.deltaTime;
|
||||
Head.Rotate(0, headRotation, 0);
|
||||
|
||||
if (CameraTransform != null)
|
||||
{
|
||||
m_verticalRotation -= m_lookAmt.y * RotateSpeed * Time.deltaTime;
|
||||
m_verticalRotation = Mathf.Clamp(m_verticalRotation, -MaxLookAngle, MaxLookAngle);
|
||||
CameraTransform.localRotation = Quaternion.Euler(m_verticalRotation, 0, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float horizontalRotation = m_lookAmt.x * RotateSpeed * Time.deltaTime;
|
||||
Quaternion deltaRotation = Quaternion.Euler(0, horizontalRotation, 0);
|
||||
m_rigidbody.MoveRotation(m_rigidbody.rotation * deltaRotation);
|
||||
|
||||
if (CameraTransform != null)
|
||||
{
|
||||
m_verticalRotation -= m_lookAmt.y * RotateSpeed * Time.deltaTime;
|
||||
m_verticalRotation = Mathf.Clamp(m_verticalRotation, -MaxLookAngle, MaxLookAngle);
|
||||
CameraTransform.localRotation = Quaternion.Euler(m_verticalRotation, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Code/Scripts/Player/PlayerLook.cs.meta
Normal file
2
Assets/Code/Scripts/Player/PlayerLook.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c1ddada0161b8c4783806ef6775348a
|
||||
74
Assets/Code/Scripts/Player/PlayerMovement.cs
Normal file
74
Assets/Code/Scripts/Player/PlayerMovement.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class PlayerMovement : MonoBehaviour
|
||||
{
|
||||
public float WalkSpeed = 10;
|
||||
public float rotationSpeed = 10f;
|
||||
|
||||
public Animator animator;
|
||||
public Transform cameraTransform;
|
||||
|
||||
private Rigidbody m_rigidbody;
|
||||
private PlayerInputController input;
|
||||
private PlayerHeadController headController;
|
||||
|
||||
private Vector3 moveDirection;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_rigidbody = GetComponent<Rigidbody>();
|
||||
input = GetComponent<PlayerInputController>();
|
||||
animator = GetComponent<Animator>();
|
||||
headController = GetComponent<PlayerHeadController>();
|
||||
|
||||
if (m_rigidbody != null)
|
||||
{
|
||||
m_rigidbody.freezeRotation = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
Vector2 m_moveAmt = input.MoveAmount;
|
||||
|
||||
float horizontal = m_moveAmt.x;
|
||||
float vertical = m_moveAmt.y;
|
||||
|
||||
Vector3 cameraForward = cameraTransform.forward;
|
||||
Vector3 cameraRight = cameraTransform.right;
|
||||
|
||||
cameraForward.y = 0f;
|
||||
cameraRight.y = 0f;
|
||||
|
||||
cameraForward.Normalize();
|
||||
cameraRight.Normalize();
|
||||
|
||||
moveDirection = (cameraForward * vertical + cameraRight * horizontal).normalized;
|
||||
|
||||
if (headController.isHoldingHead)
|
||||
{
|
||||
m_rigidbody.MovePosition(
|
||||
m_rigidbody.position + Time.deltaTime * WalkSpeed * moveDirection
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (moveDirection.magnitude >= 0.1f)
|
||||
{
|
||||
m_rigidbody.MovePosition(
|
||||
m_rigidbody.position + Time.deltaTime * WalkSpeed * moveDirection
|
||||
);
|
||||
|
||||
Quaternion targetRotation = Quaternion.LookRotation(moveDirection);
|
||||
transform.rotation = Quaternion.Slerp(
|
||||
transform.rotation,
|
||||
targetRotation,
|
||||
rotationSpeed * Time.deltaTime
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool isMoving = m_moveAmt.magnitude > 0.1f;
|
||||
animator.SetBool("isWalking", isMoving);
|
||||
}
|
||||
}
|
||||
2
Assets/Code/Scripts/Player/PlayerMovement.cs.meta
Normal file
2
Assets/Code/Scripts/Player/PlayerMovement.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f91586e8c2742341aa8f6925e597bf1
|
||||
264
Assets/Code/Scripts/Player/RobotBootSequence.cs
Normal file
264
Assets/Code/Scripts/Player/RobotBootSequence.cs
Normal file
@@ -0,0 +1,264 @@
|
||||
using System.Collections;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class RobotBootSequence : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
public PlayerInputController InputController;
|
||||
public Transform CameraTransform;
|
||||
|
||||
[Header("Timing")]
|
||||
public bool PlayOnStart = true;
|
||||
[Min(0.1f)] public float BootDuration = 2.4f;
|
||||
[Min(0.1f)] public float CharacterPerSecond = 40f;
|
||||
[Min(0f)] public float LinePause = 0.35f;
|
||||
[Min(0f)] public float DelayBeforeReveal = 0.4f;
|
||||
|
||||
[Header("Motion")]
|
||||
public Vector2 StartYawPitch = new Vector2(-30f, -20f);
|
||||
public float RollWobble = 2.5f;
|
||||
public float WobbleFrequency = 16f;
|
||||
public AnimationCurve EaseCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
|
||||
|
||||
[Header("Boot Text")]
|
||||
public Color BootTextColor = new Color(0.62f, 1f, 0.7f, 1f);
|
||||
public string[] BootLines =
|
||||
{
|
||||
"UNIT SB-3954 | preparing startup . . .",
|
||||
"verification of OS-5 . . . 4 . . . 3 . . . 2 . . . 1",
|
||||
"system integrity: OK",
|
||||
"motor bus: OK",
|
||||
"vision pipeline: ONLINE",
|
||||
"SYSTEM OK"
|
||||
};
|
||||
|
||||
[Header("Optional Audio")]
|
||||
public AudioSource BootAudioSource;
|
||||
|
||||
private bool m_IsPlaying;
|
||||
|
||||
private struct BootUI
|
||||
{
|
||||
public Canvas Canvas;
|
||||
public RectTransform LeftPanel;
|
||||
public RectTransform RightPanel;
|
||||
public TextMeshProUGUI Text;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (InputController == null)
|
||||
{
|
||||
InputController = GetComponent<PlayerInputController>();
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (PlayOnStart)
|
||||
{
|
||||
StartCoroutine(PlayBootSequence());
|
||||
}
|
||||
}
|
||||
|
||||
[ContextMenu("Play Boot Sequence")]
|
||||
public void PlayBootSequenceFromMenu()
|
||||
{
|
||||
if (!Application.isPlaying || m_IsPlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StartCoroutine(PlayBootSequence());
|
||||
}
|
||||
|
||||
public IEnumerator PlayBootSequence()
|
||||
{
|
||||
if (m_IsPlaying)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
m_IsPlaying = true;
|
||||
|
||||
if (InputController != null)
|
||||
{
|
||||
InputController.SetInputEnabled(false);
|
||||
}
|
||||
|
||||
if (BootAudioSource != null)
|
||||
{
|
||||
BootAudioSource.Play();
|
||||
}
|
||||
|
||||
if (CameraTransform == null)
|
||||
{
|
||||
m_IsPlaying = false;
|
||||
if (InputController != null)
|
||||
{
|
||||
InputController.SetInputEnabled(true);
|
||||
}
|
||||
yield break;
|
||||
}
|
||||
|
||||
Quaternion gameplayRotation = CameraTransform.localRotation;
|
||||
Quaternion fromRotation = Quaternion.Euler(StartYawPitch.y, StartYawPitch.x, 0f) * gameplayRotation;
|
||||
CameraTransform.localRotation = fromRotation;
|
||||
|
||||
BootUI bootUI = CreateBootUI();
|
||||
|
||||
yield return StartCoroutine(PlayBootText(bootUI.Text));
|
||||
|
||||
if (DelayBeforeReveal > 0f)
|
||||
{
|
||||
yield return new WaitForSeconds(DelayBeforeReveal);
|
||||
}
|
||||
|
||||
float elapsed = 0f;
|
||||
while (elapsed < BootDuration)
|
||||
{
|
||||
elapsed += Time.deltaTime;
|
||||
float t = Mathf.Clamp01(elapsed / BootDuration);
|
||||
float eased = EaseCurve.Evaluate(t);
|
||||
|
||||
float wobbleFade = 1f - eased;
|
||||
float roll = Mathf.Sin(Time.time * WobbleFrequency) * RollWobble * wobbleFade;
|
||||
Quaternion wobbleRotation = Quaternion.Euler(0f, 0f, roll);
|
||||
|
||||
CameraTransform.localRotation = Quaternion.Slerp(fromRotation, gameplayRotation, eased) * wobbleRotation;
|
||||
|
||||
RectTransform rootRect = bootUI.Canvas.GetComponent<RectTransform>();
|
||||
float halfWidth = rootRect.rect.width * 0.5f;
|
||||
float leftTarget = -(halfWidth + 24f);
|
||||
float rightTarget = halfWidth + 24f;
|
||||
bootUI.LeftPanel.anchoredPosition = new Vector2(Mathf.Lerp(0f, leftTarget, eased), 0f);
|
||||
bootUI.RightPanel.anchoredPosition = new Vector2(Mathf.Lerp(0f, rightTarget, eased), 0f);
|
||||
|
||||
Color textColor = bootUI.Text.color;
|
||||
textColor.a = 1f - eased;
|
||||
bootUI.Text.color = textColor;
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
CameraTransform.localRotation = gameplayRotation;
|
||||
|
||||
if (bootUI.Canvas != null)
|
||||
{
|
||||
Destroy(bootUI.Canvas.gameObject);
|
||||
}
|
||||
|
||||
if (InputController != null)
|
||||
{
|
||||
InputController.SetInputEnabled(true);
|
||||
}
|
||||
|
||||
m_IsPlaying = false;
|
||||
}
|
||||
|
||||
private IEnumerator PlayBootText(TextMeshProUGUI label)
|
||||
{
|
||||
if (label == null || BootLines == null || BootLines.Length == 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
label.text = string.Empty;
|
||||
float charDelay = CharacterPerSecond <= 0f ? 0f : 1f / CharacterPerSecond;
|
||||
|
||||
for (int i = 0; i < BootLines.Length; i++)
|
||||
{
|
||||
string line = BootLines[i];
|
||||
for (int c = 0; c < line.Length; c++)
|
||||
{
|
||||
label.text += line[c];
|
||||
if (charDelay > 0f)
|
||||
{
|
||||
yield return new WaitForSeconds(charDelay);
|
||||
}
|
||||
}
|
||||
|
||||
if (i < BootLines.Length - 1)
|
||||
{
|
||||
label.text += "\n";
|
||||
}
|
||||
|
||||
if (LinePause > 0f)
|
||||
{
|
||||
yield return new WaitForSeconds(LinePause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BootUI CreateBootUI()
|
||||
{
|
||||
BootUI ui = new BootUI();
|
||||
|
||||
GameObject canvasGO = new GameObject("RobotBootCanvas", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
|
||||
Canvas canvas = canvasGO.GetComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.sortingOrder = 5000;
|
||||
|
||||
CanvasScaler scaler = canvasGO.GetComponent<CanvasScaler>();
|
||||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
scaler.referenceResolution = new Vector2(1920f, 1080f);
|
||||
scaler.matchWidthOrHeight = 0.5f;
|
||||
|
||||
RectTransform root = canvasGO.GetComponent<RectTransform>();
|
||||
root.anchorMin = Vector2.zero;
|
||||
root.anchorMax = Vector2.one;
|
||||
root.offsetMin = Vector2.zero;
|
||||
root.offsetMax = Vector2.zero;
|
||||
|
||||
RectTransform leftPanel = CreatePanel("LeftPanel", root, true);
|
||||
RectTransform rightPanel = CreatePanel("RightPanel", root, false);
|
||||
TextMeshProUGUI label = CreateBootLabel(root);
|
||||
|
||||
ui.Canvas = canvas;
|
||||
ui.LeftPanel = leftPanel;
|
||||
ui.RightPanel = rightPanel;
|
||||
ui.Text = label;
|
||||
|
||||
return ui;
|
||||
}
|
||||
|
||||
private RectTransform CreatePanel(string panelName, RectTransform parent, bool isLeft)
|
||||
{
|
||||
GameObject panelGO = new GameObject(panelName, typeof(RectTransform), typeof(Image));
|
||||
RectTransform rect = panelGO.GetComponent<RectTransform>();
|
||||
rect.SetParent(parent, false);
|
||||
rect.anchorMin = isLeft ? new Vector2(0f, 0f) : new Vector2(0.5f, 0f);
|
||||
rect.anchorMax = isLeft ? new Vector2(0.5f, 1f) : new Vector2(1f, 1f);
|
||||
rect.pivot = new Vector2(0.5f, 0.5f);
|
||||
rect.offsetMin = Vector2.zero;
|
||||
rect.offsetMax = Vector2.zero;
|
||||
rect.anchoredPosition = Vector2.zero;
|
||||
|
||||
Image image = panelGO.GetComponent<Image>();
|
||||
image.color = Color.black;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
private TextMeshProUGUI CreateBootLabel(RectTransform parent)
|
||||
{
|
||||
GameObject textGO = new GameObject("BootText", typeof(RectTransform), typeof(TextMeshProUGUI));
|
||||
RectTransform rect = textGO.GetComponent<RectTransform>();
|
||||
rect.SetParent(parent, false);
|
||||
rect.anchorMin = new Vector2(0.13f, 0.5f);
|
||||
rect.anchorMax = new Vector2(0.13f, 0.5f);
|
||||
rect.pivot = new Vector2(0f, 0.5f);
|
||||
rect.sizeDelta = new Vector2(980f, 380f);
|
||||
|
||||
TextMeshProUGUI text = textGO.GetComponent<TextMeshProUGUI>();
|
||||
text.text = string.Empty;
|
||||
text.fontSize = 40f;
|
||||
text.alignment = TextAlignmentOptions.Left;
|
||||
text.color = BootTextColor;
|
||||
text.textWrappingMode = TextWrappingModes.Normal;
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
11
Assets/Code/Scripts/Player/RobotBootSequence.cs.meta
Normal file
11
Assets/Code/Scripts/Player/RobotBootSequence.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ef6855cd57b4f94b47f410d47e89ff1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
216
Assets/Code/Scripts/Player/test.cs
Normal file
216
Assets/Code/Scripts/Player/test.cs
Normal file
@@ -0,0 +1,216 @@
|
||||
//using TMPro;
|
||||
//using UnityEngine;
|
||||
//using UnityEngine.InputSystem;
|
||||
|
||||
//public class PlayerMovement : MonoBehaviour
|
||||
//{
|
||||
// public InputActionAsset InputActions;
|
||||
|
||||
// private InputAction m_moveAction;
|
||||
// private InputAction m_lookAction;
|
||||
// private InputAction m_jumpAction;
|
||||
// private InputAction m_throwAction;
|
||||
// private InputAction m_pickupAction;
|
||||
|
||||
// private Vector2 m_moveAmt;
|
||||
// private Vector2 m_lookAmt;
|
||||
|
||||
// private Rigidbody m_rigidbody;
|
||||
|
||||
// [Header("Camera/Head")]
|
||||
// public Transform CameraTransform;
|
||||
// public float MaxLookAngle = 90f;
|
||||
|
||||
// private float m_verticalRotation = 0f;
|
||||
|
||||
// public float WalkSpeed = 10;
|
||||
// public float RotateSpeed = 5;
|
||||
// public float JumpForce = 5;
|
||||
|
||||
// public Transform GroundCheck;
|
||||
// public float GroundCheckRadius = 0.2f;
|
||||
|
||||
// public Animator animator;
|
||||
|
||||
// [Header("Head Settings")]
|
||||
// public Transform Head;
|
||||
// public float ThrowForce = 10f;
|
||||
// public float PickupDistance = 3f;
|
||||
|
||||
// private bool m_isHeadThrown = false;
|
||||
// private Rigidbody m_headRigidbody;
|
||||
|
||||
// private Vector3 m_headInitialLocalPos;
|
||||
// private Quaternion m_headInitialLocalRot;
|
||||
|
||||
// private void Awake()
|
||||
// {
|
||||
// var map = InputActions.FindActionMap("Player");
|
||||
|
||||
// m_moveAction = map.FindAction("Move");
|
||||
// m_lookAction = map.FindAction("Look");
|
||||
// m_jumpAction = map.FindAction("Jump");
|
||||
// m_throwAction = map.FindAction("Throw");
|
||||
// m_pickupAction = map.FindAction("Pickup");
|
||||
|
||||
// m_rigidbody = GetComponent<Rigidbody>();
|
||||
// animator = GetComponent<Animator>();
|
||||
// }
|
||||
|
||||
// private void OnEnable()
|
||||
// {
|
||||
// InputActions.FindActionMap("Player").Enable();
|
||||
// }
|
||||
|
||||
// private void OnDisable()
|
||||
// {
|
||||
// InputActions.FindActionMap("Player").Disable();
|
||||
// }
|
||||
|
||||
// void Start()
|
||||
// {
|
||||
// Cursor.lockState = CursorLockMode.Locked;
|
||||
// m_headInitialLocalPos = Head.localPosition;
|
||||
// m_headInitialLocalRot = Head.localRotation;
|
||||
// }
|
||||
|
||||
// private void Update()
|
||||
// {
|
||||
// m_moveAmt = m_moveAction.ReadValue<Vector2>();
|
||||
// m_lookAmt = m_lookAction.ReadValue<Vector2>();
|
||||
|
||||
// if (m_jumpAction.WasPressedThisFrame())
|
||||
// {
|
||||
// Jump();
|
||||
// }
|
||||
|
||||
// if (m_throwAction.WasPressedThisFrame())
|
||||
// {
|
||||
// ThrowHead();
|
||||
// }
|
||||
|
||||
// if (m_pickupAction.WasPressedThisFrame())
|
||||
// {
|
||||
// TryPickupHead();
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// private void FixedUpdate()
|
||||
// {
|
||||
// Walking();
|
||||
// Rotating();
|
||||
// }
|
||||
|
||||
// private void Walking()
|
||||
// {
|
||||
// Vector3 move =
|
||||
// transform.forward * m_moveAmt.y +
|
||||
// transform.right * m_moveAmt.x;
|
||||
|
||||
// m_rigidbody.MovePosition(
|
||||
// m_rigidbody.position + move * WalkSpeed * Time.deltaTime
|
||||
// );
|
||||
|
||||
// bool isMoving = m_moveAmt.magnitude > 0.1f;
|
||||
// animator.SetBool("isWalking", isMoving);
|
||||
// }
|
||||
|
||||
|
||||
// private void Rotating()
|
||||
// {
|
||||
// if (m_lookAmt.magnitude <= 0.01f)
|
||||
// return;
|
||||
|
||||
// if (!m_isHeadThrown)
|
||||
// {
|
||||
// NORMAL BODY ROTATION
|
||||
// float horizontalRotation = m_lookAmt.x * RotateSpeed * Time.deltaTime;
|
||||
// Quaternion deltaRotation = Quaternion.Euler(0, horizontalRotation, 0);
|
||||
// m_rigidbody.MoveRotation(m_rigidbody.rotation * deltaRotation);
|
||||
|
||||
// if (CameraTransform != null)
|
||||
// {
|
||||
// m_verticalRotation -= m_lookAmt.y * RotateSpeed * Time.deltaTime;
|
||||
// m_verticalRotation = Mathf.Clamp(m_verticalRotation, -MaxLookAngle, MaxLookAngle);
|
||||
// CameraTransform.localRotation = Quaternion.Euler(m_verticalRotation, 0, 0);
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// HEAD ROTATION ON GROUND
|
||||
// float headRotation = m_lookAmt.x * RotateSpeed * Time.deltaTime;
|
||||
// Head.Rotate(0, headRotation, 0);
|
||||
|
||||
// Add vertical camera rotation when head is on ground
|
||||
// if (CameraTransform != null)
|
||||
// {
|
||||
// m_verticalRotation -= m_lookAmt.y * RotateSpeed * Time.deltaTime;
|
||||
// m_verticalRotation = Mathf.Clamp(m_verticalRotation, -MaxLookAngle, MaxLookAngle);
|
||||
// CameraTransform.localRotation = Quaternion.Euler(m_verticalRotation, 0, 0);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// public void Jump()
|
||||
// {
|
||||
// if (Physics.CheckSphere(GroundCheck.position, GroundCheckRadius, LayerMask.GetMask("Ground")))
|
||||
// {
|
||||
// m_rigidbody.AddForce(Vector3.up * JumpForce, ForceMode.Impulse);
|
||||
// }
|
||||
// }
|
||||
|
||||
// private void ThrowHead()
|
||||
// {
|
||||
// if (m_isHeadThrown)
|
||||
// return;
|
||||
|
||||
// animator.SetTrigger("Throw");
|
||||
|
||||
// m_isHeadThrown = true;
|
||||
|
||||
// Head.SetParent(null);
|
||||
|
||||
// m_headRigidbody = Head.gameObject.AddComponent<Rigidbody>();
|
||||
// m_headRigidbody.mass = 1f;
|
||||
|
||||
// m_headRigidbody.constraints =
|
||||
// RigidbodyConstraints.FreezeRotationX |
|
||||
// RigidbodyConstraints.FreezeRotationZ;
|
||||
|
||||
// m_headRigidbody.AddForce(CameraTransform.forward * ThrowForce, ForceMode.Impulse);
|
||||
// }
|
||||
|
||||
// private void TryPickupHead()
|
||||
// {
|
||||
// if (!m_isHeadThrown)
|
||||
// return;
|
||||
|
||||
// float distance = Vector3.Distance(transform.position, Head.position);
|
||||
|
||||
// if (distance <= PickupDistance)
|
||||
// {
|
||||
// PickupHead();
|
||||
// }
|
||||
// }
|
||||
|
||||
// private void PickupHead()
|
||||
// {
|
||||
// m_isHeadThrown = false;
|
||||
|
||||
// Remove Rigidbody
|
||||
// if (m_headRigidbody != null)
|
||||
// {
|
||||
// Destroy(m_headRigidbody);
|
||||
// }
|
||||
|
||||
// Reattach to player
|
||||
// Head.SetParent(transform);
|
||||
|
||||
// Reset position & rotation
|
||||
// Head.localPosition = m_headInitialLocalPos;
|
||||
// Head.localRotation = m_headInitialLocalRot;
|
||||
// }
|
||||
|
||||
|
||||
//}
|
||||
2
Assets/Code/Scripts/Player/test.cs.meta
Normal file
2
Assets/Code/Scripts/Player/test.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85fce5e45a2682243a133de9ba0a4324
|
||||
8
Assets/Code/Scripts/Rendering.meta
Normal file
8
Assets/Code/Scripts/Rendering.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 97a4ae8015df4732ac9524441048a765
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
123
Assets/Code/Scripts/Rendering/CRTRendererFeature.cs
Normal file
123
Assets/Code/Scripts/Rendering/CRTRendererFeature.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
// using UnityEngine;
|
||||
// using UnityEngine.Rendering;
|
||||
// using UnityEngine.Rendering.RenderGraphModule;
|
||||
// using UnityEngine.Rendering.RenderGraphModule.Util;
|
||||
// using UnityEngine.Rendering.Universal;
|
||||
|
||||
// public class CRTRendererFeature : ScriptableRendererFeature
|
||||
// {
|
||||
// [System.Serializable]
|
||||
// public class CRTSettings
|
||||
// {
|
||||
// public bool EffectEnabled = true;
|
||||
// public RenderPassEvent PassEvent = RenderPassEvent.AfterRenderingPostProcessing;
|
||||
// public Shader CRTShader;
|
||||
|
||||
// [Range(0f, 1f)] public float Intensity = 0.65f;
|
||||
// [Range(0f, 2f)] public float ScanlineDensity = 1.2f;
|
||||
// [Range(0f, 1f)] public float ScanlineStrength = 0.18f;
|
||||
// [Range(0f, 0.2f)] public float Curvature = 0.04f;
|
||||
// [Range(0f, 1f)] public float VignetteStrength = 0.28f;
|
||||
// [Range(0f, 0.05f)] public float ChromaticAberration = 0.004f;
|
||||
// [Range(0f, 0.2f)] public float NoiseStrength = 0.03f;
|
||||
// [Range(0f, 0.1f)] public float FlickerStrength = 0.015f;
|
||||
// }
|
||||
|
||||
// class CRTPass : ScriptableRenderPass
|
||||
// {
|
||||
// private Material m_Material;
|
||||
// private CRTSettings m_Settings;
|
||||
|
||||
// public void Setup(Material material, CRTSettings settings)
|
||||
// {
|
||||
// m_Material = material;
|
||||
// m_Settings = settings;
|
||||
// renderPassEvent = settings.PassEvent;
|
||||
// requiresIntermediateTexture = true;
|
||||
// }
|
||||
|
||||
// public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
|
||||
// {
|
||||
// if (m_Material == null || m_Settings == null || !m_Settings.EffectEnabled)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
|
||||
// if (resourceData.isActiveTargetBackBuffer)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// m_Material.SetFloat("_Intensity", m_Settings.Intensity);
|
||||
// m_Material.SetFloat("_ScanlineDensity", m_Settings.ScanlineDensity);
|
||||
// m_Material.SetFloat("_ScanlineStrength", m_Settings.ScanlineStrength);
|
||||
// m_Material.SetFloat("_Curvature", m_Settings.Curvature);
|
||||
// m_Material.SetFloat("_VignetteStrength", m_Settings.VignetteStrength);
|
||||
// m_Material.SetFloat("_ChromaticAberration", m_Settings.ChromaticAberration);
|
||||
// m_Material.SetFloat("_NoiseStrength", m_Settings.NoiseStrength);
|
||||
// m_Material.SetFloat("_FlickerStrength", m_Settings.FlickerStrength);
|
||||
|
||||
// TextureHandle source = resourceData.activeColorTexture;
|
||||
// TextureDesc destinationDesc = renderGraph.GetTextureDesc(source);
|
||||
// destinationDesc.name = "CameraColor-CRT";
|
||||
// destinationDesc.clearBuffer = false;
|
||||
|
||||
// TextureHandle destination = renderGraph.CreateTexture(destinationDesc);
|
||||
// RenderGraphUtils.BlitMaterialParameters blitParams = new(source, destination, m_Material, 0);
|
||||
// renderGraph.AddBlitPass(blitParams, "CRT Effect");
|
||||
|
||||
// resourceData.cameraColor = destination;
|
||||
// }
|
||||
|
||||
// public void Dispose()
|
||||
// {
|
||||
// // RenderGraph path does not allocate persistent RTHandles in this pass.
|
||||
// }
|
||||
// }
|
||||
|
||||
// public CRTSettings Settings = new();
|
||||
|
||||
// private CRTPass m_Pass;
|
||||
// private Material m_Material;
|
||||
|
||||
// public override void Create()
|
||||
// {
|
||||
// if (Settings.CRTShader == null)
|
||||
// {
|
||||
// Settings.CRTShader = Shader.Find("Hidden/HeadlessHazard/CRT");
|
||||
// }
|
||||
|
||||
// if (Settings.CRTShader != null)
|
||||
// {
|
||||
// m_Material = CoreUtils.CreateEngineMaterial(Settings.CRTShader);
|
||||
// }
|
||||
|
||||
// m_Pass ??= new CRTPass();
|
||||
// }
|
||||
|
||||
// public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
|
||||
// {
|
||||
// if (m_Material == null || !Settings.EffectEnabled)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// if (renderingData.cameraData.cameraType != CameraType.Game)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// m_Pass.Setup(m_Material, Settings);
|
||||
// renderer.EnqueuePass(m_Pass);
|
||||
// }
|
||||
|
||||
// protected override void Dispose(bool disposing)
|
||||
// {
|
||||
// m_Pass?.Dispose();
|
||||
// m_Pass = null;
|
||||
|
||||
// CoreUtils.Destroy(m_Material);
|
||||
// m_Material = null;
|
||||
// }
|
||||
// }
|
||||
11
Assets/Code/Scripts/Rendering/CRTRendererFeature.cs.meta
Normal file
11
Assets/Code/Scripts/Rendering/CRTRendererFeature.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f2de7a6cfbd47c8bc740d43bb991205
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Code/Scripts/UI.meta
Normal file
8
Assets/Code/Scripts/UI.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e88664529cd503644b2b92f055895969
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
379
Assets/Code/Scripts/UI/MainMenuUI.cs
Normal file
379
Assets/Code/Scripts/UI/MainMenuUI.cs
Normal file
@@ -0,0 +1,379 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
using UnityEngine.InputSystem.UI;
|
||||
#endif
|
||||
|
||||
public class RetroMainMenuUI : MonoBehaviour
|
||||
{
|
||||
private Canvas m_MenuCanvas;
|
||||
private bool m_MenuActive;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
private static void Bootstrap()
|
||||
{
|
||||
if (Object.FindFirstObjectByType<RetroMainMenuUI>() != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject root = new("RetroMainMenuUI");
|
||||
root.AddComponent<RetroMainMenuUI>();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
m_MenuActive = true;
|
||||
Time.timeScale = 0f;
|
||||
ApplyMenuCursorState();
|
||||
|
||||
BuildMenu();
|
||||
EnsureEventSystem();
|
||||
|
||||
UnityEngine.SceneManagement.SceneManager.sceneLoaded += OnSceneLoaded;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
UnityEngine.SceneManagement.SceneManager.sceneLoaded -= OnSceneLoaded;
|
||||
}
|
||||
|
||||
private void OnSceneLoaded(UnityEngine.SceneManagement.Scene scene, UnityEngine.SceneManagement.LoadSceneMode mode)
|
||||
{
|
||||
// Check again when the scene finishes loading to remove any baked-in duplicate EventSystems
|
||||
EnsureEventSystem();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!m_MenuActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Some gameplay scripts lock the cursor during Start/Update.
|
||||
// Force menu cursor state while the menu is active.
|
||||
ApplyMenuCursorState();
|
||||
}
|
||||
|
||||
private void BuildMenu()
|
||||
{
|
||||
Color bgColor = HexToColor("001e26");
|
||||
Color panelColor = HexToColor("517567");
|
||||
Color titleColor = HexToColor("f3d58d");
|
||||
Color TextNormalColor = HexToColor("eb9843");
|
||||
Color textWarningColor = HexToColor("c12204");
|
||||
Color shadowColor = HexToColor("520805");
|
||||
|
||||
GameObject canvasObject = new("MainMenuCanvas");
|
||||
Canvas canvas = canvasObject.AddComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.sortingOrder = 10000;
|
||||
m_MenuCanvas = canvas;
|
||||
|
||||
CanvasScaler scaler = canvasObject.AddComponent<CanvasScaler>();
|
||||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
scaler.referenceResolution = new Vector2(1920f, 1080f);
|
||||
scaler.matchWidthOrHeight = 0.5f;
|
||||
|
||||
canvasObject.AddComponent<GraphicRaycaster>();
|
||||
|
||||
// Background
|
||||
GameObject background = CreateImage("Background", canvasObject.transform, bgColor);
|
||||
StretchToFull(background.GetComponent<RectTransform>());
|
||||
|
||||
// Decorative horizontal lines (scanline aesthetic)
|
||||
CreateLine("TopLine", background.transform, new Rect(0, -60, 0, 4), panelColor, AnchorPreset.TopStretch);
|
||||
CreateLine("BotLine", background.transform, new Rect(0, 60, 0, 4), panelColor, AnchorPreset.BottomStretch);
|
||||
|
||||
// --- LEFT PANEL ---
|
||||
GameObject leftPanel = new GameObject("LeftPanel", typeof(RectTransform));
|
||||
leftPanel.transform.SetParent(canvasObject.transform, false);
|
||||
RectTransform leftRect = leftPanel.GetComponent<RectTransform>();
|
||||
leftRect.anchorMin = new Vector2(0.08f, 0.1f);
|
||||
leftRect.anchorMax = new Vector2(0.45f, 0.9f);
|
||||
leftRect.offsetMin = Vector2.zero;
|
||||
leftRect.offsetMax = Vector2.zero;
|
||||
|
||||
// Title
|
||||
TextMeshProUGUI titleText = CreateTMP("Title", leftPanel.transform, "HEADLESS HAZARD", titleColor, 72, TextAlignmentOptions.BottomLeft);
|
||||
RectTransform titleRect = titleText.GetComponent<RectTransform>();
|
||||
titleRect.anchorMin = new Vector2(0f, 0.85f);
|
||||
titleRect.anchorMax = new Vector2(1f, 1f);
|
||||
titleRect.offsetMin = Vector2.zero;
|
||||
titleRect.offsetMax = Vector2.zero;
|
||||
titleText.fontStyle = FontStyles.Bold;
|
||||
|
||||
// Title Shadow
|
||||
TextMeshProUGUI titleShadow = CreateTMP("TitleShadow", leftPanel.transform, "HEADLESS HAZARD", shadowColor, 72, TextAlignmentOptions.BottomLeft);
|
||||
RectTransform shadowRect = titleShadow.GetComponent<RectTransform>();
|
||||
shadowRect.anchorMin = new Vector2(0f, 0.85f);
|
||||
shadowRect.anchorMax = new Vector2(1f, 1f);
|
||||
shadowRect.offsetMin = new Vector2(4f, -4f); // apply drop shadow offset
|
||||
shadowRect.offsetMax = new Vector2(4f, -4f);
|
||||
titleShadow.fontStyle = FontStyles.Bold;
|
||||
titleShadow.transform.SetSiblingIndex(0); // push behind title
|
||||
|
||||
// Subtitle / Decorative Status
|
||||
TextMeshProUGUI subText = CreateTMP("Subtitle", leftPanel.transform, "SYSTEM_BOOT // OS.ACTIVE_ ", panelColor, 20, TextAlignmentOptions.TopLeft);
|
||||
RectTransform subRect = subText.GetComponent<RectTransform>();
|
||||
subRect.anchorMin = new Vector2(0f, 0.80f);
|
||||
subRect.anchorMax = new Vector2(1f, 0.85f);
|
||||
subRect.offsetMin = Vector2.zero;
|
||||
subRect.offsetMax = Vector2.zero;
|
||||
|
||||
// Button Group
|
||||
GameObject buttonGroup = new("ButtonGroup", typeof(RectTransform), typeof(VerticalLayoutGroup));
|
||||
buttonGroup.transform.SetParent(leftPanel.transform, false);
|
||||
RectTransform groupRect = buttonGroup.GetComponent<RectTransform>();
|
||||
groupRect.anchorMin = new Vector2(0f, 0f);
|
||||
groupRect.anchorMax = new Vector2(1f, 0.65f);
|
||||
groupRect.offsetMin = Vector2.zero;
|
||||
groupRect.offsetMax = Vector2.zero;
|
||||
|
||||
VerticalLayoutGroup layout = buttonGroup.GetComponent<VerticalLayoutGroup>();
|
||||
layout.childAlignment = TextAnchor.UpperLeft;
|
||||
layout.spacing = 16f;
|
||||
layout.childControlWidth = true;
|
||||
layout.childControlHeight = false;
|
||||
|
||||
CreateTextButton(buttonGroup.transform, "> INITIALIZE_PLAY", TextNormalColor, titleColor, () =>
|
||||
{
|
||||
Debug.Log("Play clicked.");
|
||||
OnPlayClicked();
|
||||
});
|
||||
|
||||
CreateTextButton(buttonGroup.transform, "> CONFIGURE_PARAMS", TextNormalColor, titleColor, () =>
|
||||
{
|
||||
Debug.Log("Options clicked.");
|
||||
});
|
||||
|
||||
CreateTextButton(buttonGroup.transform, "> TERMINATE_PROCESS", TextNormalColor, textWarningColor, () =>
|
||||
{
|
||||
Debug.Log("Quit clicked.");
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.EditorApplication.isPlaying = false;
|
||||
#else
|
||||
Application.Quit();
|
||||
#endif
|
||||
});
|
||||
|
||||
|
||||
// --- RIGHT PANEL (Level Info) ---
|
||||
GameObject rightPanel = new GameObject("RightPanel", typeof(RectTransform));
|
||||
rightPanel.transform.SetParent(canvasObject.transform, false);
|
||||
RectTransform rightRect = rightPanel.GetComponent<RectTransform>();
|
||||
rightRect.anchorMin = new Vector2(0.55f, 0.4f);
|
||||
rightRect.anchorMax = new Vector2(0.92f, 0.82f);
|
||||
rightRect.offsetMin = Vector2.zero;
|
||||
rightRect.offsetMax = Vector2.zero;
|
||||
|
||||
// Right side Border lines
|
||||
CreateLine("R_Top", rightPanel.transform, new Rect(0, 0, 0, 2), panelColor, AnchorPreset.TopStretch);
|
||||
CreateLine("R_Bot", rightPanel.transform, new Rect(0, 0, 0, 2), panelColor, AnchorPreset.BottomStretch);
|
||||
CreateLine("R_Left", rightPanel.transform, new Rect(0, 0, 2, 0), panelColor, AnchorPreset.LeftStretch);
|
||||
CreateLine("R_Right", rightPanel.transform, new Rect(0, 0, 2, 0), panelColor, AnchorPreset.RightStretch);
|
||||
|
||||
// Right Panel Headers
|
||||
TextMeshProUGUI headerText = CreateTMP("LevelHeader", rightPanel.transform, "CURRENT_SECTOR", panelColor, 24, TextAlignmentOptions.TopLeft);
|
||||
headerText.GetComponent<RectTransform>().anchorMin = new Vector2(0f, 1f);
|
||||
headerText.GetComponent<RectTransform>().anchorMax = new Vector2(1f, 1f);
|
||||
headerText.GetComponent<RectTransform>().anchoredPosition = new Vector2(20f, -20f);
|
||||
|
||||
// Big Level Text
|
||||
TextMeshProUGUI levelText = CreateTMP("LevelNumber", rightPanel.transform, "LEVEL 01", textWarningColor, 140, TextAlignmentOptions.Center);
|
||||
StretchToFull(levelText.GetComponent<RectTransform>());
|
||||
levelText.fontStyle = FontStyles.Bold;
|
||||
|
||||
// Decorative status
|
||||
TextMeshProUGUI statusText = CreateTMP("LevelStatus", rightPanel.transform, "[ STATUS: OPTIMAL ]", panelColor, 24, TextAlignmentOptions.BottomRight);
|
||||
statusText.GetComponent<RectTransform>().anchorMin = new Vector2(0f, 0f);
|
||||
statusText.GetComponent<RectTransform>().anchorMax = new Vector2(1f, 0f);
|
||||
statusText.GetComponent<RectTransform>().anchoredPosition = new Vector2(-20f, 20f);
|
||||
}
|
||||
|
||||
private void OnPlayClicked()
|
||||
{
|
||||
m_MenuActive = false;
|
||||
Time.timeScale = 1f;
|
||||
Cursor.lockState = CursorLockMode.Locked;
|
||||
Cursor.visible = false;
|
||||
|
||||
if (m_MenuCanvas != null)
|
||||
{
|
||||
Destroy(m_MenuCanvas.gameObject);
|
||||
}
|
||||
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
private static GameObject CreateTextButton(
|
||||
Transform parent,
|
||||
string label,
|
||||
Color normalColor,
|
||||
Color highlightColor,
|
||||
UnityEngine.Events.UnityAction clickAction)
|
||||
{
|
||||
GameObject buttonObject = new(label, typeof(RectTransform), typeof(TextMeshProUGUI), typeof(Button));
|
||||
buttonObject.transform.SetParent(parent, false);
|
||||
|
||||
RectTransform rect = buttonObject.GetComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(0f, 60f); // Height 60, width auto-controlled by LayoutGroup
|
||||
|
||||
TextMeshProUGUI text = buttonObject.GetComponent<TextMeshProUGUI>();
|
||||
text.text = label;
|
||||
text.fontSize = 38;
|
||||
text.alignment = TextAlignmentOptions.Left;
|
||||
text.color = Color.white; // Button tint applies on top of white
|
||||
text.textWrappingMode = TextWrappingModes.NoWrap;
|
||||
|
||||
Button button = buttonObject.GetComponent<Button>();
|
||||
button.targetGraphic = text;
|
||||
button.transition = Selectable.Transition.ColorTint;
|
||||
|
||||
ColorBlock colors = button.colors;
|
||||
colors.normalColor = normalColor;
|
||||
colors.highlightedColor = highlightColor;
|
||||
colors.pressedColor = highlightColor;
|
||||
colors.selectedColor = highlightColor;
|
||||
colors.disabledColor = Color.gray;
|
||||
colors.colorMultiplier = 1f;
|
||||
colors.fadeDuration = 0.1f;
|
||||
button.colors = colors;
|
||||
|
||||
button.onClick.AddListener(clickAction);
|
||||
|
||||
return buttonObject;
|
||||
}
|
||||
|
||||
private static TextMeshProUGUI CreateTMP(string name, Transform parent, string textStr, Color color, float size, TextAlignmentOptions align)
|
||||
{
|
||||
GameObject go = new GameObject(name, typeof(RectTransform), typeof(TextMeshProUGUI));
|
||||
go.transform.SetParent(parent, false);
|
||||
TextMeshProUGUI tmp = go.GetComponent<TextMeshProUGUI>();
|
||||
tmp.text = textStr;
|
||||
tmp.color = color;
|
||||
tmp.fontSize = size;
|
||||
tmp.alignment = align;
|
||||
tmp.textWrappingMode = TextWrappingModes.NoWrap;
|
||||
return tmp;
|
||||
}
|
||||
|
||||
enum AnchorPreset { TopStretch, BottomStretch, LeftStretch, RightStretch }
|
||||
private static GameObject CreateLine(string name, Transform parent, Rect details, Color color, AnchorPreset preset)
|
||||
{
|
||||
GameObject line = CreateImage(name, parent, color);
|
||||
RectTransform rect = line.GetComponent<RectTransform>();
|
||||
|
||||
switch (preset)
|
||||
{
|
||||
case AnchorPreset.TopStretch:
|
||||
rect.anchorMin = new Vector2(0, 1);
|
||||
rect.anchorMax = new Vector2(1, 1);
|
||||
rect.sizeDelta = new Vector2(details.width, details.height);
|
||||
rect.anchoredPosition = new Vector2(details.x, details.y);
|
||||
break;
|
||||
case AnchorPreset.BottomStretch:
|
||||
rect.anchorMin = new Vector2(0, 0);
|
||||
rect.anchorMax = new Vector2(1, 0);
|
||||
rect.sizeDelta = new Vector2(details.width, details.height);
|
||||
rect.anchoredPosition = new Vector2(details.x, details.y);
|
||||
break;
|
||||
case AnchorPreset.LeftStretch:
|
||||
rect.anchorMin = new Vector2(0, 0);
|
||||
rect.anchorMax = new Vector2(0, 1);
|
||||
rect.sizeDelta = new Vector2(details.width, details.height);
|
||||
rect.anchoredPosition = new Vector2(details.x, details.y);
|
||||
break;
|
||||
case AnchorPreset.RightStretch:
|
||||
rect.anchorMin = new Vector2(1, 0);
|
||||
rect.anchorMax = new Vector2(1, 1);
|
||||
rect.sizeDelta = new Vector2(details.width, details.height);
|
||||
rect.anchoredPosition = new Vector2(details.x, details.y);
|
||||
break;
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
private static GameObject CreateImage(string name, Transform parent, Color color)
|
||||
{
|
||||
GameObject imageObject = new(name, typeof(RectTransform), typeof(Image));
|
||||
imageObject.transform.SetParent(parent, false);
|
||||
Image image = imageObject.GetComponent<Image>();
|
||||
image.color = color;
|
||||
return imageObject;
|
||||
}
|
||||
|
||||
private static void StretchToFull(RectTransform rect)
|
||||
{
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.offsetMin = Vector2.zero;
|
||||
rect.offsetMax = Vector2.zero;
|
||||
}
|
||||
|
||||
private static Color HexToColor(string hex)
|
||||
{
|
||||
if (ColorUtility.TryParseHtmlString("#" + hex, out Color color))
|
||||
{
|
||||
return color;
|
||||
}
|
||||
|
||||
return Color.magenta;
|
||||
}
|
||||
|
||||
private static void ApplyMenuCursorState()
|
||||
{
|
||||
Cursor.lockState = CursorLockMode.None;
|
||||
Cursor.visible = true;
|
||||
}
|
||||
|
||||
private static void EnsureEventSystem()
|
||||
{
|
||||
EventSystem[] allEventSystems = Object.FindObjectsByType<EventSystem>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||||
EventSystem eventSystem;
|
||||
if (allEventSystems.Length == 0)
|
||||
{
|
||||
GameObject eventSystemObject = new("EventSystem", typeof(EventSystem));
|
||||
eventSystem = eventSystemObject.GetComponent<EventSystem>();
|
||||
}
|
||||
else
|
||||
{
|
||||
eventSystem = allEventSystems[0];
|
||||
|
||||
for (int i = 1; i < allEventSystems.Length; i++)
|
||||
{
|
||||
if (allEventSystems[i] != null)
|
||||
{
|
||||
Destroy(allEventSystems[i].gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EnsureCompatibleInputModule(eventSystem.gameObject);
|
||||
}
|
||||
|
||||
private static void EnsureCompatibleInputModule(GameObject eventSystemObject)
|
||||
{
|
||||
#if ENABLE_INPUT_SYSTEM
|
||||
StandaloneInputModule standaloneModule = eventSystemObject.GetComponent<StandaloneInputModule>();
|
||||
if (standaloneModule != null)
|
||||
{
|
||||
Destroy(standaloneModule);
|
||||
}
|
||||
|
||||
if (eventSystemObject.GetComponent<InputSystemUIInputModule>() == null)
|
||||
{
|
||||
eventSystemObject.AddComponent<InputSystemUIInputModule>();
|
||||
}
|
||||
#else
|
||||
if (eventSystemObject.GetComponent<StandaloneInputModule>() == null)
|
||||
{
|
||||
eventSystemObject.AddComponent<StandaloneInputModule>();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
2
Assets/Code/Scripts/UI/MainMenuUI.cs.meta
Normal file
2
Assets/Code/Scripts/UI/MainMenuUI.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 297533e46238b814989fcd5d46cf8927
|
||||
Reference in New Issue
Block a user