Adding a ton of funcitonality to the builder's movement and build queue
This commit is contained in:
parent
a63cce53e2
commit
f05734e19b
31 changed files with 3104 additions and 339 deletions
|
|
@ -12,33 +12,29 @@ namespace TD.Gameplay
|
|||
/// requests to <see cref="TowerPlacementManager"/> via RPC.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>Plain MonoBehaviour.</b> Placement visuals (ghost, cursor color) are
|
||||
/// <para><b>Plain MonoBehaviour.</b> Placement visuals (cursor ghost, color) are
|
||||
/// purely cosmetic and local. This component does not need to be a NetworkBehaviour.
|
||||
/// All server-authoritative logic lives in <see cref="TowerPlacementManager"/>.</para>
|
||||
///
|
||||
/// <para><b>Ghost validity check.</b> The ghost checks ownership, placement state,
|
||||
/// and tile occupancy only. It does NOT run a local path check. If the server rejects
|
||||
/// because the tower would block the path, the ghost disappears and a rejection
|
||||
/// message is shown. This avoids the complexity of maintaining a client-side BFS
|
||||
/// that may be slightly stale.</para>
|
||||
/// <para><b>Ghost validity check.</b> The cursor ghost checks ownership, placement
|
||||
/// state, and tile occupancy only. It does NOT run a local path check. If the server
|
||||
/// rejects because the tower would block the path, the cursor ghost disappears and a
|
||||
/// rejection message is shown.</para>
|
||||
///
|
||||
/// <para><b>Ghost colors.</b>
|
||||
/// <list type="bullet">
|
||||
/// <item>White — all local checks pass.</item>
|
||||
/// <item>Red — any local check fails (wrong zone, not buildable, already occupied).</item>
|
||||
/// </list>
|
||||
/// Green "pending construction" ghost is a separate system implemented in Path D.</para>
|
||||
/// <para><b>Two ghosts, two systems.</b> The <i>cursor ghost</i> (handled here)
|
||||
/// follows the mouse and turns white/red. The <i>build-site visual</i> (in D2,
|
||||
/// handled by Builder + BuildSiteVisual) is the green queued ghost or staged
|
||||
/// construction visual that appears at a confirmed placement site. They are
|
||||
/// distinct prefabs and lifecycles.</para>
|
||||
///
|
||||
/// <para><b>Placement activation.</b> The controller is idle until
|
||||
/// <see cref="BeginPlacement"/> is called (e.g., from a HUD tower button). The player
|
||||
/// right-clicks or the placement is confirmed/rejected to return to idle.</para>
|
||||
/// <para><b>Chained queueing.</b> Holding Shift while left-clicking submits the
|
||||
/// placement and stays in placement mode for another submission. Releasing Shift
|
||||
/// before the click submits and exits placement (single-shot). Right-click always
|
||||
/// cancels.</para>
|
||||
///
|
||||
/// <para><b>Input System.</b> Uses the New Input System package. Mouse position and
|
||||
/// button state are read from <c>Mouse.current</c> each frame.</para>
|
||||
///
|
||||
/// <para><b>Player slot.</b> The local player slot is currently a stub
|
||||
/// (client 0 = Player1, etc.) matching <c>TowerPlacementManager.ClientIdToPlayerSlot</c>.
|
||||
/// This will be replaced when MatchState carries the authoritative slot assignment.</para>
|
||||
/// <para><b>Input System.</b> Uses the New Input System package. Mouse and modifier
|
||||
/// state are read directly from <c>Mouse.current</c> and <c>Keyboard.current</c>
|
||||
/// each frame. No InputAction asset needed for these placement-specific bindings.</para>
|
||||
/// </remarks>
|
||||
public class TowerPlacementController : MonoBehaviour
|
||||
{
|
||||
|
|
@ -62,7 +58,7 @@ namespace TD.Gameplay
|
|||
private TowerDefinition activeDef;
|
||||
private int activeTowerTypeId;
|
||||
|
||||
// The ghost GameObject: the tower prefab instantiated with transparent materials.
|
||||
// The cursor ghost GameObject: the tower prefab instantiated with transparent materials.
|
||||
// Null when placement mode is inactive.
|
||||
private GameObject ghostGO;
|
||||
|
||||
|
|
@ -131,7 +127,7 @@ namespace TD.Gameplay
|
|||
// Compute the footprint anchor from the hit point.
|
||||
Vector2Int anchor = ComputeAnchor(hitPoint, activeDef.FootprintSize);
|
||||
|
||||
// Position the ghost at the footprint center.
|
||||
// Position the cursor ghost at the footprint center.
|
||||
Vector3 ghostPos = GridCoordinates.GetFootprintCenterWorld(anchor, activeDef.FootprintSize);
|
||||
ghostPos.y = 0.5f; // lift off the plane so the cube base sits flush
|
||||
ghostGO.transform.position = ghostPos;
|
||||
|
|
@ -151,7 +147,8 @@ namespace TD.Gameplay
|
|||
// Left-click to attempt placement.
|
||||
if (mouse.leftButton.wasPressedThisFrame)
|
||||
{
|
||||
TrySubmitPlacement(anchor);
|
||||
bool chained = IsShiftHeld();
|
||||
TrySubmitPlacement(anchor, chained);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -161,9 +158,6 @@ namespace TD.Gameplay
|
|||
/// Activates placement mode for the given tower type. The ghost appears
|
||||
/// immediately under the cursor. Call this from HUD tower buttons.
|
||||
/// </summary>
|
||||
/// <param name="def">The TowerDefinition to place.</param>
|
||||
/// <param name="towerTypeId">The type ID registered in
|
||||
/// <see cref="TowerPlacementManager"/>.</param>
|
||||
public void BeginPlacement(TowerDefinition def, int towerTypeId)
|
||||
{
|
||||
if (def == null)
|
||||
|
|
@ -199,6 +193,15 @@ namespace TD.Gameplay
|
|||
/// </summary>
|
||||
public bool IsPlacing => activeDef != null;
|
||||
|
||||
// ----- Modifier helpers -------------------------------------------
|
||||
|
||||
private static bool IsShiftHeld()
|
||||
{
|
||||
var kb = Keyboard.current;
|
||||
if (kb == null) return false;
|
||||
return kb.leftShiftKey.isPressed || kb.rightShiftKey.isPressed;
|
||||
}
|
||||
|
||||
// ----- Ghost management -------------------------------------------
|
||||
|
||||
private void CreateGhost(TowerDefinition def)
|
||||
|
|
@ -284,10 +287,6 @@ namespace TD.Gameplay
|
|||
foreach (var rend in ghostRenderers)
|
||||
{
|
||||
rend.sharedMaterial = ghostMat;
|
||||
// Property block is set empty here — color comes from the material itself.
|
||||
// If the ghost materials use _Color, override it via the block instead:
|
||||
// ghostPropertyBlock.SetColor(ColorPropertyId, valid ? Color.white : Color.red);
|
||||
// rend.SetPropertyBlock(ghostPropertyBlock);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -315,11 +314,6 @@ namespace TD.Gameplay
|
|||
/// Converts a world hit point to the footprint anchor tile (SW corner) such
|
||||
/// that the footprint center is as close as possible to the hit point.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For a 2×2 footprint: anchor = (Round(hitX - 0.5), Round(hitZ - 0.5))
|
||||
/// For a 1×1 footprint: anchor = (Round(hitX), Round(hitZ))
|
||||
/// For a 3×3 footprint: anchor = (Round(hitX - 1.0), Round(hitZ - 1.0))
|
||||
/// </remarks>
|
||||
private static Vector2Int ComputeAnchor(Vector3 hitPoint, Vector2Int footprintSize)
|
||||
{
|
||||
float t = GridCoordinates.TILE_SIZE;
|
||||
|
|
@ -357,7 +351,7 @@ namespace TD.Gameplay
|
|||
|
||||
// ----- Placement submission ---------------------------------------
|
||||
|
||||
private void TrySubmitPlacement(Vector2Int anchor)
|
||||
private void TrySubmitPlacement(Vector2Int anchor, bool chained)
|
||||
{
|
||||
var manager = TowerPlacementManager.Instance;
|
||||
if (manager == null)
|
||||
|
|
@ -369,13 +363,19 @@ namespace TD.Gameplay
|
|||
|
||||
// Send the RPC regardless of local validity state — the server is
|
||||
// authoritative. The local check drives the ghost color only.
|
||||
// The server will reject and send back a reason if invalid.
|
||||
manager.RequestPlaceTowerRpc(anchor.x, anchor.y, activeTowerTypeId);
|
||||
|
||||
// Exit placement mode immediately after submitting. If the server
|
||||
// rejects, the rejection message fires via HandlePlacementRejected.
|
||||
// If it accepts, the TowerInstance NetworkObject spawns and the
|
||||
// placed tower appears — no ghost lingering is needed.
|
||||
if (chained)
|
||||
{
|
||||
// Stay in placement mode. The server will stamp occupancy on success,
|
||||
// so when EvaluateLocalValidity runs next frame it will correctly
|
||||
// report the just-clicked tile as occupied (cursor turns red there).
|
||||
// Force a re-evaluation by invalidating the cached anchor.
|
||||
lastAnchorValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Single-shot: exit placement mode immediately.
|
||||
CancelPlacement();
|
||||
}
|
||||
|
||||
|
|
@ -395,7 +395,6 @@ namespace TD.Gameplay
|
|||
Debug.Log($"[TowerPlacementController] Placement rejected: {reason} → \"{message}\"");
|
||||
|
||||
// Fire the event so HUD components can display the message on screen.
|
||||
// The HUD that subscribes and renders this is implemented in a later path.
|
||||
OnRejectionMessageReady?.Invoke(message);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue