Initial commit: Tiny Tackle Heroes Unity project
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user