2025-09-24 11:24:38 +05:00

175 lines
6.5 KiB
C#

namespace TPSBR
{
using System;
using System.Collections.Generic;
using UnityEngine;
using Fusion;
using Fusion.Addons.InterestManagement;
using Fusion.Addons.KCC;
public sealed class AgentInterestView : PlayerInterestView
{
// PRIVATE MEMBERS
[SerializeField]
private PlayerInterestConfig _lookInterest = new PlayerInterestConfig();
[SerializeField]
private PlayerInterestConfig _shootInterest = new PlayerInterestConfig();
private KCC _kcc;
private KCCShapeCastInfo _shapeCastInfo = new KCCShapeCastInfo();
private List<AgentInterestView> _otherPlayerViews = new List<AgentInterestView>();
private static Func<AgentInterestView, AgentInterestView, float, float, float, bool, bool> _filterPlayersByView = (otherPlayerView, localPlayerView, sqrMinDistance, sqrMaxDistance, maxAngleCos, raycast) => FilterPlayersByView(otherPlayerView, localPlayerView, sqrMinDistance, sqrMaxDistance, maxAngleCos, raycast);
// PUBLIC METHODS
public void UpdateShootInterestTargets()
{
float maxViewAngleCos = Mathf.Cos(_shootInterest.MaxViewAngle * Mathf.Deg2Rad);
GlobalInterestManager globalInterestManager = Runner.GetGlobalInterestManager();
globalInterestManager.GetPlayerViews(_otherPlayerViews, _filterPlayersByView, this, _shootInterest.MinViewDistance * _shootInterest.MinViewDistance, _shootInterest.MaxViewDistance * _shootInterest.MaxViewDistance, maxViewAngleCos, false);
// The agent is shooting but other players not be interest yet (looking in a different direction).
// We add an interest shape around agent transform for all players in front of him.
for (int i = 0, count = _otherPlayerViews.Count; i < count; ++i)
{
PlayerInterestView otherPlayerView = _otherPlayerViews[i];
otherPlayerView.RegisterProvider(_shootInterest.Provider, _shootInterest.Duration);
}
}
// PlayerInterestView INTERFACE
protected override void OnViewSpawned()
{
_kcc = GetComponent<KCC>();
if (Runner.TryGetPlayerInterestManager(Object.InputAuthority, out PlayerInterestManager playerInterestManager) == true)
{
playerInterestManager.InterestView = this;
}
}
protected override void OnViewDespawned(NetworkRunner runner, bool hasState)
{
_lookInterest.Provider.Release();
_shootInterest.Provider.Release();
if (runner.TryGetPlayerInterestManager(Object.InputAuthority, out PlayerInterestManager playerInterestManager) == true)
{
if (playerInterestManager.InterestView == this)
{
playerInterestManager.InterestView = null;
}
}
}
protected override void OnViewFixedUpdateNetwork()
{
if (Runner.IsServer == true && Runner.IsForward == true)
{
// Check player in front only once per second.
int tickRate = TickRate.Resolve(Runner.Config.Simulation.TickRateSelection).Server;
if ((Runner.Tick.Raw % tickRate) == (Object.InputAuthority.AsIndex % tickRate))
{
UpdateLookInterestTargets();
}
}
}
protected override void OnDrawGizmosForPlayer(PlayerInterestView playerView, bool isSelected)
{
if (isSelected == false)
return;
Vector3 cameraPosition = playerView.CameraPosition;
Quaternion cameraRotation = playerView.CameraRotation;
if (Application.isPlaying == false)
{
playerView.Transform.GetPositionAndRotation(out cameraPosition, out cameraRotation);
}
if (_lookInterest.Provider != null)
{
InterestUtility.DrawAngle(cameraPosition, cameraRotation, _lookInterest.MaxViewAngle, _lookInterest.MinViewDistance, _lookInterest.MaxViewDistance, _lookInterest.GizmoColor);
}
if (_shootInterest.Provider != null)
{
InterestUtility.DrawAngle(cameraPosition, cameraRotation, _shootInterest.MaxViewAngle, _shootInterest.MinViewDistance, _shootInterest.MaxViewDistance, _shootInterest.GizmoColor);
}
InterestUtility.DrawCameraFrustum(Camera.main, cameraPosition, cameraRotation, Color.red);
}
// PRIVATE METHODS
private void UpdateLookInterestTargets()
{
float maxViewAngleCos = Mathf.Cos(_lookInterest.MaxViewAngle * Mathf.Deg2Rad);
GlobalInterestManager globalInterestManager = Runner.GetGlobalInterestManager();
globalInterestManager.GetPlayerViews(_otherPlayerViews, _filterPlayersByView, this, _lookInterest.MinViewDistance * _lookInterest.MinViewDistance, _lookInterest.MaxViewDistance * _lookInterest.MaxViewDistance, maxViewAngleCos, true);
// Add interest shapes around other players who are visible on screen, but not covered by default interest shapes of the player.
for (int i = 0, count = _otherPlayerViews.Count; i < count; ++i)
{
PlayerInterestConfig otherPlayerConfig = ((AgentInterestView)_otherPlayerViews[i])._lookInterest;
if (otherPlayerConfig.Duration > 0.0f && otherPlayerConfig.Provider != null)
{
RegisterProvider(otherPlayerConfig.Provider, otherPlayerConfig.Duration);
}
}
}
private static bool FilterPlayersByView(AgentInterestView otherPlayerView, AgentInterestView localPlayerView, float sqrMinDistance, float sqrMaxDistance, float maxAngleCos, bool raycast)
{
if (ReferenceEquals(otherPlayerView, localPlayerView) == true)
return false;
Vector3 positionDifference = otherPlayerView.PlayerPosition - localPlayerView.CameraPosition;
float sqrDistance = Vector3.SqrMagnitude(positionDifference);
if (sqrDistance > sqrMaxDistance || sqrDistance < sqrMinDistance)
return false;
float dot = Vector3.Dot(localPlayerView.CameraDirection, Vector3.Normalize(positionDifference));
if (dot < maxAngleCos)
return false;
if (raycast == true)
{
KCC kcc = localPlayerView._kcc;
KCCShapeCastInfo shapeCastInfo = localPlayerView._shapeCastInfo;
Vector3 fromToVector = (otherPlayerView.PlayerPosition + Vector3.up) - localPlayerView.CameraPosition;
float distance = fromToVector.magnitude;
if (kcc.RayCast(shapeCastInfo, localPlayerView.CameraPosition, fromToVector / distance, distance, QueryTriggerInteraction.Ignore) == true)
{
if (ReferenceEquals(otherPlayerView, shapeCastInfo.ColliderHits[0].Transform.GetComponentInParent<AgentInterestView>()) == true)
return true;
}
return false;
}
return true;
}
[Serializable]
private sealed class PlayerInterestConfig
{
public AgentInterestProvider Provider;
public float Duration = 1.0f;
[Range(0.0f, 90.0f)]
public float MaxViewAngle = 25.0f;
public float MinViewDistance = 10.0f;
public float MaxViewDistance = 100.0f;
public Color GizmoColor = Color.white;
}
}
}