207 lines
6.1 KiB
C#
207 lines
6.1 KiB
C#
using MegaKoop.Game.Networking;
|
|
using Steamworks;
|
|
using UnityEngine;
|
|
|
|
namespace MegaKoop.Game.Vfx
|
|
{
|
|
[DefaultExecutionOrder(-50)]
|
|
public class ProjectileImpactVfxService : MonoBehaviour
|
|
{
|
|
private const float MinLifetime = 0.5f;
|
|
|
|
private static ProjectileImpactVfxService instance;
|
|
|
|
private SteamCoopNetworkManager networkManager;
|
|
private bool isRegistered;
|
|
private ProjectileImpactVfxSettings settings;
|
|
|
|
public static ProjectileImpactVfxService Instance
|
|
{
|
|
get
|
|
{
|
|
if (instance == null)
|
|
{
|
|
instance = FindObjectOfType<ProjectileImpactVfxService>();
|
|
if (instance == null)
|
|
{
|
|
var go = new GameObject(nameof(ProjectileImpactVfxService));
|
|
instance = go.AddComponent<ProjectileImpactVfxService>();
|
|
}
|
|
}
|
|
|
|
return instance;
|
|
}
|
|
}
|
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
|
private static void AutoCreate()
|
|
{
|
|
_ = Instance;
|
|
}
|
|
|
|
public static void ReportImpact(ProjectileImpactKind kind, Vector3 position, Vector3 normal)
|
|
{
|
|
Instance.InternalReportImpact(kind, position, normal);
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
if (instance != null && instance != this)
|
|
{
|
|
Destroy(gameObject);
|
|
return;
|
|
}
|
|
|
|
instance = this;
|
|
DontDestroyOnLoad(gameObject);
|
|
LoadSettings();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
TryBindNetworkManager();
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
TryBindNetworkManager();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
UnregisterHandler();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (instance == this)
|
|
{
|
|
instance = null;
|
|
}
|
|
|
|
UnregisterHandler();
|
|
}
|
|
|
|
private void LoadSettings()
|
|
{
|
|
if (settings != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
settings = ProjectileImpactVfxSettings.LoadFromResources();
|
|
if (settings == null)
|
|
{
|
|
Debug.LogWarning($"[ProjectileImpactVfxService] Could not locate Resources/{ProjectileImpactVfxSettings.ResourcePath}. Impact particles will be skipped.");
|
|
}
|
|
}
|
|
|
|
private void TryBindNetworkManager()
|
|
{
|
|
var current = SteamCoopNetworkManager.Instance;
|
|
if (current != networkManager)
|
|
{
|
|
UnregisterHandler();
|
|
networkManager = current;
|
|
}
|
|
|
|
if (networkManager != null && !isRegistered)
|
|
{
|
|
networkManager.RegisterHandler(NetworkMessageType.ProjectileImpact, HandleImpactMessage);
|
|
isRegistered = true;
|
|
}
|
|
}
|
|
|
|
private void UnregisterHandler()
|
|
{
|
|
if (isRegistered && networkManager != null)
|
|
{
|
|
networkManager.UnregisterHandler(NetworkMessageType.ProjectileImpact, HandleImpactMessage);
|
|
}
|
|
|
|
isRegistered = false;
|
|
}
|
|
|
|
private void HandleImpactMessage(NetworkMessage message)
|
|
{
|
|
ProjectileImpactMessage impact = ProjectileImpactMessage.Deserialize(message.Payload);
|
|
SpawnImpact(impact.Kind, impact.Position, impact.Normal);
|
|
}
|
|
|
|
private void InternalReportImpact(ProjectileImpactKind kind, Vector3 position, Vector3 normal)
|
|
{
|
|
SpawnImpact(kind, position, normal);
|
|
|
|
TryBindNetworkManager();
|
|
if (networkManager == null || !networkManager.IsHost)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var impactMessage = new ProjectileImpactMessage(kind, position, normal);
|
|
byte[] payload = ProjectileImpactMessage.Serialize(impactMessage);
|
|
networkManager.SendToAll(NetworkMessageType.ProjectileImpact, payload, EP2PSend.k_EP2PSendUnreliableNoDelay);
|
|
}
|
|
|
|
private void SpawnImpact(ProjectileImpactKind kind, Vector3 position, Vector3 normal)
|
|
{
|
|
LoadSettings();
|
|
if (settings == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
GameObject prefab = kind switch
|
|
{
|
|
ProjectileImpactKind.Hero => settings.HeroImpactPrefab,
|
|
ProjectileImpactKind.Enemy => settings.EnemyImpactPrefab,
|
|
_ => null
|
|
};
|
|
|
|
if (prefab == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Quaternion rotation = normal.sqrMagnitude > 0.001f ? Quaternion.LookRotation(normal) : Quaternion.identity;
|
|
GameObject instance = Instantiate(prefab, position, rotation);
|
|
float lifetime = EstimateLifetime(instance);
|
|
if (lifetime > 0f)
|
|
{
|
|
Destroy(instance, lifetime);
|
|
}
|
|
}
|
|
|
|
private float EstimateLifetime(GameObject effectInstance)
|
|
{
|
|
float fallback = settings != null ? settings.FallbackLifetime : 4f;
|
|
ParticleSystem[] particleSystems = effectInstance.GetComponentsInChildren<ParticleSystem>();
|
|
if (particleSystems == null || particleSystems.Length == 0)
|
|
{
|
|
return Mathf.Max(MinLifetime, fallback);
|
|
}
|
|
|
|
float maxLifetime = fallback;
|
|
foreach (ParticleSystem system in particleSystems)
|
|
{
|
|
var main = system.main;
|
|
if (main.loop)
|
|
{
|
|
maxLifetime = Mathf.Max(maxLifetime, fallback);
|
|
continue;
|
|
}
|
|
|
|
float candidate = main.duration + main.startLifetime.constantMax;
|
|
if (candidate <= 0f)
|
|
{
|
|
candidate = fallback;
|
|
}
|
|
|
|
maxLifetime = Mathf.Max(maxLifetime, candidate);
|
|
}
|
|
|
|
return Mathf.Max(MinLifetime, maxLifetime);
|
|
}
|
|
}
|
|
}
|