313 lines
9.4 KiB
C#
313 lines
9.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using BumpCombat.Enemies;
|
|
using UnityEngine;
|
|
|
|
namespace BumpCombat.Combat
|
|
{
|
|
[DefaultExecutionOrder(1000)]
|
|
public sealed class ArtifactStatusVisual : MonoBehaviour
|
|
{
|
|
public const int StatusSortingOffset = 1;
|
|
|
|
private const int FrameCount = 6;
|
|
private const float PixelsPerUnit = 32f;
|
|
private const string IgniteResourcePath = "Artifacts/ThreeColor-v1/StatusEffects/Ignite-Warlock-v1";
|
|
private const string ShockResourcePath = "Artifacts/ThreeColor-v1/StatusEffects/Shock-Warlock-v1";
|
|
|
|
private static readonly float[] IgniteFrameDurations =
|
|
{
|
|
0.1f,
|
|
0.1f,
|
|
0.1f,
|
|
0.1f,
|
|
0.1f,
|
|
0.1f,
|
|
};
|
|
|
|
private static readonly float[] ShockFrameDurations =
|
|
{
|
|
0.06f,
|
|
0.04f,
|
|
0.07f,
|
|
0.05f,
|
|
0.07f,
|
|
0.11f,
|
|
};
|
|
|
|
private sealed class CachedFrames
|
|
{
|
|
public CachedFrames(Sprite[] frames, bool ownsSprites)
|
|
{
|
|
Frames = frames;
|
|
OwnsSprites = ownsSprites;
|
|
}
|
|
|
|
public Sprite[] Frames { get; }
|
|
public bool OwnsSprites { get; }
|
|
}
|
|
|
|
private static readonly Dictionary<string, CachedFrames> FrameCache = new();
|
|
|
|
private sealed class StatusSlot
|
|
{
|
|
public StatusSlot(
|
|
string resourcePath,
|
|
string objectName,
|
|
float[] frameDurations)
|
|
{
|
|
ResourcePath = resourcePath;
|
|
ObjectName = objectName;
|
|
FrameDurations = frameDurations;
|
|
}
|
|
|
|
public string ResourcePath { get; }
|
|
public string ObjectName { get; }
|
|
public float[] FrameDurations { get; }
|
|
public Sprite[] Frames;
|
|
public GameObject Root;
|
|
public SpriteRenderer Renderer;
|
|
public int FrameIndex;
|
|
public float FrameElapsed;
|
|
}
|
|
|
|
private readonly StatusSlot ignite = new(
|
|
IgniteResourcePath,
|
|
"Ignite Status Visual",
|
|
IgniteFrameDurations);
|
|
private readonly StatusSlot shock = new(
|
|
ShockResourcePath,
|
|
"Shock Status Visual",
|
|
ShockFrameDurations);
|
|
|
|
private EnemyController enemy;
|
|
private SpriteRenderer sourceRenderer;
|
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
|
private static void ResetFrameCache()
|
|
{
|
|
foreach (CachedFrames cached in FrameCache.Values)
|
|
{
|
|
if (!cached.OwnsSprites)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (Sprite sprite in cached.Frames)
|
|
{
|
|
if (sprite == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (Application.isPlaying)
|
|
{
|
|
Destroy(sprite);
|
|
}
|
|
else
|
|
{
|
|
DestroyImmediate(sprite);
|
|
}
|
|
}
|
|
}
|
|
|
|
FrameCache.Clear();
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
enemy = GetComponent<EnemyController>();
|
|
sourceRenderer = GetComponent<SpriteRenderer>();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
if (enemy == null)
|
|
{
|
|
enemy = GetComponent<EnemyController>();
|
|
}
|
|
|
|
if (sourceRenderer == null)
|
|
{
|
|
sourceRenderer = GetComponent<SpriteRenderer>();
|
|
}
|
|
|
|
if (enemy != null)
|
|
{
|
|
enemy.OnDied += HandleEnemyDied;
|
|
}
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (enemy != null)
|
|
{
|
|
enemy.OnDied -= HandleEnemyDied;
|
|
}
|
|
|
|
ClearSlot(ignite);
|
|
ClearSlot(shock);
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
if (enemy == null
|
|
|| sourceRenderer == null
|
|
|| enemy.IsDead
|
|
|| !gameObject.activeInHierarchy)
|
|
{
|
|
ClearSlot(ignite);
|
|
ClearSlot(shock);
|
|
return;
|
|
}
|
|
|
|
UpdateSlot(ignite, enemy.IsIgnited);
|
|
UpdateSlot(shock, enemy.IsShocked);
|
|
}
|
|
|
|
private void HandleEnemyDied(EnemyController deadEnemy)
|
|
{
|
|
ClearSlot(ignite);
|
|
ClearSlot(shock);
|
|
}
|
|
|
|
private void UpdateSlot(StatusSlot slot, bool active)
|
|
{
|
|
if (!active)
|
|
{
|
|
ClearSlot(slot);
|
|
return;
|
|
}
|
|
|
|
if (slot.Root == null)
|
|
{
|
|
slot.Frames ??= LoadFrames(slot.ResourcePath);
|
|
if (slot.Frames.Length == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CreateSlot(slot);
|
|
}
|
|
|
|
SyncSlot(slot);
|
|
AdvanceSlot(slot, Time.deltaTime);
|
|
}
|
|
|
|
private void CreateSlot(StatusSlot slot)
|
|
{
|
|
slot.Root = new GameObject(slot.ObjectName);
|
|
slot.Root.transform.SetParent(transform, false);
|
|
slot.Root.transform.localPosition = Vector3.zero;
|
|
slot.Renderer = slot.Root.AddComponent<SpriteRenderer>();
|
|
slot.Renderer.color = Color.white;
|
|
slot.FrameIndex = 0;
|
|
slot.FrameElapsed = 0f;
|
|
slot.Renderer.sprite = slot.Frames[slot.FrameIndex];
|
|
}
|
|
|
|
private void SyncSlot(StatusSlot slot)
|
|
{
|
|
if (slot.Root == null || slot.Renderer == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Vector2 groundAnchor = enemy.GroundAnchorPosition;
|
|
slot.Root.transform.SetPositionAndRotation(
|
|
new Vector3(groundAnchor.x, groundAnchor.y, transform.position.z),
|
|
Quaternion.identity);
|
|
slot.Root.transform.localScale = Vector3.one;
|
|
slot.Renderer.sortingLayerID = sourceRenderer.sortingLayerID;
|
|
slot.Renderer.sortingOrder =
|
|
sourceRenderer.sortingOrder + StatusSortingOffset;
|
|
slot.Renderer.flipX = sourceRenderer.flipX;
|
|
slot.Renderer.flipY = sourceRenderer.flipY;
|
|
slot.Renderer.sharedMaterial = sourceRenderer.sharedMaterial;
|
|
slot.Renderer.enabled = true;
|
|
}
|
|
|
|
private static void AdvanceSlot(StatusSlot slot, float deltaTime)
|
|
{
|
|
if (slot.Renderer == null
|
|
|| slot.Frames == null
|
|
|| slot.Frames.Length == 0
|
|
|| deltaTime <= 0f)
|
|
{
|
|
return;
|
|
}
|
|
|
|
slot.FrameElapsed += deltaTime;
|
|
while (slot.FrameElapsed >= slot.FrameDurations[slot.FrameIndex])
|
|
{
|
|
slot.FrameElapsed -= slot.FrameDurations[slot.FrameIndex];
|
|
slot.FrameIndex = (slot.FrameIndex + 1) % slot.Frames.Length;
|
|
slot.Renderer.sprite = slot.Frames[slot.FrameIndex];
|
|
}
|
|
}
|
|
|
|
private static void ClearSlot(StatusSlot slot)
|
|
{
|
|
if (slot.Root != null)
|
|
{
|
|
slot.Root.SetActive(false);
|
|
Destroy(slot.Root);
|
|
}
|
|
|
|
slot.Root = null;
|
|
slot.Renderer = null;
|
|
slot.FrameIndex = 0;
|
|
slot.FrameElapsed = 0f;
|
|
}
|
|
|
|
private static Sprite[] LoadFrames(string resourcePath)
|
|
{
|
|
if (FrameCache.TryGetValue(resourcePath, out CachedFrames cached))
|
|
{
|
|
return cached.Frames;
|
|
}
|
|
|
|
Sprite[] importedFrames = Resources.LoadAll<Sprite>(resourcePath);
|
|
if (importedFrames != null && importedFrames.Length >= FrameCount)
|
|
{
|
|
Sprite[] frames = new Sprite[FrameCount];
|
|
Array.Copy(importedFrames, frames, FrameCount);
|
|
FrameCache[resourcePath] = new CachedFrames(frames, false);
|
|
return frames;
|
|
}
|
|
|
|
Texture2D texture = importedFrames != null
|
|
&& importedFrames.Length > 0
|
|
? importedFrames[0].texture
|
|
: Resources.Load<Texture2D>(resourcePath);
|
|
if (texture == null
|
|
|| texture.width < FrameCount
|
|
|| texture.width % FrameCount != 0
|
|
|| texture.height <= 0)
|
|
{
|
|
Sprite[] missing = Array.Empty<Sprite>();
|
|
FrameCache[resourcePath] = new CachedFrames(missing, false);
|
|
return missing;
|
|
}
|
|
|
|
int frameWidth = texture.width / FrameCount;
|
|
Sprite[] generatedFrames = new Sprite[FrameCount];
|
|
for (int i = 0; i < FrameCount; i++)
|
|
{
|
|
generatedFrames[i] = Sprite.Create(
|
|
texture,
|
|
new Rect(i * frameWidth, 0f, frameWidth, texture.height),
|
|
new Vector2(0.5f, 0.125f),
|
|
PixelsPerUnit,
|
|
0,
|
|
SpriteMeshType.FullRect);
|
|
generatedFrames[i].name = $"{resourcePath} Frame {i + 1}";
|
|
generatedFrames[i].hideFlags = HideFlags.HideAndDontSave;
|
|
}
|
|
|
|
FrameCache[resourcePath] = new CachedFrames(generatedFrames, true);
|
|
return generatedFrames;
|
|
}
|
|
}
|
|
}
|