Files
BABA_YAGA/Assets/Scripts/Trap/TrapPlacementController.cs
2026-07-02 08:59:23 +07:00

245 lines
7.6 KiB
C#

using Fusion;
using UnityEngine;
public class TrapPlacementController : NetworkBehaviour
{
[Header("Placement Setup")]
[SerializeField] private TrapDataSO[] availableTraps;
[SerializeField] private LayerMask placementLayerMask;
private PlayerData playerData;
private int selectedTrapIndex = 0;
private GameObject currentGhostInstance;
private bool isPreviewing = false;
private float[] cooldownTimers;
public override void Spawned()
{
playerData = GetComponent<PlayerData>();
// Initialize cooldown tracking for each trap type
if (availableTraps != null)
{
cooldownTimers = new float[availableTraps.Length];
}
}
void Update()
{
// Only run placement logic for the local player who is a Trapper
if (!Object.HasInputAuthority) return;
if (playerData == null || playerData.PlayerRole != _Role.Trapper) return;
// Handle cooldown timers update
UpdateCooldowns();
// 1. Input: Select Trap Type via Alpha Keys (1, 2, 3, 4)
HandleTrapSelectionInput();
// 2. Input: Toggle Preview (Q key)
if (Input.GetKeyDown(KeyCode.Q))
{
TogglePreview();
}
// 3. Update Preview & Handle Placement Click
if (isPreviewing)
{
UpdateGhostPosition();
if (Input.GetMouseButtonDown(0)) // Left Click to Place
{
TryPlaceTrap();
}
else if (Input.GetMouseButtonDown(1)) // Right Click to Cancel
{
CancelPreview();
}
}
}
private void UpdateCooldowns()
{
for (int i = 0; i < cooldownTimers.Length; ++i)
{
if (cooldownTimers[i] > 0)
{
cooldownTimers[i] -= Time.deltaTime;
}
}
}
private void HandleTrapSelectionInput()
{
for (int i = 0; i < availableTraps.Length; ++i)
{
if (Input.GetKeyDown(KeyCode.Alpha1 + i))
{
if (selectedTrapIndex != i)
{
selectedTrapIndex = i;
Debug.Log($"Selected trap: {availableTraps[selectedTrapIndex].TrapName}");
// If already previewing, recreate ghost for the new type
if (isPreviewing)
{
DestroyGhost();
CreateGhost();
}
}
}
}
}
private void TogglePreview()
{
if (isPreviewing)
{
CancelPreview();
}
else
{
CreateGhost();
}
}
private void CreateGhost()
{
if (selectedTrapIndex < 0 || selectedTrapIndex >= availableTraps.Length) return;
var data = availableTraps[selectedTrapIndex];
if (data.GhostPrefab != null)
{
currentGhostInstance = Instantiate(data.GhostPrefab);
isPreviewing = true;
}
}
private void CancelPreview()
{
DestroyGhost();
isPreviewing = false;
}
private void DestroyGhost()
{
if (currentGhostInstance != null)
{
Destroy(currentGhostInstance);
currentGhostInstance = null;
}
}
private void UpdateGhostPosition()
{
if (currentGhostInstance == null) return;
// Perform raycast from viewport center
Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
var data = availableTraps[selectedTrapIndex];
if (Physics.Raycast(ray, out RaycastHit hit, data.PlacementMaxDistance, placementLayerMask))
{
currentGhostInstance.SetActive(true);
currentGhostInstance.transform.position = hit.point;
// Align rotation with the surface normal
currentGhostInstance.transform.rotation = Quaternion.FromToRotation(Vector3.up, hit.normal);
// Validate placement (make sure no walls, players, or other traps block it)
bool isValid = ValidatePlacement(hit.point);
UpdateGhostVisuals(isValid);
}
else
{
currentGhostInstance.SetActive(false); // Hide preview when looking in the air/out of bounds
}
}
private bool ValidatePlacement(Vector3 position)
{
// Simple check to ensure we don't stack traps on top of each other or clip through walls
Collider[] overlaps = Physics.OverlapSphere(position, 0.5f, LayerMask.GetMask("Walls", "Trap"));
return overlaps.Length == 0;
}
private void UpdateGhostVisuals(bool isValid)
{
if (currentGhostInstance == null) return;
// Visual indicator: Green transparency for valid, red for invalid
Renderer[] renderers = currentGhostInstance.GetComponentsInChildren<Renderer>();
Color ghostColor = isValid ? new Color(0f, 1f, 0f, 0.4f) : new Color(1f, 0f, 0f, 0.4f);
foreach (var r in renderers)
{
foreach (var mat in r.materials)
{
// Set standard rendering properties to handle transparent coloring
mat.SetFloat("_Mode", 3); // Transparent mode
mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
mat.SetInt("_ZWrite", 0);
mat.DisableKeyword("_ALPHATEST_ON");
mat.EnableKeyword("_ALPHABLEND_ON");
mat.DisableKeyword("_ALPHAPREMULTIPLY_ON");
mat.renderQueue = 3000;
mat.color = ghostColor;
}
}
}
private void TryPlaceTrap()
{
if (selectedTrapIndex < 0 || selectedTrapIndex >= availableTraps.Length) return;
// Check local cooldown
if (cooldownTimers[selectedTrapIndex] > 0)
{
Debug.LogWarning($"{availableTraps[selectedTrapIndex].TrapName} is on cooldown!");
return;
}
var data = availableTraps[selectedTrapIndex];
Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0));
if (Physics.Raycast(ray, out RaycastHit hit, data.PlacementMaxDistance, placementLayerMask))
{
if (ValidatePlacement(hit.point))
{
// Send placement request RPC to host
RPC_RequestPlaceTrap(hit.point, Quaternion.FromToRotation(Vector3.up, hit.normal), selectedTrapIndex);
// Set cooldown timer
cooldownTimers[selectedTrapIndex] = data.Cooldown;
CancelPreview();
}
}
}
[Rpc(RpcSources.InputAuthority, RpcTargets.StateAuthority)]
private void RPC_RequestPlaceTrap(Vector3 position, Quaternion rotation, int trapIndex)
{
if (trapIndex < 0 || trapIndex >= availableTraps.Length) return;
TrapDataSO data = availableTraps[trapIndex];
// Host spawns the networked trap prefab
NetworkObject spawnedTrapObj = Runner.Spawn(data.TrapPrefab, position, rotation, Object.InputAuthority);
// Configure ownership
TrapBase trap = spawnedTrapObj.GetComponent<TrapBase>();
if (trap != null)
{
trap.Owner = Object.InputAuthority;
}
Debug.Log($"[Host] Spawned trap '{data.TrapName}' requested by Player {Object.InputAuthority.PlayerId}");
}
private void OnDestroy()
{
DestroyGhost();
}
}