first push!
This commit is contained in:
234
Assets/Scripts/Input/RowingInputManager.cs
Normal file
234
Assets/Scripts/Input/RowingInputManager.cs
Normal file
@@ -0,0 +1,234 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class RowingInputManager : MonoBehaviour
|
||||
{
|
||||
public static RowingInputManager Instance;
|
||||
|
||||
public enum InputSource { LivePM5, Recorded, Simulated }
|
||||
|
||||
[Header("Data Source")]
|
||||
public InputSource currentInputSource = InputSource.LivePM5;
|
||||
|
||||
[Header("Recorded Mode Controls")]
|
||||
public List<RowingSessionData> recordedSessions;
|
||||
public int currentSessionIndex = 0;
|
||||
[Range(0.1f, 5f)] public float playbackSpeed = 1f;
|
||||
public bool loopSimulation = true;
|
||||
|
||||
[Header("Simulated Mode (Button Mashing)")]
|
||||
public KeyCode simulateStrokeKey = KeyCode.Space;
|
||||
public float simulateWattJump = 50f; // Lowered slightly so you have to work for it
|
||||
public float simulateWattDecayRate = 85f; // Increased so the "flywheel" slows down faster
|
||||
public float simulateMaxWatts = 300f; // The new hard cap!
|
||||
|
||||
private float _currentSimulatedWatts = 0f;
|
||||
private float _lastSimStrokeTime = 0f;
|
||||
|
||||
[Header("Game Physics Settings")]
|
||||
public float maxGameSpeed = 50f;
|
||||
public float wattsToSpeedMultiplier = 0.1f;
|
||||
public float speedSmoothTime = 0.75f;
|
||||
public int userMaxHR = 190;
|
||||
|
||||
[Header("Live Output (Read Only)")]
|
||||
public float CurrentSmoothedVelocity;
|
||||
public float LastStrokeImpulse;
|
||||
public bool IsLastStrokeSmooth;
|
||||
public int MaxPowerOutput;
|
||||
public int CurrentHRZone;
|
||||
public float LastStrokeRatio;
|
||||
|
||||
public Action<float> OnVelocityChanged;
|
||||
public Action<float, bool, float> OnStrokeCompleted;
|
||||
public Action<int> OnZoneChanged;
|
||||
|
||||
private float _targetVelocity;
|
||||
private float _velocityVelocity;
|
||||
|
||||
// Recorded State
|
||||
private float _playbackTimer = 0f;
|
||||
private int _playbackFrameIndex = 0;
|
||||
|
||||
// Stroke Tracking State Machine
|
||||
private enum StrokeState { Idle, Drive, Recovery }
|
||||
private StrokeState _currentState = StrokeState.Idle;
|
||||
private float _driveTimer = 0f;
|
||||
private float _recoveryTimer = 0f;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance == null) Instance = this;
|
||||
else Destroy(gameObject);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (PerformanceMonitorManager.Instance != null)
|
||||
{
|
||||
PerformanceMonitorManager.Instance.OnStatsUpdated += (stats) => { if (currentInputSource == InputSource.LivePM5) ProcessStats(stats.Watts, stats.SPM, stats.HeartRate); };
|
||||
PerformanceMonitorManager.Instance.OnForceCurveUpdated += (curve) => { if (currentInputSource == InputSource.LivePM5) ProcessForceCurve(curve); };
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
HandleKeyboardControls();
|
||||
|
||||
if (currentInputSource == InputSource.Recorded)
|
||||
{
|
||||
RunRecordedSession();
|
||||
}
|
||||
else if (currentInputSource == InputSource.Simulated)
|
||||
{
|
||||
RunManualSimulation();
|
||||
}
|
||||
|
||||
// Apply Smoothing
|
||||
CurrentSmoothedVelocity = Mathf.SmoothDamp(CurrentSmoothedVelocity, _targetVelocity, ref _velocityVelocity, speedSmoothTime);
|
||||
OnVelocityChanged?.Invoke(CurrentSmoothedVelocity);
|
||||
|
||||
// Timers
|
||||
if (_currentState == StrokeState.Drive) _driveTimer += Time.deltaTime;
|
||||
else if (_currentState == StrokeState.Recovery) _recoveryTimer += Time.deltaTime;
|
||||
}
|
||||
|
||||
private void RunManualSimulation()
|
||||
{
|
||||
// 1. Decay the watts over time (Simulating the flywheel slowing down)
|
||||
_currentSimulatedWatts = Mathf.Max(0f, _currentSimulatedWatts - (simulateWattDecayRate * Time.deltaTime));
|
||||
|
||||
// 2. Handle the "Pull"
|
||||
if (Input.GetKeyDown(simulateStrokeKey))
|
||||
{
|
||||
// Add power, but firmly cap it so we don't break the sound barrier
|
||||
_currentSimulatedWatts = Mathf.Min(_currentSimulatedWatts + simulateWattJump, simulateMaxWatts);
|
||||
|
||||
// Calculate a fake SPM based on how fast you are mashing
|
||||
float timeSinceLast = Time.time - _lastSimStrokeTime;
|
||||
int fakeSPM = timeSinceLast > 0 ? Mathf.RoundToInt(60f / timeSinceLast) : 0;
|
||||
_lastSimStrokeTime = Time.time;
|
||||
|
||||
// Fake HR that scales up gently with your power output
|
||||
int fakeHR = Mathf.Clamp(100 + Mathf.RoundToInt(_currentSimulatedWatts / 4f), 70, 200);
|
||||
|
||||
ProcessStats(Mathf.RoundToInt(_currentSimulatedWatts), fakeSPM, fakeHR);
|
||||
|
||||
// Generate a fake, perfectly smooth force curve (a bell shape)
|
||||
List<float> fakeCurve = new List<float> { 20f, 60f, 120f, 150f, 120f, 60f, 20f };
|
||||
ProcessForceCurve(fakeCurve);
|
||||
|
||||
// Immediately send an empty list to signify the drive ended and trigger recovery
|
||||
ProcessForceCurve(new List<float>());
|
||||
}
|
||||
else
|
||||
{
|
||||
// If not pulling, just update the decaying watts
|
||||
ProcessStats(Mathf.RoundToInt(_currentSimulatedWatts), 0, 100);
|
||||
}
|
||||
}
|
||||
|
||||
private void RunRecordedSession()
|
||||
{
|
||||
if (recordedSessions == null || recordedSessions.Count == 0 || recordedSessions[currentSessionIndex] == null) return;
|
||||
|
||||
var session = recordedSessions[currentSessionIndex];
|
||||
|
||||
_playbackTimer += Time.deltaTime * playbackSpeed;
|
||||
|
||||
while (_playbackFrameIndex < session.frames.Count && _playbackTimer >= session.frames[_playbackFrameIndex].timestamp)
|
||||
{
|
||||
RowingFrame frame = session.frames[_playbackFrameIndex];
|
||||
|
||||
ProcessStats(frame.watts, frame.spm, frame.heartRate);
|
||||
|
||||
if (frame.forceCurve != null && frame.forceCurve.Count > 0)
|
||||
{
|
||||
ProcessForceCurve(frame.forceCurve);
|
||||
}
|
||||
else if (frame.watts == 0 && frame.spm == 0)
|
||||
{
|
||||
ProcessForceCurve(new List<float>());
|
||||
}
|
||||
|
||||
_playbackFrameIndex++;
|
||||
}
|
||||
|
||||
if (_playbackFrameIndex >= session.frames.Count)
|
||||
{
|
||||
if (loopSimulation)
|
||||
{
|
||||
_playbackTimer = 0f;
|
||||
_playbackFrameIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleKeyboardControls()
|
||||
{
|
||||
if (currentInputSource == InputSource.Recorded)
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.UpArrow)) playbackSpeed += 0.25f;
|
||||
if (Input.GetKeyDown(KeyCode.DownArrow)) playbackSpeed = Mathf.Max(0.25f, playbackSpeed - 0.25f);
|
||||
|
||||
for (int i = 0; i < recordedSessions.Count; i++)
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.Alpha1 + i))
|
||||
{
|
||||
currentSessionIndex = i;
|
||||
_playbackTimer = 0f;
|
||||
_playbackFrameIndex = 0;
|
||||
Debug.Log($"Switched to Session: {recordedSessions[i].sessionName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Core Logic (Shared by Live, Recorded, and Simulated) ---
|
||||
|
||||
private void ProcessStats(int watts, int spm, int hr)
|
||||
{
|
||||
_targetVelocity = Mathf.Clamp(watts * wattsToSpeedMultiplier, 0, maxGameSpeed);
|
||||
if (watts > MaxPowerOutput) MaxPowerOutput = watts;
|
||||
|
||||
int newZone = CalculateHRZone(hr);
|
||||
if (newZone != CurrentHRZone)
|
||||
{
|
||||
CurrentHRZone = newZone;
|
||||
OnZoneChanged?.Invoke(CurrentHRZone);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessForceCurve(List<float> points)
|
||||
{
|
||||
if (points == null || points.Count == 0)
|
||||
{
|
||||
if (_currentState == StrokeState.Drive)
|
||||
{
|
||||
_currentState = StrokeState.Recovery;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentState == StrokeState.Recovery || _currentState == StrokeState.Idle)
|
||||
{
|
||||
if (_recoveryTimer > 0)
|
||||
{
|
||||
LastStrokeRatio = _driveTimer / _recoveryTimer;
|
||||
|
||||
LastStrokeImpulse = CalculateImpulse(points);
|
||||
IsLastStrokeSmooth = AnalyzeSmoothness(points);
|
||||
|
||||
OnStrokeCompleted?.Invoke(LastStrokeImpulse, IsLastStrokeSmooth, LastStrokeRatio);
|
||||
}
|
||||
_driveTimer = 0f;
|
||||
_recoveryTimer = 0f;
|
||||
_currentState = StrokeState.Drive;
|
||||
}
|
||||
}
|
||||
|
||||
private int CalculateHRZone(int currentHR) { return 1; /* Replace with your actual HR math from previous step */ }
|
||||
private float CalculateImpulse(List<float> curve) { return 1f; /* Replace with your actual Impulse math */ }
|
||||
private bool AnalyzeSmoothness(List<float> curve) { return true; /* Replace with your actual Smoothness math */ }
|
||||
}
|
||||
Reference in New Issue
Block a user