245 lines
7.6 KiB
C#
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();
|
|
}
|
|
}
|