Files
Test-Multiplayer/Assets/Scripts/RiveNavigationController.cs
pelpanagiotisandClaude Opus 5.5 5fc9ebf1e6 Add practice range: shooting, AK47, intro cinematic, dressed warehouse scene
- PortalTestDestination practice range: Valorant-style hitscan rifle (recoil,
  spray/bloom, reload, controller support), debug HUD, practice-dummy targets
  with head/body hitboxes, hit/death/respawn animations, procedural SFX/FX
- AK47 model (Quaternius, CC0) held via Animator IK, with fire/reload/equip anims
- Intro cinematic (camera shots, title card, target beam-in, GO hand-off)
- Scene dressing editor pass: CC0 textures, props, warehouse roof, lighting and
  post-processing; custom stylized skybox shader
- CharacterMovement: camera recoil, shoulder offset, cinematic control lock
- Unity CLI pipeline package; plus in-progress chat/Rive/player changes

Asset credits: Assets/Models/Props/PROPS_CREDITS.txt (3 CC-BY 3.0 props),
Assets/Textures/Range/TEXTURES_LICENSE.txt, Assets/Resources/*/..._LICENSE.txt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:56:35 +03:00

150 lines
5.0 KiB
C#

using ProceduralEditor;
using Rive;
using Rive.Components;
using System.Reflection.Metadata;
using UnityEngine;
using UnityEngine.InputSystem;
/// Sends keyboard and gamepad navigation commands to the Rive menu and
/// activates the Play option when Enter/gamepad confirm is pressed
public class RiveNavigationController : MonoBehaviour
{
[Header("Rive")]
[Tooltip("RiveWidget that hosts the menu artboard.")]
[SerializeField] private RiveWidget riveWidget;
[Header("Scene Transition")]
[Tooltip("Object containing the SceneTransitionController component.")]
[SerializeField] private SceneTransitionController sceneTransitionController;
[Header("Menu Selection")]
[Tooltip("Total number of selectable Rive menu buttons.")]
[Min(1)]
[SerializeField] private int menuItemCount = 1;
private const string NextTriggerName = "nextButtonClicked";
private const string PreviousTriggerName = "previousButtonClicked";
private const int PlayIndex = 0;
private ViewModelInstanceTriggerProperty nextTrigger;
private ViewModelInstanceTriggerProperty previousTrigger;
private int selectedIndex;
private void OnEnable()
{
if (riveWidget == null)
{
Debug.LogError($"{nameof(RiveNavigationController)}: riveWidget is not assigned.");
return;
}
if (sceneTransitionController == null)
{
Debug.LogError($"{nameof(RiveNavigationController)}: SceneTransitionController is not assigned.");
}
riveWidget.OnWidgetStatusChanged += HandleWidgetStatusChanged;
// In case the widget already finished loading before we subscribed.
HandleWidgetStatusChanged();
}
private void OnDisable()
{
if (riveWidget != null)
riveWidget.OnWidgetStatusChanged -= HandleWidgetStatusChanged;
}
private void HandleWidgetStatusChanged()
{
if (riveWidget.Status != WidgetStatus.Loaded)
return;
ViewModelInstance viewModelInstance = riveWidget.StateMachine?.ViewModelInstance;
if (viewModelInstance == null)
{
Debug.LogError($"{nameof(RiveNavigationController)}: ViewModelInstance is null. " +
"Set Data Binding Mode to Auto Bind Default on the Rive Widget.");
return;
}
// Cache both triggers once the artboard is ready so Update() doesn't do
// string lookups every frame.
nextTrigger = viewModelInstance.GetTriggerProperty(NextTriggerName);
previousTrigger = viewModelInstance.GetTriggerProperty(PreviousTriggerName);
if (nextTrigger == null)
Debug.LogError($"{nameof(RiveNavigationController)}: Trigger '{NextTriggerName}' not found on view model.");
if (previousTrigger == null)
Debug.LogError($"{nameof(RiveNavigationController)}: Trigger '{PreviousTriggerName}' not found on view model.");
}
private void Update()
{
Keyboard keyboard = Keyboard.current;
Gamepad gamepad = Gamepad.current;
bool movedRight = false;
bool movedLeft = false;
bool confirmPressed = false;
if (keyboard != null)
{
movedRight |= keyboard.rightArrowKey.wasPressedThisFrame;
movedLeft |= keyboard.leftArrowKey.wasPressedThisFrame;
confirmPressed |= keyboard.enterKey.wasPressedThisFrame || keyboard.numpadEnterKey.wasPressedThisFrame;
}
if (gamepad != null)
{
// D-pad and left stick both move the selection — leftStick.left/right are the
// Input System's built-in synthetic buttons derived from the stick's deflection,
// so this fires once per push just like a button, not continuously while held.
movedRight |= gamepad.dpad.right.wasPressedThisFrame || gamepad.leftStick.right.wasPressedThisFrame;
movedLeft |= gamepad.dpad.left.wasPressedThisFrame || gamepad.leftStick.left.wasPressedThisFrame;
confirmPressed |= gamepad.buttonSouth.wasPressedThisFrame;
}
if (movedRight)
{
MoveSelection(+1);
nextTrigger?.Trigger();
}
if (movedLeft)
{
MoveSelection(-1);
previousTrigger?.Trigger();
}
if (confirmPressed && selectedIndex == PlayIndex)
{
if (sceneTransitionController == null)
{
Debug.LogError($"{nameof(RiveNavigationController)}: SceneTransitionController is not assigned.");
return;
}
sceneTransitionController.LoadGameplayScene();
}
}
private void MoveSelection(int direction)
{
if (menuItemCount <= 0)
return;
selectedIndex += direction;
if (selectedIndex >= menuItemCount)
selectedIndex = 0;
if (selectedIndex < 0)
selectedIndex = menuItemCount - 1;
Debug.Log($"Rive menu selection index: {selectedIndex}");
}
}