Working MVP code for Valentines
This commit is contained in:
3
Assets/Scripts/Common/Camera.meta
Normal file
3
Assets/Scripts/Common/Camera.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44c4b5c8fcd54d1887fb05ca65a9bb20
|
||||
timeCreated: 1764851223
|
||||
169
Assets/Scripts/Common/Camera/CameraStateManager.cs
Normal file
169
Assets/Scripts/Common/Camera/CameraStateManager.cs
Normal file
@@ -0,0 +1,169 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Core;
|
||||
using Core.Lifecycle;
|
||||
using Unity.Cinemachine;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Common.Camera
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic state-based camera controller using Cinemachine.
|
||||
/// Manages camera transitions by setting priorities on virtual cameras.
|
||||
/// Type parameter TState must be an enum representing camera states.
|
||||
/// </summary>
|
||||
public abstract class CameraStateManager<TState> : ManagedBehaviour where TState : Enum
|
||||
{
|
||||
#region Configuration
|
||||
|
||||
[Header("Camera Priority Settings")]
|
||||
[Tooltip("Priority for inactive cameras")]
|
||||
[SerializeField] protected int inactivePriority = 10;
|
||||
|
||||
[Tooltip("Priority for the active camera")]
|
||||
[SerializeField] protected int activePriority = 20;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] protected bool showDebugLogs = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region State
|
||||
|
||||
private Dictionary<TState, CinemachineCamera> _cameraMap = new Dictionary<TState, CinemachineCamera>();
|
||||
private TState _currentState;
|
||||
private bool _isInitialized = false;
|
||||
|
||||
public TState CurrentState => _currentState;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Fired when camera state changes. Parameters: (TState oldState, TState newState)
|
||||
/// </summary>
|
||||
public event Action<TState, TState> OnStateChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
|
||||
/// <summary>
|
||||
/// Register a camera for a specific state.
|
||||
/// Call this in subclass OnManagedAwake to set up the camera map.
|
||||
/// </summary>
|
||||
protected void RegisterCamera(TState state, CinemachineCamera pCamera)
|
||||
{
|
||||
if (pCamera == null)
|
||||
{
|
||||
Logging.Warning($"[{GetType().Name}] Attempted to register null camera for state {state}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_cameraMap.ContainsKey(state))
|
||||
{
|
||||
Logging.Warning($"[{GetType().Name}] Camera for state {state} already registered, overwriting");
|
||||
}
|
||||
|
||||
_cameraMap[state] = pCamera;
|
||||
|
||||
// Set all cameras to inactive priority initially
|
||||
pCamera.Priority.Value = inactivePriority;
|
||||
|
||||
if (showDebugLogs) Logging.Debug($"[{GetType().Name}] Registered camera '{pCamera.gameObject.name}' for state {state}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalize initialization after all cameras are registered.
|
||||
/// Call this at the end of subclass OnManagedAwake.
|
||||
/// </summary>
|
||||
protected void FinalizeInitialization()
|
||||
{
|
||||
_isInitialized = true;
|
||||
|
||||
if (_cameraMap.Count == 0)
|
||||
{
|
||||
Logging.Warning($"[{GetType().Name}] No cameras registered!");
|
||||
}
|
||||
|
||||
if (showDebugLogs) Logging.Debug($"[{GetType().Name}] Initialized with {_cameraMap.Count} cameras");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region State Management
|
||||
|
||||
/// <summary>
|
||||
/// Switch to a specific camera state
|
||||
/// </summary>
|
||||
public virtual void SwitchToState(TState newState)
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
Logging.Error($"[{GetType().Name}] Cannot switch state - not initialized!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_cameraMap.ContainsKey(newState))
|
||||
{
|
||||
Logging.Error($"[{GetType().Name}] No camera registered for state {newState}!");
|
||||
return;
|
||||
}
|
||||
|
||||
TState oldState = _currentState;
|
||||
_currentState = newState;
|
||||
|
||||
// Set all cameras to inactive priority
|
||||
foreach (var kvp in _cameraMap)
|
||||
{
|
||||
kvp.Value.Priority.Value = inactivePriority;
|
||||
}
|
||||
|
||||
// Set target camera to active priority
|
||||
_cameraMap[newState].Priority.Value = activePriority;
|
||||
|
||||
if (showDebugLogs) Logging.Debug($"[{GetType().Name}] Switched from {oldState} to {newState} (camera: {_cameraMap[newState].gameObject.name})");
|
||||
|
||||
OnStateChanged?.Invoke(oldState, newState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the camera for a specific state
|
||||
/// </summary>
|
||||
public CinemachineCamera GetCamera(TState state)
|
||||
{
|
||||
if (_cameraMap.TryGetValue(state, out CinemachineCamera pCamera))
|
||||
{
|
||||
return pCamera;
|
||||
}
|
||||
|
||||
Logging.Warning($"[{GetType().Name}] No camera found for state {state}");
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a camera is registered for a state
|
||||
/// </summary>
|
||||
public bool HasCamera(TState state)
|
||||
{
|
||||
return _cameraMap.ContainsKey(state);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Validation
|
||||
|
||||
/// <summary>
|
||||
/// Validate that all required states have cameras registered.
|
||||
/// Subclasses can override to add custom validation.
|
||||
/// </summary>
|
||||
protected virtual void ValidateCameras()
|
||||
{
|
||||
// Subclasses should implement specific validation
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/Common/Camera/CameraStateManager.cs.meta
Normal file
3
Assets/Scripts/Common/Camera/CameraStateManager.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4fc438e61b94c529f7d1e8fe9fb70fa
|
||||
timeCreated: 1764851223
|
||||
3
Assets/Scripts/Common/Input.meta
Normal file
3
Assets/Scripts/Common/Input.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35838202f1ac4fa4b606b0582fa4e439
|
||||
timeCreated: 1764851204
|
||||
373
Assets/Scripts/Common/Input/DragLaunchController.cs
Normal file
373
Assets/Scripts/Common/Input/DragLaunchController.cs
Normal file
@@ -0,0 +1,373 @@
|
||||
using System;
|
||||
using Core;
|
||||
using Core.Lifecycle;
|
||||
using Input;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Common.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for drag-to-launch mechanics (Angry Birds style).
|
||||
/// Provides core drag logic, force calculation, and input handling.
|
||||
/// Uses SlingshotConfig for all settings - fully configuration-driven.
|
||||
/// Subclasses implement visual feedback and specific launch behavior.
|
||||
/// </summary>
|
||||
public abstract class DragLaunchController : ManagedBehaviour, ITouchInputConsumer
|
||||
{
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Fired when drag starts. Parameters: (Vector2 startPosition)
|
||||
/// </summary>
|
||||
public event Action<Vector2> OnDragStart;
|
||||
|
||||
/// <summary>
|
||||
/// Fired during drag update. Parameters: (Vector2 currentPosition, Vector2 direction, float force)
|
||||
/// </summary>
|
||||
public event Action<Vector2, Vector2, float> OnDragUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when drag ends. Parameters: (Vector2 endPosition, Vector2 direction, float force)
|
||||
/// </summary>
|
||||
public event Action<Vector2, Vector2, float> OnDragEnd;
|
||||
|
||||
/// <summary>
|
||||
/// Fired when launch occurs. Parameters: (Vector2 direction, float force)
|
||||
/// </summary>
|
||||
public event Action<Vector2, float> OnLaunch;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Settings
|
||||
|
||||
private SlingshotConfig _config;
|
||||
|
||||
protected SlingshotConfig Config
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_config == null)
|
||||
{
|
||||
_config = GetSlingshotConfig();
|
||||
}
|
||||
return _config;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subclasses implement to return their slingshot configuration
|
||||
/// from their specific settings object
|
||||
/// </summary>
|
||||
protected abstract SlingshotConfig GetSlingshotConfig();
|
||||
|
||||
/// <summary>
|
||||
/// Subclasses implement to return the projectile prefab that will be launched.
|
||||
/// Used for reading Rigidbody2D properties (mass, gravityScale) for trajectory calculations.
|
||||
/// </summary>
|
||||
protected abstract GameObject GetProjectilePrefab();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Inspector Properties
|
||||
|
||||
[Header("Launch Settings Overrides (leave 0 to use config)")]
|
||||
[Tooltip("Override max drag distance (0 = use config)")]
|
||||
[SerializeField] protected float maxDragDistanceOverride = 0f;
|
||||
|
||||
[Tooltip("Override max force (0 = use config)")]
|
||||
[SerializeField] protected float maxForceOverride = 0f;
|
||||
|
||||
[Header("References")]
|
||||
[Tooltip("Launch anchor point (spawn/slingshot position)")]
|
||||
[SerializeField] protected Transform launchAnchor;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] protected bool showDebugLogs;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Computed Properties
|
||||
|
||||
protected float MaxDragDistance => maxDragDistanceOverride > 0 ? maxDragDistanceOverride : Config?.maxDragDistance ?? 5f;
|
||||
protected float MaxForce => maxForceOverride > 0 ? maxForceOverride : Config?.baseLaunchForce ?? 20f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region State
|
||||
|
||||
private bool _isDragging;
|
||||
private Vector2 _dragStartPosition;
|
||||
private bool _isEnabled = false;
|
||||
private bool _isRegistered = false;
|
||||
|
||||
public bool IsDragging => _isDragging;
|
||||
public bool IsEnabled => _isEnabled;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lifecycle
|
||||
|
||||
internal override void OnManagedAwake()
|
||||
{
|
||||
base.OnManagedAwake();
|
||||
|
||||
if (launchAnchor == null)
|
||||
{
|
||||
launchAnchor = transform;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Enable/Disable
|
||||
|
||||
/// <summary>
|
||||
/// Enable the launch controller and register with InputManager
|
||||
/// </summary>
|
||||
public virtual void Enable()
|
||||
{
|
||||
_isEnabled = true;
|
||||
|
||||
// Register with InputManager as override consumer
|
||||
if (InputManager.Instance != null && !_isRegistered)
|
||||
{
|
||||
InputManager.Instance.RegisterOverrideConsumer(this);
|
||||
_isRegistered = true;
|
||||
if (showDebugLogs) Logging.Debug($"[{GetType().Name}] Registered with InputManager");
|
||||
}
|
||||
|
||||
// Show preview visuals
|
||||
ShowPreview();
|
||||
|
||||
if (showDebugLogs) Logging.Debug($"[{GetType().Name}] Enabled");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable the launch controller and unregister from InputManager
|
||||
/// </summary>
|
||||
public virtual void Disable()
|
||||
{
|
||||
_isEnabled = false;
|
||||
_isDragging = false;
|
||||
|
||||
// Unregister from InputManager
|
||||
if (InputManager.Instance != null && _isRegistered)
|
||||
{
|
||||
InputManager.Instance.UnregisterOverrideConsumer(this);
|
||||
_isRegistered = false;
|
||||
if (showDebugLogs) Logging.Debug($"[{GetType().Name}] Unregistered from InputManager");
|
||||
}
|
||||
|
||||
// Hide preview visuals
|
||||
HidePreview();
|
||||
|
||||
if (showDebugLogs) Logging.Debug($"[{GetType().Name}] Disabled");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ITouchInputConsumer Implementation
|
||||
|
||||
public void OnTap(Vector2 worldPosition)
|
||||
{
|
||||
// Drag-to-launch uses hold/drag, not tap
|
||||
}
|
||||
|
||||
public void OnHoldStart(Vector2 worldPosition)
|
||||
{
|
||||
if (!_isEnabled) return;
|
||||
StartDrag(worldPosition);
|
||||
}
|
||||
|
||||
public void OnHoldMove(Vector2 worldPosition)
|
||||
{
|
||||
if (!_isEnabled || !_isDragging) return;
|
||||
UpdateDrag(worldPosition);
|
||||
}
|
||||
|
||||
public void OnHoldEnd(Vector2 worldPosition)
|
||||
{
|
||||
if (!_isEnabled || !_isDragging) return;
|
||||
EndDrag(worldPosition);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drag Handling
|
||||
|
||||
/// <summary>
|
||||
/// Start drag operation
|
||||
/// </summary>
|
||||
protected virtual void StartDrag(Vector2 worldPosition)
|
||||
{
|
||||
_isDragging = true;
|
||||
// Use launch anchor as the reference point (like Angry Birds)
|
||||
_dragStartPosition = launchAnchor.position;
|
||||
|
||||
if (showDebugLogs) Logging.Debug($"[{GetType().Name}] Started drag at {worldPosition}, anchor at {_dragStartPosition}");
|
||||
|
||||
OnDragStart?.Invoke(worldPosition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update drag operation
|
||||
/// </summary>
|
||||
protected virtual void UpdateDrag(Vector2 currentWorldPosition)
|
||||
{
|
||||
// Calculate drag vector from anchor to current drag position
|
||||
// Pull back (away from anchor) = launch forward (toward anchor direction)
|
||||
Vector2 dragVector = _dragStartPosition - currentWorldPosition;
|
||||
|
||||
// Calculate force and direction
|
||||
float dragDistance = dragVector.magnitude;
|
||||
float dragRatio = Mathf.Clamp01(dragDistance / MaxDragDistance);
|
||||
|
||||
// Use config to calculate force with multipliers
|
||||
float force = Config?.CalculateForce(dragDistance, dragRatio) ?? (dragRatio * MaxForce);
|
||||
Vector2 direction = dragVector.normalized;
|
||||
float mass = GetProjectileMass();
|
||||
|
||||
// Warn if mass is zero or invalid
|
||||
if (mass <= 0f && showDebugLogs)
|
||||
{
|
||||
Logging.Warning($"[{GetType().Name}] Projectile mass is {mass}! Trajectory calculation will be inaccurate. Override GetProjectileMass().");
|
||||
}
|
||||
|
||||
// Update visuals with mass parameter
|
||||
UpdateVisuals(currentWorldPosition, direction, force, dragDistance, mass);
|
||||
|
||||
OnDragUpdate?.Invoke(currentWorldPosition, direction, force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End drag operation and potentially launch
|
||||
/// </summary>
|
||||
protected virtual void EndDrag(Vector2 currentWorldPosition)
|
||||
{
|
||||
_isDragging = false;
|
||||
|
||||
// Hide preview
|
||||
HidePreview();
|
||||
|
||||
// Calculate final launch parameters
|
||||
Vector2 dragVector = _dragStartPosition - currentWorldPosition;
|
||||
float dragDistance = dragVector.magnitude;
|
||||
float dragRatio = Mathf.Clamp01(dragDistance / MaxDragDistance);
|
||||
|
||||
// Use config to calculate force with multipliers
|
||||
float force = Config?.CalculateForce(dragDistance, dragRatio) ?? (dragRatio * MaxForce);
|
||||
Vector2 direction = dragVector.normalized;
|
||||
|
||||
// Get minimum force from config
|
||||
float minForce = Config?.GetMinForce() ?? (MaxForce * 0.1f);
|
||||
|
||||
if (showDebugLogs)
|
||||
Logging.Debug($"[{GetType().Name}] Drag ended - Force: {force:F2}, Min: {minForce:F2}, Distance: {dragDistance:F2}");
|
||||
|
||||
OnDragEnd?.Invoke(currentWorldPosition, direction, force);
|
||||
|
||||
// Launch if force exceeds minimum
|
||||
if (force >= minForce)
|
||||
{
|
||||
if (showDebugLogs) Logging.Debug($"[{GetType().Name}] Launching with force {force:F2}");
|
||||
PerformLaunch(direction, force);
|
||||
OnLaunch?.Invoke(direction, force);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (showDebugLogs) Logging.Debug($"[{GetType().Name}] Drag too short - force {force:F2} < min {minForce:F2}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Abstract Methods - Subclass Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Update visual feedback during drag (trajectory preview, rubber band, etc.)
|
||||
/// </summary>
|
||||
protected abstract void UpdateVisuals(Vector2 currentPosition, Vector2 direction, float force, float dragDistance, float mass);
|
||||
|
||||
/// <summary>
|
||||
/// Show preview visuals when controller is enabled
|
||||
/// </summary>
|
||||
protected abstract void ShowPreview();
|
||||
|
||||
/// <summary>
|
||||
/// Hide preview visuals when controller is disabled
|
||||
/// </summary>
|
||||
protected abstract void HidePreview();
|
||||
|
||||
/// <summary>
|
||||
/// Perform the actual launch (spawn projectile/airplane, apply force, etc.)
|
||||
/// </summary>
|
||||
protected abstract void PerformLaunch(Vector2 direction, float force);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Virtual Methods - Optional Override
|
||||
|
||||
/// <summary>
|
||||
/// Get projectile mass for trajectory calculation.
|
||||
/// Reads from the prefab's Rigidbody2D component.
|
||||
/// Subclasses can override for custom behavior (e.g., if mass changes dynamically).
|
||||
/// </summary>
|
||||
protected virtual float GetProjectileMass()
|
||||
{
|
||||
GameObject prefab = GetProjectilePrefab();
|
||||
if (prefab == null)
|
||||
{
|
||||
if (showDebugLogs)
|
||||
Logging.Warning($"[{GetType().Name}] GetProjectilePrefab() returned null!");
|
||||
return 0f;
|
||||
}
|
||||
|
||||
var rb = prefab.GetComponent<Rigidbody2D>();
|
||||
if (rb == null)
|
||||
{
|
||||
if (showDebugLogs)
|
||||
Logging.Warning($"[{GetType().Name}] Projectile prefab '{prefab.name}' has no Rigidbody2D!");
|
||||
return 0f;
|
||||
}
|
||||
|
||||
return rb.mass;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get gravity value for trajectory calculation.
|
||||
/// Uses Physics2D.gravity.magnitude * prefab's Rigidbody2D gravityScale.
|
||||
/// </summary>
|
||||
protected virtual float GetGravity()
|
||||
{
|
||||
GameObject prefab = GetProjectilePrefab();
|
||||
if (prefab == null)
|
||||
{
|
||||
// Fallback to project gravity
|
||||
return Physics2D.gravity.magnitude;
|
||||
}
|
||||
|
||||
var rb = prefab.GetComponent<Rigidbody2D>();
|
||||
float gravityScale = rb != null ? rb.gravityScale : 1f;
|
||||
|
||||
return Physics2D.gravity.magnitude * gravityScale;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cleanup
|
||||
|
||||
internal override void OnManagedDestroy()
|
||||
{
|
||||
base.OnManagedDestroy();
|
||||
|
||||
// Ensure we unregister from InputManager
|
||||
if (_isRegistered && InputManager.Instance != null)
|
||||
{
|
||||
InputManager.Instance.UnregisterOverrideConsumer(this);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/Common/Input/DragLaunchController.cs.meta
Normal file
3
Assets/Scripts/Common/Input/DragLaunchController.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44e042d1338149f6bb8adf6129e1c6c2
|
||||
timeCreated: 1764851204
|
||||
59
Assets/Scripts/Common/Input/SlingshotConfig.cs
Normal file
59
Assets/Scripts/Common/Input/SlingshotConfig.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Common.Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration for slingshot launch mechanics.
|
||||
/// Can be embedded in any minigame settings that use drag-to-launch.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class SlingshotConfig
|
||||
{
|
||||
[Header("Drag & Force Settings")]
|
||||
[Tooltip("Distance to reach max force")]
|
||||
public float maxDragDistance = 5f;
|
||||
|
||||
[Tooltip("Base force value")]
|
||||
public float baseLaunchForce = 20f;
|
||||
|
||||
[Tooltip("Minimum threshold (0-1)")]
|
||||
[Range(0f, 1f)]
|
||||
public float minForceMultiplier = 0.1f;
|
||||
|
||||
[Tooltip("Maximum cap (0-2, usually 1)")]
|
||||
[Range(0f, 2f)]
|
||||
public float maxForceMultiplier = 1f;
|
||||
|
||||
[Header("Trajectory Settings")]
|
||||
[Tooltip("Number of preview points")]
|
||||
public int trajectoryPoints = 50;
|
||||
|
||||
[Tooltip("Time between points")]
|
||||
public float trajectoryTimeStep = 0.1f;
|
||||
|
||||
[Tooltip("Show trajectory after launch (seconds, 0 = no lock)")]
|
||||
public float trajectoryLockDuration = 2f;
|
||||
|
||||
[Header("Input")]
|
||||
[Tooltip("Auto-register with InputManager on Enable()")]
|
||||
public bool autoRegisterInput = true;
|
||||
|
||||
/// <summary>
|
||||
/// Calculate force from drag parameters using configured multipliers
|
||||
/// </summary>
|
||||
public float CalculateForce(float dragDistance, float dragRatio)
|
||||
{
|
||||
return dragRatio * maxForceMultiplier * baseLaunchForce;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate minimum force threshold
|
||||
/// </summary>
|
||||
public float GetMinForce()
|
||||
{
|
||||
return baseLaunchForce * minForceMultiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/Common/Input/SlingshotConfig.cs.meta
Normal file
3
Assets/Scripts/Common/Input/SlingshotConfig.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be4f5d5fd7084425a7bf28a1fadf125e
|
||||
timeCreated: 1764854225
|
||||
Reference in New Issue
Block a user