From ab434be65fec1e7bb3399cd1687af5824c8fcfd5 Mon Sep 17 00:00:00 2001 From: Thibault Pouch Date: Fri, 13 Mar 2026 18:38:48 +0100 Subject: [PATCH 1/7] feat : add a cathodic efect on the player cam --- Assets/Code/Scripts/Rendering.meta | 8 + .../Scripts/Rendering/CRTRendererFeature.cs | 173 ++++++++++++++++++ .../Rendering/CRTRendererFeature.cs.meta | 11 ++ Assets/Code/Shaders/CRTScreenEffect.shader | 97 ++++++++++ .../Code/Shaders/CRTScreenEffect.shader.meta | 10 + Assets/Settings/PC_Renderer.asset | 72 +++++--- 6 files changed, 342 insertions(+), 29 deletions(-) create mode 100644 Assets/Code/Scripts/Rendering.meta create mode 100644 Assets/Code/Scripts/Rendering/CRTRendererFeature.cs create mode 100644 Assets/Code/Scripts/Rendering/CRTRendererFeature.cs.meta create mode 100644 Assets/Code/Shaders/CRTScreenEffect.shader create mode 100644 Assets/Code/Shaders/CRTScreenEffect.shader.meta diff --git a/Assets/Code/Scripts/Rendering.meta b/Assets/Code/Scripts/Rendering.meta new file mode 100644 index 0000000..3510470 --- /dev/null +++ b/Assets/Code/Scripts/Rendering.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 97a4ae8015df4732ac9524441048a765 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Code/Scripts/Rendering/CRTRendererFeature.cs b/Assets/Code/Scripts/Rendering/CRTRendererFeature.cs new file mode 100644 index 0000000..4f708bd --- /dev/null +++ b/Assets/Code/Scripts/Rendering/CRTRendererFeature.cs @@ -0,0 +1,173 @@ +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 readonly ProfilingSampler m_ProfilingSampler = new("CRT Effect"); + + private Material m_Material; + private CRTSettings m_Settings; + private RTHandle m_TempColorTexture; + + 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(); + 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 override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) + { + cameraTextureDescriptor.depthBufferBits = 0; + RenderingUtils.ReAllocateHandleIfNeeded( + ref m_TempColorTexture, + cameraTextureDescriptor, + FilterMode.Bilinear, + TextureWrapMode.Clamp, + name: "_CRTTempColorTexture" + ); + } + + public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) + { + if (m_Material == null || m_Settings == null || !m_Settings.EffectEnabled) + { + return; + } + + if (renderingData.cameraData.isSceneViewCamera) + { + return; + } + + CommandBuffer cmd = CommandBufferPool.Get(); + + using (new ProfilingScope(cmd, m_ProfilingSampler)) + { + 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); + + RTHandle source = renderingData.cameraData.renderer.cameraColorTargetHandle; + Blitter.BlitCameraTexture(cmd, source, m_TempColorTexture, m_Material, 0); + Blitter.BlitCameraTexture(cmd, m_TempColorTexture, source); + } + + context.ExecuteCommandBuffer(cmd); + CommandBufferPool.Release(cmd); + } + + public void Dispose() + { + m_TempColorTexture?.Release(); + m_TempColorTexture = null; + } + } + + 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; + } +} diff --git a/Assets/Code/Scripts/Rendering/CRTRendererFeature.cs.meta b/Assets/Code/Scripts/Rendering/CRTRendererFeature.cs.meta new file mode 100644 index 0000000..7bcc2e6 --- /dev/null +++ b/Assets/Code/Scripts/Rendering/CRTRendererFeature.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4f2de7a6cfbd47c8bc740d43bb991205 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Code/Shaders/CRTScreenEffect.shader b/Assets/Code/Shaders/CRTScreenEffect.shader new file mode 100644 index 0000000..8f55a39 --- /dev/null +++ b/Assets/Code/Shaders/CRTScreenEffect.shader @@ -0,0 +1,97 @@ +Shader "Hidden/HeadlessHazard/CRT" +{ + Properties + { + _Intensity ("Intensity", Range(0,1)) = 0.65 + _ScanlineDensity ("Scanline Density", Range(0,2)) = 1.2 + _ScanlineStrength ("Scanline Strength", Range(0,1)) = 0.18 + _Curvature ("Curvature", Range(0,0.2)) = 0.04 + _VignetteStrength ("Vignette Strength", Range(0,1)) = 0.28 + _ChromaticAberration ("Chromatic Aberration", Range(0,0.05)) = 0.004 + _NoiseStrength ("Noise Strength", Range(0,0.2)) = 0.03 + _FlickerStrength ("Flicker Strength", Range(0,0.1)) = 0.015 + } + + SubShader + { + Tags { "RenderPipeline" = "UniversalPipeline" } + + Pass + { + Name "CRT" + ZWrite Off + ZTest Always + Cull Off + Blend One Zero + + HLSLPROGRAM + #pragma vertex Vert + #pragma fragment Frag + #pragma target 3.5 + + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" + #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl" + + float _Intensity; + float _ScanlineDensity; + float _ScanlineStrength; + float _Curvature; + float _VignetteStrength; + float _ChromaticAberration; + float _NoiseStrength; + float _FlickerStrength; + + float Random01(float2 seed) + { + return frac(sin(dot(seed, float2(12.9898, 78.233))) * 43758.5453); + } + + float2 DistortUV(float2 uv, float curvature) + { + float2 center = uv * 2.0 - 1.0; + float radius2 = dot(center, center); + center *= 1.0 + (radius2 * curvature); + return center * 0.5 + 0.5; + } + + half4 Frag(Varyings input) : SV_Target + { + float2 uv = input.texcoord; + float2 curvedUV = DistortUV(uv, _Curvature); + + if (curvedUV.x < 0.0 || curvedUV.x > 1.0 || curvedUV.y < 0.0 || curvedUV.y > 1.0) + { + return half4(0.0, 0.0, 0.0, 1.0); + } + + float2 fromCenter = curvedUV - 0.5; + float2 aberrationOffset = fromCenter * _ChromaticAberration; + + half red = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, curvedUV + aberrationOffset).r; + half green = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, curvedUV).g; + half blue = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, curvedUV - aberrationOffset).b; + half3 color = half3(red, green, blue); + + float scanlineWave = sin((curvedUV.y * _ScreenParams.y * 0.5 * _ScanlineDensity) + (_Time.y * 18.0)); + float scanlineMask = lerp(1.0, saturate(0.7 + 0.3 * scanlineWave), _ScanlineStrength); + color *= scanlineMask; + + float noise = Random01(curvedUV * _ScreenParams.xy + _Time.yy * 37.0) - 0.5; + color += noise * _NoiseStrength; + + float flicker = 1.0 - (_FlickerStrength * (0.5 + 0.5 * sin(_Time.y * 32.0))); + color *= flicker; + + float2 vignetteUV = curvedUV * (1.0 - curvedUV.yx); + float vignette = saturate(pow(vignetteUV.x * vignetteUV.y * 18.0, 0.2)); + color *= lerp(1.0 - _VignetteStrength, 1.0, vignette); + + half3 baseColor = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_LinearClamp, uv).rgb; + half3 finalColor = lerp(baseColor, color, _Intensity); + + return half4(finalColor, 1.0); + } + ENDHLSL + } + } +} diff --git a/Assets/Code/Shaders/CRTScreenEffect.shader.meta b/Assets/Code/Shaders/CRTScreenEffect.shader.meta new file mode 100644 index 0000000..6936b44 --- /dev/null +++ b/Assets/Code/Shaders/CRTScreenEffect.shader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 0a9f7eb85c2f4f9f8ec82c8565f4e8b1 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/PC_Renderer.asset b/Assets/Settings/PC_Renderer.asset index 475b02e..c110d16 100644 --- a/Assets/Settings/PC_Renderer.asset +++ b/Assets/Settings/PC_Renderer.asset @@ -1,5 +1,30 @@ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: +--- !u!114 &-4377071725885749089 +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: 4f2de7a6cfbd47c8bc740d43bb991205, type: 3} + m_Name: CRTRendererFeature + m_EditorClassIdentifier: Assembly-CSharp::CRTRendererFeature + m_Active: 1 + Settings: + EffectEnabled: 1 + PassEvent: 600 + CRTShader: {fileID: 4800000, guid: 0a9f7eb85c2f4f9f8ec82c8565f4e8b1, type: 3} + Intensity: 0.65 + ScanlineDensity: 1.2 + ScanlineStrength: 0.18 + Curvature: 0.04 + VignetteStrength: 0.28 + ChromaticAberration: 0.004 + NoiseStrength: 0.03 + FlickerStrength: 0.015 --- !u!114 &11400000 MonoBehaviour: m_ObjectHideFlags: 0 @@ -13,32 +38,28 @@ MonoBehaviour: m_Name: PC_Renderer m_EditorClassIdentifier: debugShaders: - debugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7, - type: 3} + debugReplacementPS: {fileID: 4800000, guid: cf852408f2e174538bcd9b7fda1c5ae7, type: 3} hdrDebugViewPS: {fileID: 4800000, guid: 573620ae32aec764abd4d728906d2587, type: 3} - probeVolumeSamplingDebugComputeShader: {fileID: 7200000, guid: 53626a513ea68ce47b59dc1299fe3959, - type: 3} + probeVolumeSamplingDebugComputeShader: {fileID: 7200000, guid: 53626a513ea68ce47b59dc1299fe3959, type: 3} probeVolumeResources: - probeVolumeDebugShader: {fileID: 4800000, guid: e5c6678ed2aaa91408dd3df699057aae, - type: 3} - probeVolumeFragmentationDebugShader: {fileID: 4800000, guid: 03cfc4915c15d504a9ed85ecc404e607, - type: 3} - probeVolumeOffsetDebugShader: {fileID: 4800000, guid: 53a11f4ebaebf4049b3638ef78dc9664, - type: 3} - probeVolumeSamplingDebugShader: {fileID: 4800000, guid: 8f96cd657dc40064aa21efcc7e50a2e7, - type: 3} - probeSamplingDebugMesh: {fileID: -3555484719484374845, guid: 57d7c4c16e2765b47a4d2069b311bffe, - type: 3} - probeSamplingDebugTexture: {fileID: 2800000, guid: 24ec0e140fb444a44ab96ee80844e18e, - type: 3} - probeVolumeBlendStatesCS: {fileID: 7200000, guid: b9a23f869c4fd45f19c5ada54dd82176, - type: 3} + probeVolumeDebugShader: {fileID: 4800000, guid: e5c6678ed2aaa91408dd3df699057aae, type: 3} + probeVolumeFragmentationDebugShader: {fileID: 4800000, guid: 03cfc4915c15d504a9ed85ecc404e607, type: 3} + probeVolumeOffsetDebugShader: {fileID: 4800000, guid: 53a11f4ebaebf4049b3638ef78dc9664, type: 3} + probeVolumeSamplingDebugShader: {fileID: 4800000, guid: 8f96cd657dc40064aa21efcc7e50a2e7, type: 3} + probeSamplingDebugMesh: {fileID: -3555484719484374845, guid: 57d7c4c16e2765b47a4d2069b311bffe, type: 3} + probeSamplingDebugTexture: {fileID: 2800000, guid: 24ec0e140fb444a44ab96ee80844e18e, type: 3} + probeVolumeBlendStatesCS: {fileID: 7200000, guid: b9a23f869c4fd45f19c5ada54dd82176, type: 3} m_RendererFeatures: - {fileID: 7833122117494664109} - m_RendererFeatureMap: ad6b866f10d7b46c + - {fileID: -4377071725885749089} + m_RendererFeatureMap: ad6b866f10d7b46c9f882cbe748441c3 m_UseNativeRenderPass: 1 + xrSystemData: {fileID: 0} postProcessData: {fileID: 11400000, guid: 41439944d30ece34e96484bdb6645b55, type: 2} - m_AssetVersion: 2 + m_AssetVersion: 3 + m_PrepassLayerMask: + serializedVersion: 2 + m_Bits: 4294967295 m_OpaqueLayerMask: serializedVersion: 2 m_Bits: 4294967295 @@ -56,6 +77,8 @@ MonoBehaviour: m_RenderingMode: 2 m_DepthPrimingMode: 0 m_CopyDepthMode: 0 + m_DepthAttachmentFormat: 0 + m_DepthTextureFormat: 0 m_AccurateGbufferNormals: 0 m_IntermediateTextureMode: 0 --- !u!114 &7833122117494664109 @@ -84,12 +107,3 @@ MonoBehaviour: BlurQuality: 0 Falloff: 100 SampleCount: -1 - m_BlueNoise256Textures: - - {fileID: 2800000, guid: 36f118343fc974119bee3d09e2111500, type: 3} - - {fileID: 2800000, guid: 4b7b083e6b6734e8bb2838b0b50a0bc8, type: 3} - - {fileID: 2800000, guid: c06cc21c692f94f5fb5206247191eeee, type: 3} - - {fileID: 2800000, guid: cb76dd40fa7654f9587f6a344f125c9a, type: 3} - - {fileID: 2800000, guid: e32226222ff144b24bf3a5a451de54bc, type: 3} - - {fileID: 2800000, guid: 3302065f671a8450b82c9ddf07426f3a, type: 3} - - {fileID: 2800000, guid: 56a77a3e8d64f47b6afe9e3c95cb57d5, type: 3} - m_Shader: {fileID: 4800000, guid: 0849e84e3d62649e8882e9d6f056a017, type: 3} From f42463176f3156e7b1d2f96c19337f1f95d7b8a4 Mon Sep 17 00:00:00 2001 From: Thibault Pouch Date: Fri, 13 Mar 2026 18:58:15 +0100 Subject: [PATCH 2/7] feat: add robot boot sequence with input control management --- .../Code/Scripts/Player/PlayerInputHandler.cs | 17 ++ .../Code/Scripts/Player/RobotBootSequence.cs | 264 ++++++++++++++++++ .../Scripts/Player/RobotBootSequence.cs.meta | 11 + 3 files changed, 292 insertions(+) create mode 100644 Assets/Code/Scripts/Player/RobotBootSequence.cs create mode 100644 Assets/Code/Scripts/Player/RobotBootSequence.cs.meta diff --git a/Assets/Code/Scripts/Player/PlayerInputHandler.cs b/Assets/Code/Scripts/Player/PlayerInputHandler.cs index b768a2d..5ca9d8d 100644 --- a/Assets/Code/Scripts/Player/PlayerInputHandler.cs +++ b/Assets/Code/Scripts/Player/PlayerInputHandler.cs @@ -4,6 +4,7 @@ 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; @@ -44,6 +45,17 @@ public class PlayerInputController : MonoBehaviour private void Update() { + if (!InputEnabled) + { + MoveAmount = Vector2.zero; + LookAmount = Vector2.zero; + ShiftPressed = false; + JumpPressed = false; + ThrowPressed = false; + HeadInteractionPressed = false; + return; + } + MoveAmount = m_moveAction.ReadValue(); LookAmount = m_lookAction.ReadValue(); @@ -52,4 +64,9 @@ public class PlayerInputController : MonoBehaviour ThrowPressed = m_throwAction.WasPressedThisFrame(); HeadInteractionPressed = m_headInteractAction.WasPressedThisFrame(); } + + public void SetInputEnabled(bool enabled) + { + InputEnabled = enabled; + } } \ No newline at end of file diff --git a/Assets/Code/Scripts/Player/RobotBootSequence.cs b/Assets/Code/Scripts/Player/RobotBootSequence.cs new file mode 100644 index 0000000..1b43a00 --- /dev/null +++ b/Assets/Code/Scripts/Player/RobotBootSequence.cs @@ -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(); + } + } + + 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(); + 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.renderMode = RenderMode.ScreenSpaceOverlay; + canvas.sortingOrder = 5000; + + CanvasScaler scaler = canvasGO.GetComponent(); + scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; + scaler.referenceResolution = new Vector2(1920f, 1080f); + scaler.matchWidthOrHeight = 0.5f; + + RectTransform root = canvasGO.GetComponent(); + 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(); + 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.color = Color.black; + + return rect; + } + + private TextMeshProUGUI CreateBootLabel(RectTransform parent) + { + GameObject textGO = new GameObject("BootText", typeof(RectTransform), typeof(TextMeshProUGUI)); + RectTransform rect = textGO.GetComponent(); + 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(); + text.text = string.Empty; + text.fontSize = 40f; + text.alignment = TextAlignmentOptions.Left; + text.color = BootTextColor; + text.enableWordWrapping = true; + + return text; + } +} diff --git a/Assets/Code/Scripts/Player/RobotBootSequence.cs.meta b/Assets/Code/Scripts/Player/RobotBootSequence.cs.meta new file mode 100644 index 0000000..e0ad01c --- /dev/null +++ b/Assets/Code/Scripts/Player/RobotBootSequence.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6ef6855cd57b4f94b47f410d47e89ff1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 64b2d637997eebf679c9b0bb2f2846e628eff862 Mon Sep 17 00:00:00 2001 From: Thibault Pouch Date: Fri, 13 Mar 2026 19:28:03 +0100 Subject: [PATCH 3/7] refactor: update text wrapping mode in RobotBootSequence and clean up CRTRendererFeature --- .../Code/Scripts/Player/RobotBootSequence.cs | 2 +- .../Scripts/Rendering/CRTRendererFeature.cs | 52 +------------------ 2 files changed, 2 insertions(+), 52 deletions(-) diff --git a/Assets/Code/Scripts/Player/RobotBootSequence.cs b/Assets/Code/Scripts/Player/RobotBootSequence.cs index 1b43a00..09f9610 100644 --- a/Assets/Code/Scripts/Player/RobotBootSequence.cs +++ b/Assets/Code/Scripts/Player/RobotBootSequence.cs @@ -257,7 +257,7 @@ public class RobotBootSequence : MonoBehaviour text.fontSize = 40f; text.alignment = TextAlignmentOptions.Left; text.color = BootTextColor; - text.enableWordWrapping = true; + text.textWrappingMode = TextWrappingModes.Normal; return text; } diff --git a/Assets/Code/Scripts/Rendering/CRTRendererFeature.cs b/Assets/Code/Scripts/Rendering/CRTRendererFeature.cs index 4f708bd..662a5c0 100644 --- a/Assets/Code/Scripts/Rendering/CRTRendererFeature.cs +++ b/Assets/Code/Scripts/Rendering/CRTRendererFeature.cs @@ -25,11 +25,8 @@ public class CRTRendererFeature : ScriptableRendererFeature class CRTPass : ScriptableRenderPass { - private readonly ProfilingSampler m_ProfilingSampler = new("CRT Effect"); - private Material m_Material; private CRTSettings m_Settings; - private RTHandle m_TempColorTexture; public void Setup(Material material, CRTSettings settings) { @@ -73,56 +70,9 @@ public class CRTRendererFeature : ScriptableRendererFeature resourceData.cameraColor = destination; } - public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) - { - cameraTextureDescriptor.depthBufferBits = 0; - RenderingUtils.ReAllocateHandleIfNeeded( - ref m_TempColorTexture, - cameraTextureDescriptor, - FilterMode.Bilinear, - TextureWrapMode.Clamp, - name: "_CRTTempColorTexture" - ); - } - - public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) - { - if (m_Material == null || m_Settings == null || !m_Settings.EffectEnabled) - { - return; - } - - if (renderingData.cameraData.isSceneViewCamera) - { - return; - } - - CommandBuffer cmd = CommandBufferPool.Get(); - - using (new ProfilingScope(cmd, m_ProfilingSampler)) - { - 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); - - RTHandle source = renderingData.cameraData.renderer.cameraColorTargetHandle; - Blitter.BlitCameraTexture(cmd, source, m_TempColorTexture, m_Material, 0); - Blitter.BlitCameraTexture(cmd, m_TempColorTexture, source); - } - - context.ExecuteCommandBuffer(cmd); - CommandBufferPool.Release(cmd); - } - public void Dispose() { - m_TempColorTexture?.Release(); - m_TempColorTexture = null; + // RenderGraph path does not allocate persistent RTHandles in this pass. } } From 53fd617abedc35231a94a863689822bd9468a8d8 Mon Sep 17 00:00:00 2001 From: Thibault Pouch Date: Fri, 13 Mar 2026 19:28:19 +0100 Subject: [PATCH 4/7] feat: add RetroMainMenuUI script and associated meta file for main menu functionality --- Assets/Code/Scripts/UI.meta | 8 + Assets/Code/Scripts/UI/RetroMainMenuUI.cs | 363 ++++++++++++++++++ .../Code/Scripts/UI/RetroMainMenuUI.cs.meta | 2 + 3 files changed, 373 insertions(+) create mode 100644 Assets/Code/Scripts/UI.meta create mode 100644 Assets/Code/Scripts/UI/RetroMainMenuUI.cs create mode 100644 Assets/Code/Scripts/UI/RetroMainMenuUI.cs.meta diff --git a/Assets/Code/Scripts/UI.meta b/Assets/Code/Scripts/UI.meta new file mode 100644 index 0000000..a85f6a0 --- /dev/null +++ b/Assets/Code/Scripts/UI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e88664529cd503644b2b92f055895969 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Code/Scripts/UI/RetroMainMenuUI.cs b/Assets/Code/Scripts/UI/RetroMainMenuUI.cs new file mode 100644 index 0000000..7350d09 --- /dev/null +++ b/Assets/Code/Scripts/UI/RetroMainMenuUI.cs @@ -0,0 +1,363 @@ +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() != null) + { + return; + } + + GameObject root = new("RetroMainMenuUI"); + root.AddComponent(); + } + + private void Awake() + { + m_MenuActive = true; + Time.timeScale = 0f; + ApplyMenuCursorState(); + + BuildMenu(); + 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.renderMode = RenderMode.ScreenSpaceOverlay; + canvas.sortingOrder = 10000; + m_MenuCanvas = canvas; + + CanvasScaler scaler = canvasObject.AddComponent(); + scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; + scaler.referenceResolution = new Vector2(1920f, 1080f); + scaler.matchWidthOrHeight = 0.5f; + + canvasObject.AddComponent(); + + // Background + GameObject background = CreateImage("Background", canvasObject.transform, bgColor); + StretchToFull(background.GetComponent()); + + // 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(); + 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(); + 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(); + 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(); + 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(); + 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(); + 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."); + Application.Quit(); + }); + + + // --- RIGHT PANEL (Level Info) --- + GameObject rightPanel = new GameObject("RightPanel", typeof(RectTransform)); + rightPanel.transform.SetParent(canvasObject.transform, false); + RectTransform rightRect = rightPanel.GetComponent(); + 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().anchorMin = new Vector2(0f, 1f); + headerText.GetComponent().anchorMax = new Vector2(1f, 1f); + headerText.GetComponent().anchoredPosition = new Vector2(20f, -20f); + + // Big Level Text + TextMeshProUGUI levelText = CreateTMP("LevelNumber", rightPanel.transform, "LEVEL 01", textWarningColor, 140, TextAlignmentOptions.Center); + StretchToFull(levelText.GetComponent()); + levelText.fontStyle = FontStyles.Bold; + + // Decorative status + TextMeshProUGUI statusText = CreateTMP("LevelStatus", rightPanel.transform, "[ STATUS: OPTIMAL ]", panelColor, 24, TextAlignmentOptions.BottomRight); + statusText.GetComponent().anchorMin = new Vector2(0f, 0f); + statusText.GetComponent().anchorMax = new Vector2(1f, 0f); + statusText.GetComponent().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(); + rect.sizeDelta = new Vector2(0f, 60f); // Height 60, width auto-controlled by LayoutGroup + + TextMeshProUGUI text = buttonObject.GetComponent(); + 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