875 lines
28 KiB
C#
875 lines
28 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using BumpCombat.Core;
|
|
using BumpCombat.Player;
|
|
using UnityEngine;
|
|
|
|
namespace BumpCombat.Combat
|
|
{
|
|
[DisallowMultipleComponent]
|
|
[DefaultExecutionOrder(1000)]
|
|
public sealed class ArtifactChargeVisual : MonoBehaviour
|
|
{
|
|
public const int FrameCount = 6;
|
|
public const int FlashFrameCount = 4;
|
|
public const int CellSize = 64;
|
|
public const float PixelsPerUnit = 32f;
|
|
public const float ChargeVisualY = 0f;
|
|
public const float ReadyFlashDuration = 0.14f;
|
|
public const float ReleaseVisualDuration = 0.24f;
|
|
public const float SwitchVisualDuration = 0.36f;
|
|
|
|
private const int AuraSortingOffset = -1;
|
|
private const int ReadyAuraSortingOffset = -1;
|
|
private const int FlashSortingOffset = 1;
|
|
private const int ReleaseSortingOffset = 1;
|
|
private const string ResourceRoot = "Artifacts/ThreeColor-v1/Charge/";
|
|
private const string AuraSuffix = "-Aura-v1";
|
|
private const string ReadyAuraSuffix = "-ReadyAura-v1";
|
|
private const string FlashSuffix = "-Flash-v1";
|
|
private const string ReleaseSuffix = "-Release-v1";
|
|
|
|
private static readonly float[] AuraFrameDurations =
|
|
{
|
|
0.06f, 0.06f, 0.06f, 0.06f, 0.06f, 0.06f,
|
|
};
|
|
|
|
private static readonly float[] ReadyAuraFrameDurations =
|
|
{
|
|
0.06f, 0.06f, 0.06f, 0.06f, 0.06f, 0.06f,
|
|
};
|
|
|
|
private static readonly float[] FlashFrameDurations =
|
|
{
|
|
0.03f, 0.04f, 0.04f, 0.03f,
|
|
};
|
|
|
|
private static readonly float[] ReleaseFrameDurations =
|
|
{
|
|
0.03f, 0.04f, 0.04f, 0.04f, 0.04f, 0.05f,
|
|
};
|
|
|
|
private sealed class CachedFrames
|
|
{
|
|
public CachedFrames(Sprite[] frames, bool ownsSprites)
|
|
{
|
|
Frames = frames;
|
|
OwnsSprites = ownsSprites;
|
|
}
|
|
|
|
public Sprite[] Frames { get; }
|
|
public bool OwnsSprites { get; }
|
|
}
|
|
|
|
private sealed class VisualSlot
|
|
{
|
|
public VisualSlot(
|
|
string objectName,
|
|
int sortingOffset,
|
|
float[] frameDurations)
|
|
{
|
|
ObjectName = objectName;
|
|
SortingOffset = sortingOffset;
|
|
FrameDurations = frameDurations;
|
|
}
|
|
|
|
public string ObjectName { get; }
|
|
public int SortingOffset { get; }
|
|
public float[] FrameDurations { get; }
|
|
public Sprite[] Frames;
|
|
public GameObject Root;
|
|
public SpriteRenderer Renderer;
|
|
public int FrameIndex;
|
|
public float FrameElapsed;
|
|
public bool Loop;
|
|
}
|
|
|
|
private static readonly Dictionary<string, CachedFrames> FrameCache = new();
|
|
|
|
private readonly VisualSlot aura = new(
|
|
"Artifact Charge Aura",
|
|
AuraSortingOffset,
|
|
AuraFrameDurations);
|
|
private readonly VisualSlot readyAura = new(
|
|
"Artifact Charge Ready Aura",
|
|
ReadyAuraSortingOffset,
|
|
ReadyAuraFrameDurations);
|
|
private readonly VisualSlot flash = new(
|
|
"Artifact Charge Flash",
|
|
FlashSortingOffset,
|
|
FlashFrameDurations);
|
|
private readonly VisualSlot release = new(
|
|
"Artifact Release",
|
|
ReleaseSortingOffset,
|
|
ReleaseFrameDurations);
|
|
private readonly VisualSlot switchPulse = new(
|
|
"Artifact Switch",
|
|
FlashSortingOffset,
|
|
AuraFrameDurations);
|
|
|
|
private ActiveArtifactController controller;
|
|
private readonly Dictionary<string, Sprite[]> equipmentFrames = new();
|
|
private readonly Dictionary<Sprite, Sprite> equipmentOriginals = new();
|
|
private PlayerHealth playerHealth;
|
|
private SpriteRenderer playerRenderer;
|
|
private SpriteRenderer guardOutline;
|
|
private readonly Dictionary<Sprite, Sprite> guardFrames = new();
|
|
private RunManager subscribedRunManager;
|
|
private ActiveArtifactDefinition chargingArtifact;
|
|
private bool readyFlashPlayed;
|
|
private int flashPlayCount;
|
|
private int releasePlayCount;
|
|
|
|
public bool IsAuraVisible => IsVisible(aura);
|
|
public bool IsSwitchVisible => IsVisible(switchPulse);
|
|
public GameObject SwitchObject => switchPulse.Root;
|
|
public bool IsReadyAuraVisible => IsVisible(readyAura);
|
|
public bool IsFlashVisible => IsVisible(flash);
|
|
public bool IsReleaseVisible => IsVisible(release);
|
|
public GameObject AuraObject => aura.Root;
|
|
public GameObject ReadyAuraObject => readyAura.Root;
|
|
public GameObject FlashObject => flash.Root;
|
|
public GameObject ReleaseObject => release.Root;
|
|
public int FlashPlayCount => flashPlayCount;
|
|
public int ReleasePlayCount => releasePlayCount;
|
|
|
|
[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()
|
|
{
|
|
controller = GetComponent<ActiveArtifactController>();
|
|
playerHealth = GetComponent<PlayerHealth>();
|
|
playerRenderer = FindPlayerRenderer();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
if (controller == null)
|
|
{
|
|
controller = GetComponent<ActiveArtifactController>();
|
|
}
|
|
|
|
if (playerHealth == null)
|
|
{
|
|
playerHealth = GetComponent<PlayerHealth>();
|
|
}
|
|
|
|
if (playerRenderer == null)
|
|
{
|
|
playerRenderer = FindPlayerRenderer();
|
|
}
|
|
|
|
SubscribeController();
|
|
SubscribePlayerHealth();
|
|
RefreshRunManagerSubscription();
|
|
RestoreCurrentChargeVisuals();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (guardOutline != null) guardOutline.enabled = false;
|
|
RestoreEquipmentSprite();
|
|
UnsubscribeController();
|
|
UnsubscribePlayerHealth();
|
|
UnsubscribeRunManager();
|
|
ClearAllVisuals();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (guardOutline != null) Destroy(guardOutline.gameObject);
|
|
UnsubscribeController();
|
|
UnsubscribePlayerHealth();
|
|
UnsubscribeRunManager();
|
|
ClearAllVisuals();
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
RefreshEquipmentSprite();
|
|
RefreshGuardOutline();
|
|
RefreshRunManagerSubscription();
|
|
if (subscribedRunManager != null
|
|
&& (subscribedRunManager.IsSelectionOpen
|
|
|| subscribedRunManager.IsGameOver
|
|
|| subscribedRunManager.IsTitleScreen))
|
|
{
|
|
ClearAllVisuals();
|
|
return;
|
|
}
|
|
|
|
if (controller == null)
|
|
{
|
|
ClearAllVisuals();
|
|
return;
|
|
}
|
|
|
|
if (!controller.isActiveAndEnabled)
|
|
{
|
|
ClearAllVisuals();
|
|
return;
|
|
}
|
|
|
|
if (controller.IsCharging)
|
|
{
|
|
UpdateChargeVisuals();
|
|
}
|
|
else
|
|
{
|
|
ClearChargeHoldVisuals();
|
|
}
|
|
|
|
SyncSlot(aura, true);
|
|
SyncSlot(readyAura, true);
|
|
SyncSlot(flash, true);
|
|
SyncSlot(release, false);
|
|
SyncSlot(switchPulse, true);
|
|
AdvanceSlot(aura, Time.deltaTime);
|
|
AdvanceSlot(readyAura, Time.deltaTime);
|
|
AdvanceSlot(flash, Time.deltaTime);
|
|
AdvanceSlot(release, Time.deltaTime);
|
|
AdvanceSlot(switchPulse, Time.deltaTime);
|
|
}
|
|
|
|
public void RefreshEquipmentSprite()
|
|
{
|
|
if (playerRenderer == null || playerRenderer.sprite == null) return;
|
|
Sprite current = playerRenderer.sprite;
|
|
Sprite original = equipmentOriginals.TryGetValue(current, out Sprite source) ? source : current;
|
|
if (controller == null || !controller.isActiveAndEnabled || controller.CurrentArtifact == null)
|
|
{
|
|
playerRenderer.sprite = original;
|
|
return;
|
|
}
|
|
string path = "Artifacts/ThreeColor-v1/Player/" + original.texture.name
|
|
+ "-" + controller.CurrentArtifact.ArtifactColor + "-v1";
|
|
if (!equipmentFrames.TryGetValue(path, out Sprite[] frames))
|
|
{
|
|
frames = Resources.LoadAll<Sprite>(path);
|
|
equipmentFrames[path] = frames;
|
|
}
|
|
foreach (Sprite frame in frames)
|
|
{
|
|
if (frame.rect != original.rect) continue;
|
|
equipmentOriginals[frame] = original;
|
|
playerRenderer.sprite = frame;
|
|
return;
|
|
}
|
|
playerRenderer.sprite = original;
|
|
}
|
|
|
|
public void RefreshGuardOutline()
|
|
{
|
|
bool visible = isActiveAndEnabled && playerHealth != null
|
|
&& playerHealth.isActiveAndEnabled && playerHealth.CurrentHealth > 0f
|
|
&& playerHealth.IsGuarding && playerRenderer != null
|
|
&& playerRenderer.enabled && playerRenderer.sprite != null
|
|
&& (RunManager.Instance == null || !RunManager.Instance.IsGameOver);
|
|
if (!visible)
|
|
{
|
|
if (guardOutline != null) guardOutline.enabled = false;
|
|
return;
|
|
}
|
|
|
|
if (guardOutline == null)
|
|
{
|
|
var root = new GameObject("Player Guard Outline");
|
|
root.transform.SetParent(playerRenderer.transform, false);
|
|
guardOutline = root.AddComponent<SpriteRenderer>();
|
|
}
|
|
|
|
Sprite current = playerRenderer.sprite;
|
|
Sprite original = equipmentOriginals.TryGetValue(current, out Sprite source) ? source : current;
|
|
if (!guardFrames.TryGetValue(original, out Sprite mask))
|
|
{
|
|
foreach (Sprite frame in Resources.LoadAll<Sprite>("Combat/Guard-v1/" + original.texture.name + "-outline"))
|
|
{
|
|
if (frame.rect != original.rect) continue;
|
|
mask = frame;
|
|
break;
|
|
}
|
|
guardFrames[original] = mask;
|
|
}
|
|
guardOutline.sprite = mask;
|
|
guardOutline.enabled = mask != null;
|
|
guardOutline.flipX = playerRenderer.flipX;
|
|
guardOutline.flipY = playerRenderer.flipY;
|
|
guardOutline.color = new Color(1f, 1f, 1f, playerRenderer.color.a);
|
|
guardOutline.sharedMaterial = playerRenderer.sharedMaterial;
|
|
guardOutline.sortingLayerID = playerRenderer.sortingLayerID;
|
|
guardOutline.sortingOrder = playerRenderer.sortingOrder + 1;
|
|
}
|
|
|
|
private void RestoreEquipmentSprite()
|
|
{
|
|
if (playerRenderer != null && playerRenderer.sprite != null
|
|
&& equipmentOriginals.TryGetValue(playerRenderer.sprite, out Sprite original))
|
|
playerRenderer.sprite = original;
|
|
}
|
|
|
|
public void Connect(ActiveArtifactController artifactController)
|
|
{
|
|
if (controller == artifactController && controller != null)
|
|
{
|
|
SubscribeController();
|
|
return;
|
|
}
|
|
|
|
UnsubscribeController();
|
|
controller = artifactController;
|
|
SubscribeController();
|
|
}
|
|
|
|
public void NotifyChargeEnded(bool preserveReadyFlash)
|
|
{
|
|
ClearChargeHoldVisuals();
|
|
if (!preserveReadyFlash)
|
|
{
|
|
ClearSlot(flash);
|
|
}
|
|
}
|
|
|
|
public void NotifyArtifactUseFailed()
|
|
{
|
|
ClearAllVisuals();
|
|
}
|
|
|
|
public void ClearAllVisuals()
|
|
{
|
|
ClearSlot(aura);
|
|
ClearSlot(readyAura);
|
|
ClearSlot(flash);
|
|
ClearSlot(release);
|
|
ClearSlot(switchPulse);
|
|
}
|
|
|
|
private void SubscribeController()
|
|
{
|
|
if (controller == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
controller.OnStateChanged -= HandleControllerStateChanged;
|
|
controller.OnArtifactChargeStarted -= HandleArtifactChargeStarted;
|
|
controller.OnArtifactUseSucceeded -= HandleArtifactUseSucceeded;
|
|
controller.OnArtifactSwitched -= HandleArtifactSwitched;
|
|
controller.OnStateChanged += HandleControllerStateChanged;
|
|
controller.OnArtifactChargeStarted += HandleArtifactChargeStarted;
|
|
controller.OnArtifactUseSucceeded += HandleArtifactUseSucceeded;
|
|
controller.OnArtifactSwitched += HandleArtifactSwitched;
|
|
}
|
|
|
|
private void UnsubscribeController()
|
|
{
|
|
if (controller == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
controller.OnStateChanged -= HandleControllerStateChanged;
|
|
controller.OnArtifactChargeStarted -= HandleArtifactChargeStarted;
|
|
controller.OnArtifactUseSucceeded -= HandleArtifactUseSucceeded;
|
|
controller.OnArtifactSwitched -= HandleArtifactSwitched;
|
|
}
|
|
|
|
private void SubscribePlayerHealth()
|
|
{
|
|
if (playerHealth == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
playerHealth.OnDamaged -= HandlePlayerDamaged;
|
|
playerHealth.OnDied -= HandlePlayerDied;
|
|
playerHealth.OnDamaged += HandlePlayerDamaged;
|
|
playerHealth.OnDied += HandlePlayerDied;
|
|
}
|
|
|
|
private void UnsubscribePlayerHealth()
|
|
{
|
|
if (playerHealth == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
playerHealth.OnDamaged -= HandlePlayerDamaged;
|
|
playerHealth.OnDied -= HandlePlayerDied;
|
|
}
|
|
|
|
private void RefreshRunManagerSubscription()
|
|
{
|
|
RunManager current = RunManager.Instance;
|
|
if (current == subscribedRunManager)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UnsubscribeRunManager();
|
|
subscribedRunManager = current;
|
|
if (subscribedRunManager != null)
|
|
{
|
|
subscribedRunManager.OnSelectionChanged += HandleSelectionChanged;
|
|
}
|
|
}
|
|
|
|
private void UnsubscribeRunManager()
|
|
{
|
|
if (subscribedRunManager != null)
|
|
{
|
|
subscribedRunManager.OnSelectionChanged -= HandleSelectionChanged;
|
|
subscribedRunManager = null;
|
|
}
|
|
}
|
|
|
|
private void HandleArtifactChargeStarted(ActiveArtifactDefinition definition)
|
|
{
|
|
ClearSlot(switchPulse);
|
|
chargingArtifact = definition;
|
|
readyFlashPlayed = false;
|
|
ClearSlot(readyAura);
|
|
ClearSlot(flash);
|
|
ClearSlot(aura);
|
|
if (!Application.isPlaying || definition == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
aura.Frames = LoadFrames(
|
|
GetResourcePath(definition, AuraSuffix),
|
|
FrameCount);
|
|
readyAura.Frames = LoadFrames(
|
|
GetResourcePath(definition, ReadyAuraSuffix),
|
|
FrameCount);
|
|
flash.Frames = LoadFrames(
|
|
GetResourcePath(definition, FlashSuffix),
|
|
FlashFrameCount);
|
|
if (aura.Frames.Length == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CreateSlot(aura, true, Vector2.zero, 1f);
|
|
}
|
|
|
|
private void RestoreCurrentChargeVisuals()
|
|
{
|
|
if (!Application.isPlaying
|
|
|| controller == null
|
|
|| !controller.IsCharging)
|
|
{
|
|
return;
|
|
}
|
|
|
|
chargingArtifact = controller.CurrentArtifact;
|
|
if (chargingArtifact == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
aura.Frames = LoadFrames(
|
|
GetResourcePath(chargingArtifact, AuraSuffix),
|
|
FrameCount);
|
|
readyAura.Frames = LoadFrames(
|
|
GetResourcePath(chargingArtifact, ReadyAuraSuffix),
|
|
FrameCount);
|
|
flash.Frames = LoadFrames(
|
|
GetResourcePath(chargingArtifact, FlashSuffix),
|
|
FlashFrameCount);
|
|
UpdateChargeVisuals();
|
|
}
|
|
|
|
private void HandleControllerStateChanged()
|
|
{
|
|
if (controller == null || !controller.IsCharging)
|
|
{
|
|
ClearChargeHoldVisuals();
|
|
return;
|
|
}
|
|
|
|
UpdateChargeVisuals();
|
|
}
|
|
|
|
private void HandleArtifactUseSucceeded(
|
|
ActiveArtifactDefinition definition,
|
|
bool charged)
|
|
{
|
|
ClearSlot(switchPulse);
|
|
ClearChargeHoldVisuals();
|
|
if (!Application.isPlaying || definition == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
release.Frames = LoadFrames(
|
|
GetResourcePath(definition, ReleaseSuffix),
|
|
FrameCount);
|
|
if (release.Frames.Length == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Vector2 origin = controller.LastArtifactUsePosition;
|
|
CreateSlot(release, false, origin, charged ? 1.2f : 1f);
|
|
releasePlayCount++;
|
|
}
|
|
|
|
private void HandleArtifactSwitched(ActiveArtifactDefinition definition)
|
|
{
|
|
ClearSlot(switchPulse);
|
|
if (!Application.isPlaying || definition == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
switchPulse.Frames = LoadFrames(
|
|
GetResourcePath(definition, ReleaseSuffix), FrameCount);
|
|
if (switchPulse.Frames.Length == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Reuse the existing colored outward burst, following the player
|
|
// for a single short cycle so movement does not leave it behind.
|
|
CreateSlot(switchPulse, true, Vector2.zero, 1f);
|
|
}
|
|
|
|
private void HandlePlayerDamaged(Vector2 _, float __)
|
|
{
|
|
ClearAllVisuals();
|
|
}
|
|
|
|
private void HandlePlayerDied()
|
|
{
|
|
ClearAllVisuals();
|
|
}
|
|
|
|
private void HandleSelectionChanged(bool isOpen)
|
|
{
|
|
if (isOpen)
|
|
{
|
|
ClearAllVisuals();
|
|
}
|
|
}
|
|
|
|
private void UpdateChargeVisuals()
|
|
{
|
|
if (controller == null
|
|
|| !controller.IsCharging
|
|
|| chargingArtifact == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!controller.IsFullyCharged)
|
|
{
|
|
EnsureLoopSlot(aura, AuraSuffix, 1f);
|
|
ClearSlot(readyAura);
|
|
SetAlpha(
|
|
aura,
|
|
Mathf.Lerp(0.55f, 0.85f, controller.ChargeProgress));
|
|
return;
|
|
}
|
|
|
|
EnsureLoopSlot(readyAura, ReadyAuraSuffix, 1f);
|
|
ClearSlot(aura);
|
|
if (!readyFlashPlayed)
|
|
{
|
|
readyFlashPlayed = true;
|
|
flashPlayCount++;
|
|
if (flash.Frames.Length > 0 && Application.isPlaying)
|
|
{
|
|
CreateSlot(flash, true, Vector2.zero, 1f);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void EnsureLoopSlot(
|
|
VisualSlot slot,
|
|
string suffix,
|
|
float scale)
|
|
{
|
|
if (slot.Root != null || chargingArtifact == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
slot.Frames = LoadFrames(
|
|
GetResourcePath(chargingArtifact, suffix),
|
|
FrameCount);
|
|
if (slot.Frames.Length == 0 || !Application.isPlaying)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CreateSlot(slot, true, Vector2.zero, scale);
|
|
}
|
|
|
|
private void CreateSlot(
|
|
VisualSlot slot,
|
|
bool followPlayer,
|
|
Vector2 worldOrigin,
|
|
float scale)
|
|
{
|
|
ClearSlot(slot);
|
|
slot.Root = new GameObject(slot.ObjectName);
|
|
if (followPlayer)
|
|
{
|
|
slot.Root.transform.SetParent(transform, false);
|
|
slot.Root.transform.localPosition = new Vector3(
|
|
0f,
|
|
ChargeVisualY,
|
|
0f);
|
|
}
|
|
else
|
|
{
|
|
slot.Root.transform.position = new Vector3(
|
|
worldOrigin.x,
|
|
worldOrigin.y + ChargeVisualY,
|
|
transform.position.z);
|
|
}
|
|
|
|
slot.Root.transform.localScale = Vector3.one * scale;
|
|
slot.Renderer = slot.Root.AddComponent<SpriteRenderer>();
|
|
slot.Renderer.color = Color.white;
|
|
slot.Renderer.sprite = slot.Frames[0];
|
|
slot.Renderer.enabled = true;
|
|
slot.FrameIndex = 0;
|
|
slot.FrameElapsed = 0f;
|
|
slot.Loop = slot == aura || slot == readyAura;
|
|
SyncSlot(slot, followPlayer);
|
|
}
|
|
|
|
private void SyncSlot(VisualSlot slot, bool followPlayer)
|
|
{
|
|
if (slot.Root == null || slot.Renderer == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (followPlayer)
|
|
{
|
|
slot.Root.transform.SetParent(transform, false);
|
|
slot.Root.transform.localPosition = new Vector3(
|
|
0f,
|
|
ChargeVisualY,
|
|
0f);
|
|
}
|
|
|
|
if (playerRenderer == null)
|
|
{
|
|
playerRenderer = FindPlayerRenderer();
|
|
}
|
|
|
|
if (playerRenderer != null)
|
|
{
|
|
slot.Renderer.sortingLayerID = playerRenderer.sortingLayerID;
|
|
slot.Renderer.sortingOrder =
|
|
playerRenderer.sortingOrder + slot.SortingOffset;
|
|
slot.Renderer.sharedMaterial = playerRenderer.sharedMaterial;
|
|
}
|
|
|
|
Color color = Color.white;
|
|
if (slot == aura
|
|
&& controller != null
|
|
&& controller.IsCharging
|
|
&& !controller.IsFullyCharged)
|
|
{
|
|
color.a = Mathf.Lerp(0.55f, 0.85f, controller.ChargeProgress);
|
|
}
|
|
slot.Renderer.color = color;
|
|
slot.Renderer.enabled = true;
|
|
}
|
|
|
|
private static void SetAlpha(VisualSlot slot, float alpha)
|
|
{
|
|
if (slot.Renderer == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Color color = Color.white;
|
|
color.a = Mathf.Clamp01(alpha);
|
|
slot.Renderer.color = color;
|
|
}
|
|
|
|
private static void AdvanceSlot(VisualSlot slot, float deltaTime)
|
|
{
|
|
if (slot.Root == null
|
|
|| 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];
|
|
int nextFrame = slot.FrameIndex + 1;
|
|
if (nextFrame >= slot.Frames.Length)
|
|
{
|
|
if (!slot.Loop)
|
|
{
|
|
ClearSlot(slot);
|
|
return;
|
|
}
|
|
|
|
nextFrame = 0;
|
|
}
|
|
|
|
slot.FrameIndex = nextFrame;
|
|
slot.Renderer.sprite = slot.Frames[slot.FrameIndex];
|
|
}
|
|
}
|
|
|
|
private void ClearChargeHoldVisuals()
|
|
{
|
|
ClearSlot(aura);
|
|
ClearSlot(readyAura);
|
|
}
|
|
|
|
private static bool IsVisible(VisualSlot slot)
|
|
{
|
|
return slot.Root != null
|
|
&& slot.Renderer != null
|
|
&& slot.Renderer.enabled;
|
|
}
|
|
|
|
private SpriteRenderer FindPlayerRenderer()
|
|
{
|
|
SpriteRenderer rootRenderer = GetComponent<SpriteRenderer>();
|
|
if (rootRenderer != null)
|
|
{
|
|
return rootRenderer;
|
|
}
|
|
|
|
SpriteRenderer[] renderers = GetComponentsInChildren<SpriteRenderer>(true);
|
|
foreach (SpriteRenderer renderer in renderers)
|
|
{
|
|
if (renderer != null && renderer.transform != transform)
|
|
{
|
|
return renderer;
|
|
}
|
|
}
|
|
|
|
return GetComponent<SpriteRenderer>();
|
|
}
|
|
|
|
private static string GetResourcePath(
|
|
ActiveArtifactDefinition definition,
|
|
string suffix)
|
|
{
|
|
return ResourceRoot + definition.Effect + suffix;
|
|
}
|
|
|
|
private static void ClearSlot(VisualSlot slot)
|
|
{
|
|
if (slot.Root != null)
|
|
{
|
|
slot.Root.SetActive(false);
|
|
if (Application.isPlaying)
|
|
{
|
|
Destroy(slot.Root);
|
|
}
|
|
else
|
|
{
|
|
DestroyImmediate(slot.Root);
|
|
}
|
|
}
|
|
|
|
slot.Root = null;
|
|
slot.Renderer = null;
|
|
slot.FrameIndex = 0;
|
|
slot.FrameElapsed = 0f;
|
|
}
|
|
|
|
private static Sprite[] LoadFrames(
|
|
string resourcePath,
|
|
int expectedFrameCount)
|
|
{
|
|
if (FrameCache.TryGetValue(resourcePath, out CachedFrames cached))
|
|
{
|
|
return cached.Frames;
|
|
}
|
|
|
|
Sprite[] importedFrames = Resources.LoadAll<Sprite>(resourcePath);
|
|
if (importedFrames != null
|
|
&& importedFrames.Length >= expectedFrameCount)
|
|
{
|
|
Array.Sort(
|
|
importedFrames,
|
|
(left, right) => String.CompareOrdinal(left.name, right.name));
|
|
Sprite[] frames = new Sprite[expectedFrameCount];
|
|
Array.Copy(importedFrames, frames, expectedFrameCount);
|
|
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 < CellSize * expectedFrameCount
|
|
|| texture.height < CellSize
|
|
|| texture.width % expectedFrameCount != 0)
|
|
{
|
|
Sprite[] missing = Array.Empty<Sprite>();
|
|
FrameCache[resourcePath] = new CachedFrames(missing, false);
|
|
return missing;
|
|
}
|
|
|
|
int frameWidth = texture.width / expectedFrameCount;
|
|
Sprite[] generatedFrames = new Sprite[expectedFrameCount];
|
|
for (int i = 0; i < expectedFrameCount; i++)
|
|
{
|
|
generatedFrames[i] = Sprite.Create(
|
|
texture,
|
|
new Rect(i * frameWidth, 0f, frameWidth, CellSize),
|
|
new Vector2(0.5f, 0.5f),
|
|
PixelsPerUnit,
|
|
0,
|
|
SpriteMeshType.FullRect);
|
|
generatedFrames[i].name =
|
|
$"{resourcePath} Frame {i + 1}";
|
|
generatedFrames[i].hideFlags = HideFlags.HideAndDontSave;
|
|
}
|
|
|
|
FrameCache[resourcePath] = new CachedFrames(generatedFrames, true);
|
|
return generatedFrames;
|
|
}
|
|
}
|
|
}
|