MVP of the plane throwing game (#77)
Co-authored-by: Michal Pikulski <michal@foolhardyhorizons.com> Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com> Reviewed-on: #77
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
using Core;
|
||||
using Core.Lifecycle;
|
||||
using Common.Camera;
|
||||
using Core;
|
||||
using Minigames.FortFight.Data;
|
||||
using Unity.Cinemachine;
|
||||
using UnityEngine;
|
||||
@@ -8,11 +8,10 @@ namespace Minigames.FortFight.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages camera states and transitions for the Fort Fight minigame.
|
||||
/// Extends CameraStateManager to use the common state-based camera system.
|
||||
/// Subscribes to turn events and switches camera views accordingly.
|
||||
/// Uses Cinemachine for smooth camera blending.
|
||||
/// Singleton pattern for easy access.
|
||||
/// </summary>
|
||||
public class CameraController : ManagedBehaviour
|
||||
public class CameraController : CameraStateManager<FortFightCameraState>
|
||||
{
|
||||
#region Singleton
|
||||
|
||||
@@ -31,41 +30,15 @@ namespace Minigames.FortFight.Core
|
||||
|
||||
#endregion
|
||||
|
||||
#region Inspector References
|
||||
|
||||
[Header("Cinemachine Cameras")]
|
||||
[Tooltip("Virtual camera showing wide battlefield view (both forts)")]
|
||||
[SerializeField] private CinemachineCamera wideViewCamera;
|
||||
|
||||
[Tooltip("Player One's dedicated camera (position this in the scene for Player 1's view)")]
|
||||
[SerializeField] private CinemachineCamera playerOneCamera;
|
||||
|
||||
[Tooltip("Player Two's dedicated camera (position this in the scene for Player 2's view)")]
|
||||
[SerializeField] private CinemachineCamera playerTwoCamera;
|
||||
|
||||
[Tooltip("Camera that follows projectiles in flight (should have CinemachineFollow component)")]
|
||||
[SerializeField] private CinemachineCamera projectileCamera;
|
||||
|
||||
// Note: TurnManager accessed via singleton
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
public CinemachineCamera WideViewCamera => wideViewCamera;
|
||||
public CinemachineCamera PlayerOneCamera => playerOneCamera;
|
||||
public CinemachineCamera PlayerTwoCamera => playerTwoCamera;
|
||||
public CinemachineCamera ProjectileCamera => projectileCamera;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Lifecycle
|
||||
|
||||
internal override void OnManagedAwake()
|
||||
{
|
||||
// Base class handles InitializeCameraMap() and ValidateCameras()
|
||||
base.OnManagedAwake();
|
||||
|
||||
// Register singleton
|
||||
// Set singleton
|
||||
if (_instance != null && _instance != this)
|
||||
{
|
||||
Logging.Warning("[CameraController] Multiple instances detected! Destroying duplicate.");
|
||||
@@ -73,28 +46,6 @@ namespace Minigames.FortFight.Core
|
||||
return;
|
||||
}
|
||||
_instance = this;
|
||||
|
||||
// Validate references
|
||||
if (wideViewCamera == null)
|
||||
{
|
||||
Logging.Error("[CameraController] Wide view camera not assigned!");
|
||||
}
|
||||
|
||||
if (playerOneCamera == null)
|
||||
{
|
||||
Logging.Error("[CameraController] Player One camera not assigned!");
|
||||
}
|
||||
|
||||
if (playerTwoCamera == null)
|
||||
{
|
||||
Logging.Error("[CameraController] Player Two camera not assigned!");
|
||||
}
|
||||
|
||||
if (projectileCamera == null)
|
||||
{
|
||||
Logging.Warning("[CameraController] Projectile camera not assigned - projectiles won't be followed!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal override void OnManagedStart()
|
||||
@@ -136,75 +87,55 @@ namespace Minigames.FortFight.Core
|
||||
#region Event Handlers
|
||||
|
||||
/// <summary>
|
||||
/// Called when a player's turn starts - activate their dedicated camera
|
||||
/// Called when a player's turn starts - switch to appropriate camera state
|
||||
/// </summary>
|
||||
private void HandleTurnStarted(PlayerData player, TurnState turnState)
|
||||
{
|
||||
Logging.Debug($"[CameraController] Turn started for {player.PlayerName} (Index: {player.PlayerIndex}, State: {turnState})");
|
||||
if (showDebugLogs)
|
||||
Logging.Debug($"[CameraController] Turn started for {player.PlayerName} (Index: {player.PlayerIndex}, State: {turnState})");
|
||||
|
||||
// If transitioning, show wide view
|
||||
if (turnState == TurnState.TransitioningTurn)
|
||||
{
|
||||
ActivateCamera(wideViewCamera);
|
||||
SwitchToState(FortFightCameraState.WideView);
|
||||
return;
|
||||
}
|
||||
|
||||
// Activate the appropriate player camera based on player index
|
||||
// Switch to appropriate player camera based on index
|
||||
if (player.PlayerIndex == 0)
|
||||
{
|
||||
// Player One's turn
|
||||
ActivateCamera(playerOneCamera);
|
||||
SwitchToState(FortFightCameraState.PlayerOne);
|
||||
}
|
||||
else if (player.PlayerIndex == 1)
|
||||
{
|
||||
// Player Two's turn
|
||||
ActivateCamera(playerTwoCamera);
|
||||
SwitchToState(FortFightCameraState.PlayerTwo);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logging.Warning($"[CameraController] Unknown player index: {player.PlayerIndex}, defaulting to wide view");
|
||||
ActivateCamera(wideViewCamera);
|
||||
SwitchToState(FortFightCameraState.WideView);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a player's turn ends - camera switches handled by turn state changes
|
||||
/// Called when a player's turn ends
|
||||
/// </summary>
|
||||
private void HandleTurnEnded(PlayerData player)
|
||||
{
|
||||
Logging.Debug($"[CameraController] Turn ended for {player.PlayerName}");
|
||||
if (showDebugLogs) Logging.Debug($"[CameraController] Turn ended for {player.PlayerName}");
|
||||
// Camera switching happens via OnTurnStarted when state changes to TransitioningTurn
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activate a specific camera by setting its priority highest
|
||||
/// </summary>
|
||||
private void ActivateCamera(CinemachineCamera camera)
|
||||
{
|
||||
if (camera == null) return;
|
||||
|
||||
// Set all cameras to low priority
|
||||
if (wideViewCamera != null) wideViewCamera.Priority.Value = 10;
|
||||
if (playerOneCamera != null) playerOneCamera.Priority.Value = 10;
|
||||
if (playerTwoCamera != null) playerTwoCamera.Priority.Value = 10;
|
||||
if (projectileCamera != null) projectileCamera.Priority.Value = 10;
|
||||
|
||||
// Set target camera to high priority
|
||||
camera.Priority.Value = 20;
|
||||
|
||||
Logging.Debug($"[CameraController] Activated camera: {camera.gameObject.name}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Projectile Tracking
|
||||
|
||||
/// <summary>
|
||||
/// Start following a projectile with the projectile camera.
|
||||
/// Called when a projectile is launched.
|
||||
/// Start following a projectile with the projectile camera
|
||||
/// </summary>
|
||||
public void StartFollowingProjectile(Transform projectileTransform)
|
||||
{
|
||||
var projectileCamera = GetCamera(FortFightCameraState.Projectile);
|
||||
if (projectileCamera == null)
|
||||
{
|
||||
Logging.Warning("[CameraController] Cannot follow projectile - projectile camera not assigned!");
|
||||
@@ -217,38 +148,32 @@ namespace Minigames.FortFight.Core
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify CinemachineFollow component exists (optional check)
|
||||
var followComponent = projectileCamera.GetComponent<CinemachineFollow>();
|
||||
if (followComponent == null)
|
||||
{
|
||||
Logging.Error("[CameraController] Projectile camera missing CinemachineFollow component!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the follow target on the CinemachineCamera's Target property
|
||||
// Set the follow target
|
||||
projectileCamera.Target.TrackingTarget = projectileTransform;
|
||||
|
||||
// Activate the projectile camera
|
||||
ActivateCamera(projectileCamera);
|
||||
// Switch to projectile camera
|
||||
SwitchToState(FortFightCameraState.Projectile);
|
||||
|
||||
Logging.Debug($"[CameraController] Now following projectile: {projectileTransform.gameObject.name}");
|
||||
if (showDebugLogs)
|
||||
Logging.Debug($"[CameraController] Now following projectile: {projectileTransform.gameObject.name}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop following the projectile and return to wide view.
|
||||
/// Called when projectile has settled.
|
||||
/// Stop following the projectile and return to wide view
|
||||
/// </summary>
|
||||
public void StopFollowingProjectile()
|
||||
{
|
||||
var projectileCamera = GetCamera(FortFightCameraState.Projectile);
|
||||
if (projectileCamera == null) return;
|
||||
|
||||
// Clear the follow target on the CinemachineCamera's Target property
|
||||
// Clear the follow target
|
||||
projectileCamera.Target.TrackingTarget = null;
|
||||
|
||||
// Return to wide view
|
||||
ActivateCamera(wideViewCamera);
|
||||
SwitchToState(FortFightCameraState.WideView);
|
||||
|
||||
Logging.Debug("[CameraController] Stopped following projectile, returned to wide view");
|
||||
if (showDebugLogs)
|
||||
Logging.Debug("[CameraController] Stopped following projectile, returned to wide view");
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -256,11 +181,11 @@ namespace Minigames.FortFight.Core
|
||||
#region Public API
|
||||
|
||||
/// <summary>
|
||||
/// Manually switch to wide view (useful for game start/end)
|
||||
/// Manually switch to wide view
|
||||
/// </summary>
|
||||
public void ShowWideView()
|
||||
{
|
||||
ActivateCamera(wideViewCamera);
|
||||
SwitchToState(FortFightCameraState.WideView);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -270,24 +195,36 @@ namespace Minigames.FortFight.Core
|
||||
{
|
||||
if (playerIndex == 0)
|
||||
{
|
||||
ActivateCamera(playerOneCamera);
|
||||
SwitchToState(FortFightCameraState.PlayerOne);
|
||||
}
|
||||
else if (playerIndex == 1)
|
||||
{
|
||||
ActivateCamera(playerTwoCamera);
|
||||
SwitchToState(FortFightCameraState.PlayerTwo);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Editor Helpers
|
||||
#region Validation
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
protected override void ValidateCameras()
|
||||
{
|
||||
// Base class validates all enum states have cameras assigned
|
||||
base.ValidateCameras();
|
||||
|
||||
// Additional validation: Check if projectile camera has follow component
|
||||
var projectileCamera = GetCamera(FortFightCameraState.Projectile);
|
||||
if (projectileCamera != null)
|
||||
{
|
||||
var followComponent = projectileCamera.GetComponent<CinemachineFollow>();
|
||||
if (followComponent == null)
|
||||
{
|
||||
Logging.Warning("[CameraController] Projectile camera missing CinemachineFollow component!");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AppleHills.Core.Settings;
|
||||
using Common.Input;
|
||||
using Minigames.FortFight.Data;
|
||||
using Minigames.FortFight.Settings;
|
||||
using UnityEngine;
|
||||
@@ -14,6 +15,19 @@ namespace Minigames.FortFight.Core
|
||||
[CreateAssetMenu(fileName = "FortFightSettings", menuName = "AppleHills/Settings/Fort Fight", order = 8)]
|
||||
public class FortFightSettings : BaseSettings, IFortFightSettings
|
||||
{
|
||||
[Header("Slingshot Configuration")]
|
||||
[SerializeField] private SlingshotConfig slingshotSettings = new SlingshotConfig
|
||||
{
|
||||
maxDragDistance = 5f,
|
||||
baseLaunchForce = 20f,
|
||||
minForceMultiplier = 0.1f,
|
||||
maxForceMultiplier = 1f,
|
||||
trajectoryPoints = 50,
|
||||
trajectoryTimeStep = 0.1f,
|
||||
trajectoryLockDuration = 2f,
|
||||
autoRegisterInput = false // TurnManager handles registration
|
||||
};
|
||||
|
||||
[Header("Block Material Configurations")]
|
||||
[Tooltip("HP and mass configurations for each material type")]
|
||||
[SerializeField] private List<BlockMaterialConfig> materialConfigs = new List<BlockMaterialConfig>
|
||||
@@ -112,21 +126,6 @@ namespace Minigames.FortFight.Core
|
||||
[Tooltip("Downward velocity when dropping (m/s)")]
|
||||
[SerializeField] private float ceilingFanDropSpeed = 20f;
|
||||
|
||||
[Header("Slingshot Settings")]
|
||||
[Tooltip("Base launch force multiplier - higher values = projectiles fly farther")]
|
||||
[SerializeField] private float baseLaunchForce = 20f;
|
||||
|
||||
[Tooltip("Minimum force multiplier (0-1, e.g. 0.1 = 10% of max force required to launch)")]
|
||||
[Range(0f, 1f)]
|
||||
[SerializeField] private float minForceMultiplier = 0.1f;
|
||||
|
||||
[Tooltip("Maximum force multiplier (0-1, e.g. 1.0 = 100% at max drag distance)")]
|
||||
[Range(0f, 2f)]
|
||||
[SerializeField] private float maxForceMultiplier = 1f;
|
||||
|
||||
[Tooltip("How long to keep trajectory visible after launching (seconds)")]
|
||||
[SerializeField] private float trajectoryLockDuration = 2f;
|
||||
|
||||
[Header("Physics Layers")]
|
||||
[Tooltip("Layer for fort blocks - projectiles will collide with these (Default: Layer 8 'FortBlock')")]
|
||||
[AppleHills.Core.Settings.Layer]
|
||||
@@ -142,6 +141,8 @@ namespace Minigames.FortFight.Core
|
||||
|
||||
#region IFortFightSettings Implementation
|
||||
|
||||
public SlingshotConfig SlingshotSettings => slingshotSettings;
|
||||
|
||||
public List<BlockMaterialConfig> MaterialConfigs => materialConfigs;
|
||||
public List<BlockSizeConfig> SizeConfigs => sizeConfigs;
|
||||
|
||||
@@ -164,11 +165,6 @@ namespace Minigames.FortFight.Core
|
||||
|
||||
public Color DamageColorTint => damageColorTint;
|
||||
|
||||
public float BaseLaunchForce => baseLaunchForce;
|
||||
public float MinForceMultiplier => minForceMultiplier;
|
||||
public float MaxForceMultiplier => maxForceMultiplier;
|
||||
public float TrajectoryLockDuration => trajectoryLockDuration;
|
||||
|
||||
public float VacuumSlideSpeed => vacuumSlideSpeed;
|
||||
public int VacuumDestroyBlockCount => vacuumDestroyBlockCount;
|
||||
public float VacuumBlockDamage => vacuumBlockDamage;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using AppleHills.Core.Settings;
|
||||
using Common.Input;
|
||||
using Core;
|
||||
using Core.Lifecycle;
|
||||
using Minigames.FortFight.Data;
|
||||
using Minigames.FortFight.Projectiles;
|
||||
using UnityEngine;
|
||||
@@ -9,24 +9,14 @@ using UnityEngine;
|
||||
namespace Minigames.FortFight.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Controls slingshot aiming and projectile launching.
|
||||
/// Angry Birds-style drag-to-aim mechanic with trajectory preview.
|
||||
/// Implements ITouchInputConsumer for InputManager integration.
|
||||
/// Controls slingshot aiming and projectile launching for FortFight.
|
||||
/// Extends DragLaunchController with FortFight-specific ammo management and trajectory preview.
|
||||
/// </summary>
|
||||
public class SlingshotController : ManagedBehaviour, ITouchInputConsumer
|
||||
public class SlingshotController : DragLaunchController
|
||||
{
|
||||
#region Inspector Properties
|
||||
|
||||
[Header("Launch Settings")]
|
||||
[Tooltip("Drag distance to reach max force")]
|
||||
[SerializeField] private float maxDragDistance = 5f;
|
||||
|
||||
[Tooltip("Spawn point for projectiles")]
|
||||
[SerializeField] private Transform projectileSpawnPoint;
|
||||
|
||||
[Header("References")]
|
||||
[Tooltip("Trajectory preview component")]
|
||||
[SerializeField] private TrajectoryPreview trajectoryPreview;
|
||||
// Note: trajectoryPreview is inherited from DragLaunchController base class
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -58,8 +48,15 @@ namespace Minigames.FortFight.Core
|
||||
}
|
||||
}
|
||||
|
||||
private float MaxForce => CachedSettings?.BaseLaunchForce ?? 20f;
|
||||
private bool ShowDebugLogs => CachedDevSettings?.SlingshotShowDebugLogs ?? false;
|
||||
protected override SlingshotConfig GetSlingshotConfig()
|
||||
{
|
||||
return CachedSettings?.SlingshotSettings;
|
||||
}
|
||||
|
||||
protected override GameObject GetProjectilePrefab()
|
||||
{
|
||||
return _currentAmmo?.prefab;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -74,13 +71,10 @@ namespace Minigames.FortFight.Core
|
||||
|
||||
#region State
|
||||
|
||||
private bool _isDragging;
|
||||
private Vector2 _dragStartPosition;
|
||||
private ProjectileConfig _currentAmmo;
|
||||
private ProjectileBase _activeProjectile;
|
||||
|
||||
public bool IsDragging => _isDragging;
|
||||
public bool IsEnabled { get; private set; } = true;
|
||||
public ProjectileBase ActiveProjectile => _activeProjectile;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -90,156 +84,56 @@ namespace Minigames.FortFight.Core
|
||||
{
|
||||
base.OnManagedAwake();
|
||||
|
||||
if (projectileSpawnPoint == null)
|
||||
{
|
||||
projectileSpawnPoint = transform;
|
||||
}
|
||||
// Base class handles launchAnchor and trajectoryPreview
|
||||
|
||||
if (trajectoryPreview == null)
|
||||
{
|
||||
trajectoryPreview = GetComponent<TrajectoryPreview>();
|
||||
}
|
||||
// Set debug logging from developer settings
|
||||
showDebugLogs = CachedDevSettings?.SlingshotShowDebugLogs ?? false;
|
||||
}
|
||||
|
||||
internal override void OnManagedStart()
|
||||
|
||||
#endregion
|
||||
|
||||
#region Override Methods
|
||||
|
||||
public override void Enable()
|
||||
{
|
||||
base.OnManagedStart();
|
||||
|
||||
// Hide trajectory by default
|
||||
// Clear any locked trajectory from previous turn
|
||||
if (trajectoryPreview != null)
|
||||
{
|
||||
trajectoryPreview.Hide();
|
||||
trajectoryPreview.ForceHide();
|
||||
}
|
||||
|
||||
base.Enable();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ITouchInputConsumer Implementation
|
||||
|
||||
public void OnTap(Vector2 worldPosition)
|
||||
{
|
||||
// Slingshot 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
|
||||
|
||||
private void StartDrag(Vector2 worldPosition)
|
||||
protected override void StartDrag(Vector2 worldPosition)
|
||||
{
|
||||
// Check ammo before starting drag
|
||||
if (_currentAmmo == null)
|
||||
{
|
||||
if (ShowDebugLogs) Logging.Warning("[SlingshotController] No ammo selected!");
|
||||
if (showDebugLogs) Logging.Warning("[SlingshotController] No ammo selected!");
|
||||
return;
|
||||
}
|
||||
|
||||
_isDragging = true;
|
||||
// Use the projectile spawn point as the anchor, not the touch position
|
||||
// This makes it work like Angry Birds - pull back from slingshot to launch forward
|
||||
_dragStartPosition = projectileSpawnPoint.position;
|
||||
|
||||
// Show trajectory preview
|
||||
if (trajectoryPreview != null)
|
||||
{
|
||||
trajectoryPreview.Show();
|
||||
}
|
||||
|
||||
if (ShowDebugLogs) Logging.Debug($"[SlingshotController] Started drag at {worldPosition}, anchor at spawn point {_dragStartPosition}");
|
||||
base.StartDrag(worldPosition);
|
||||
}
|
||||
|
||||
private void UpdateDrag(Vector2 currentWorldPosition)
|
||||
protected override void PerformLaunch(Vector2 direction, float force)
|
||||
{
|
||||
// Calculate drag vector from spawn point to current drag position
|
||||
// Pull back (away from spawn) = launch forward (toward spawn direction)
|
||||
Vector2 dragVector = _dragStartPosition - currentWorldPosition;
|
||||
|
||||
// Calculate force and direction
|
||||
float dragDistance = dragVector.magnitude;
|
||||
float dragRatio = Mathf.Clamp01(dragDistance / maxDragDistance);
|
||||
|
||||
// Apply configurable max force multiplier
|
||||
float maxMultiplier = CachedSettings?.MaxForceMultiplier ?? 1f;
|
||||
float forceMultiplier = dragRatio * maxMultiplier;
|
||||
float force = forceMultiplier * MaxForce;
|
||||
|
||||
Vector2 direction = dragVector.normalized;
|
||||
|
||||
// Update trajectory preview with projectile mass
|
||||
if (trajectoryPreview != null && _currentAmmo != null)
|
||||
{
|
||||
Vector2 worldStartPos = projectileSpawnPoint.position;
|
||||
float mass = _currentAmmo.GetMass();
|
||||
|
||||
// Debug: Log trajectory calculation (uncomment for debugging)
|
||||
// if (showDebugLogs && Time.frameCount % 30 == 0) // Log every 30 frames to avoid spam
|
||||
// {
|
||||
// Logging.Debug($"[Slingshot] Preview - Force: {force:F2}, Mass: {mass:F2}, Velocity: {force/mass:F2}, Dir: {direction}");
|
||||
// }
|
||||
|
||||
trajectoryPreview.UpdateTrajectory(worldStartPos, direction, force, mass);
|
||||
}
|
||||
LaunchProjectile(direction, force);
|
||||
}
|
||||
|
||||
private void EndDrag(Vector2 currentWorldPosition)
|
||||
protected override float GetProjectileMass()
|
||||
{
|
||||
_isDragging = false;
|
||||
|
||||
// Hide trajectory
|
||||
if (trajectoryPreview != null)
|
||||
if (_currentAmmo == null)
|
||||
{
|
||||
trajectoryPreview.Hide();
|
||||
if (showDebugLogs)
|
||||
Logging.Warning("[SlingshotController] No ammo selected, cannot get mass!");
|
||||
return 1f; // Default fallback
|
||||
}
|
||||
|
||||
// Calculate final launch parameters from spawn point to final drag position
|
||||
Vector2 dragVector = _dragStartPosition - currentWorldPosition;
|
||||
float dragDistance = dragVector.magnitude;
|
||||
float dragRatio = Mathf.Clamp01(dragDistance / maxDragDistance);
|
||||
|
||||
// Apply configurable max force multiplier
|
||||
float maxMultiplier = CachedSettings?.MaxForceMultiplier ?? 1f;
|
||||
float forceMultiplier = dragRatio * maxMultiplier;
|
||||
float force = forceMultiplier * MaxForce;
|
||||
|
||||
Vector2 direction = dragVector.normalized;
|
||||
|
||||
// Check against configurable minimum force threshold
|
||||
float minMultiplier = CachedSettings?.MinForceMultiplier ?? 0.1f;
|
||||
float minForce = minMultiplier * MaxForce;
|
||||
|
||||
// Launch projectile if force exceeds minimum
|
||||
if (force >= minForce)
|
||||
{
|
||||
if (ShowDebugLogs && _currentAmmo != null)
|
||||
{
|
||||
float mass = _currentAmmo.GetMass();
|
||||
float velocity = force / mass;
|
||||
Logging.Debug($"[Slingshot] Launch - Force: {force:F2}, Mass: {mass:F2}, Velocity: {velocity:F2}, Dir: {direction}");
|
||||
}
|
||||
|
||||
LaunchProjectile(direction, force);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ShowDebugLogs) Logging.Debug($"[SlingshotController] Drag too short - force {force:F2} < min {minForce:F2}");
|
||||
}
|
||||
// Read from ProjectileConfig settings - same source as ProjectileBase.Initialize()
|
||||
return _currentAmmo.mass;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -252,7 +146,7 @@ namespace Minigames.FortFight.Core
|
||||
public void SetAmmo(ProjectileConfig ammoConfig)
|
||||
{
|
||||
_currentAmmo = ammoConfig;
|
||||
if (ShowDebugLogs) Logging.Debug($"[SlingshotController] Ammo set to: {ammoConfig?.displayName ?? "null"}");
|
||||
if (showDebugLogs) Logging.Debug($"[SlingshotController] Ammo set to: {ammoConfig?.displayName ?? "null"}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -266,8 +160,8 @@ namespace Minigames.FortFight.Core
|
||||
return;
|
||||
}
|
||||
|
||||
// Spawn projectile
|
||||
GameObject projectileObj = Instantiate(_currentAmmo.prefab, projectileSpawnPoint.position, Quaternion.identity);
|
||||
// Spawn projectile at launch anchor
|
||||
GameObject projectileObj = Instantiate(_currentAmmo.prefab, launchAnchor.position, Quaternion.identity);
|
||||
_activeProjectile = projectileObj.GetComponent<ProjectileBase>();
|
||||
|
||||
if (_activeProjectile == null)
|
||||
@@ -286,11 +180,11 @@ namespace Minigames.FortFight.Core
|
||||
// Lock trajectory to show the shot path
|
||||
if (trajectoryPreview != null)
|
||||
{
|
||||
float lockDuration = CachedSettings?.TrajectoryLockDuration ?? 2f;
|
||||
float lockDuration = Config?.trajectoryLockDuration ?? 2f;
|
||||
trajectoryPreview.LockTrajectory(lockDuration);
|
||||
}
|
||||
|
||||
if (ShowDebugLogs) Logging.Debug($"[SlingshotController] Launched {_currentAmmo?.displayName ?? "projectile"} with force {force}");
|
||||
if (showDebugLogs) Logging.Debug($"[SlingshotController] Launched {_currentAmmo?.displayName ?? "projectile"} with force {force}");
|
||||
|
||||
// Fire event
|
||||
OnProjectileLaunched?.Invoke(_activeProjectile);
|
||||
@@ -324,7 +218,7 @@ namespace Minigames.FortFight.Core
|
||||
float speed = velocity.magnitude;
|
||||
float force = mass * speed;
|
||||
|
||||
if (ShowDebugLogs)
|
||||
if (showDebugLogs)
|
||||
{
|
||||
Logging.Debug($"[Slingshot] LaunchWithVelocity - Velocity: {velocity}, Mass: {mass:F2}, Force: {force:F2}");
|
||||
}
|
||||
@@ -341,34 +235,7 @@ namespace Minigames.FortFight.Core
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Enable/Disable
|
||||
|
||||
/// <summary>
|
||||
/// Enable slingshot (allow aiming/launching)
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
IsEnabled = true;
|
||||
if (ShowDebugLogs) Logging.Debug("[SlingshotController] Enabled");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disable slingshot (prevent aiming/launching)
|
||||
/// </summary>
|
||||
public void Disable()
|
||||
{
|
||||
IsEnabled = false;
|
||||
_isDragging = false;
|
||||
|
||||
if (trajectoryPreview != null)
|
||||
{
|
||||
trajectoryPreview.Hide();
|
||||
}
|
||||
|
||||
if (ShowDebugLogs) Logging.Debug("[SlingshotController] Disabled");
|
||||
}
|
||||
|
||||
#endregion
|
||||
// Note: Enable/Disable methods now handled by base DragLaunchController class
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
using Core;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Minigames.FortFight.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Displays trajectory prediction line for projectile launches.
|
||||
/// Shows dotted line preview of projectile arc.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(LineRenderer))]
|
||||
public class TrajectoryPreview : MonoBehaviour
|
||||
{
|
||||
[Header("Trajectory Settings")]
|
||||
[Tooltip("Number of points to simulate (physics steps)")]
|
||||
[SerializeField] private int simulationSteps = 50;
|
||||
|
||||
[Header("Visual")]
|
||||
[Tooltip("Color of trajectory line")]
|
||||
[SerializeField] private Color lineColor = Color.yellow;
|
||||
|
||||
[Tooltip("Width of trajectory line")]
|
||||
[SerializeField] private float lineWidth = 0.1f;
|
||||
|
||||
private LineRenderer lineRenderer;
|
||||
private bool isLocked = false;
|
||||
private float lockTimer = 0f;
|
||||
private float lockDuration = 0f;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
lineRenderer = GetComponent<LineRenderer>();
|
||||
|
||||
// Configure line renderer
|
||||
if (lineRenderer != null)
|
||||
{
|
||||
lineRenderer.startWidth = lineWidth;
|
||||
lineRenderer.endWidth = lineWidth;
|
||||
lineRenderer.startColor = lineColor;
|
||||
lineRenderer.endColor = lineColor;
|
||||
lineRenderer.positionCount = simulationSteps;
|
||||
lineRenderer.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (isLocked)
|
||||
{
|
||||
lockTimer += Time.deltaTime;
|
||||
if (lockTimer >= lockDuration)
|
||||
{
|
||||
isLocked = false;
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show the trajectory preview
|
||||
/// </summary>
|
||||
public void Show()
|
||||
{
|
||||
if (lineRenderer != null)
|
||||
{
|
||||
lineRenderer.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide the trajectory preview (unless locked)
|
||||
/// </summary>
|
||||
public void Hide()
|
||||
{
|
||||
// Don't hide if trajectory is locked
|
||||
if (isLocked)
|
||||
return;
|
||||
|
||||
if (lineRenderer != null)
|
||||
{
|
||||
lineRenderer.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lock the current trajectory display for a duration
|
||||
/// </summary>
|
||||
public void LockTrajectory(float duration)
|
||||
{
|
||||
isLocked = true;
|
||||
lockTimer = 0f;
|
||||
lockDuration = duration;
|
||||
|
||||
// Ensure line is visible
|
||||
if (lineRenderer != null)
|
||||
{
|
||||
lineRenderer.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the trajectory preview with new parameters.
|
||||
/// Uses Physics.fixedDeltaTime for accurate simulation matching Unity's physics.
|
||||
/// </summary>
|
||||
/// <param name="startPosition">Starting position of trajectory</param>
|
||||
/// <param name="direction">Launch direction (normalized)</param>
|
||||
/// <param name="force">Launch force (impulse)</param>
|
||||
/// <param name="mass">Projectile mass</param>
|
||||
public void UpdateTrajectory(Vector2 startPosition, Vector2 direction, float force, float mass = 1f)
|
||||
{
|
||||
if (lineRenderer == null) return;
|
||||
|
||||
// Calculate initial velocity: impulse force F gives velocity v = F/m
|
||||
Vector2 startVelocity = (direction * force) / mass;
|
||||
|
||||
// Get gravity with projectile gravity scale from settings
|
||||
var settings = GameManager.GetSettingsObject<AppleHills.Core.Settings.IFortFightSettings>();
|
||||
float gravityScale = settings?.ProjectileGravityScale ?? 1f;
|
||||
Vector2 gravity = new Vector2(Physics2D.gravity.x, Physics2D.gravity.y) * gravityScale;
|
||||
|
||||
// Simulate trajectory using Unity's physics time step
|
||||
Vector3[] points = new Vector3[simulationSteps];
|
||||
Vector2 pos = startPosition;
|
||||
Vector2 vel = startVelocity;
|
||||
|
||||
for (int i = 0; i < simulationSteps; i++)
|
||||
{
|
||||
// Set current position
|
||||
points[i] = new Vector3(pos.x, pos.y, 0);
|
||||
|
||||
// Update velocity (gravity applied over fixedDeltaTime)
|
||||
vel = vel + gravity * Time.fixedDeltaTime;
|
||||
|
||||
// Update position (velocity applied over fixedDeltaTime)
|
||||
pos = pos + vel * Time.fixedDeltaTime;
|
||||
|
||||
// Optional: Stop if hits ground (y < threshold)
|
||||
if (pos.y < -10f)
|
||||
{
|
||||
// Fill remaining points at ground level
|
||||
for (int j = i + 1; j < simulationSteps; j++)
|
||||
{
|
||||
points[j] = new Vector3(pos.x, -10f, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lineRenderer.positionCount = simulationSteps;
|
||||
lineRenderer.SetPositions(points);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b1e26667c6d4415f8dc51e4a58ba9479
|
||||
timeCreated: 1764682615
|
||||
@@ -61,5 +61,15 @@
|
||||
Medium, // Moderate deviations, moderate thinking
|
||||
Hard // Minimal deviations, faster thinking
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Camera states for Fort Fight minigame
|
||||
/// </summary>
|
||||
public enum FortFightCameraState
|
||||
{
|
||||
WideView, // Shows entire battlefield
|
||||
PlayerOne, // Player 1's view
|
||||
PlayerTwo, // Player 2's view
|
||||
Projectile // Follows projectile in flight
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user