Unity client features: - WebSocket connection via NativeWebSocket - 2.5D agent visuals with programmatic placeholder sprites - Billboard system for sprites and UI elements - Floating UI panels (name, HP, energy bars) - Speech bubble system with pop-in animation - RTS-style camera controller (WASD + scroll zoom) - Editor tools for prefab creation and scene setup Scripts: - NetworkManager: WebSocket singleton - GameManager: Agent spawning and event handling - AgentVisual: 2.5D sprite and UI creation - Billboard: Camera-facing behavior - SpeechBubble: Animated dialogue display - CameraController: RTS camera with UI input detection - UIManager: HUD and command input 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
600 lines
21 KiB
C#
600 lines
21 KiB
C#
using System.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using TMPro;
|
|
using TheIsland.Models;
|
|
using TheIsland.Network;
|
|
|
|
namespace TheIsland.Visual
|
|
{
|
|
/// <summary>
|
|
/// Complete 2.5D Agent visual system.
|
|
/// Creates sprite, floating UI, and speech bubble programmatically.
|
|
/// Attach to an empty GameObject to create a full agent visual.
|
|
/// </summary>
|
|
public class AgentVisual : MonoBehaviour
|
|
{
|
|
#region Configuration
|
|
[Header("Sprite Settings")]
|
|
[Tooltip("Assign a sprite, or leave empty for auto-generated placeholder")]
|
|
[SerializeField] private Sprite characterSprite;
|
|
[SerializeField] private Color spriteColor = Color.white;
|
|
[SerializeField] private float spriteScale = 2f;
|
|
[SerializeField] private int sortingOrder = 10;
|
|
|
|
[Header("Placeholder Colors (if no sprite assigned)")]
|
|
[SerializeField] private Color placeholderBodyColor = new Color(0.3f, 0.6f, 0.9f);
|
|
[SerializeField] private Color placeholderOutlineColor = new Color(0.2f, 0.4f, 0.7f);
|
|
|
|
[Header("UI Settings")]
|
|
[SerializeField] private Vector3 uiOffset = new Vector3(0, 2.2f, 0);
|
|
[SerializeField] private float uiScale = 0.008f;
|
|
|
|
[Header("Speech Bubble")]
|
|
[SerializeField] private float speechDuration = 5f;
|
|
[SerializeField] private Vector3 speechOffset = new Vector3(0, 3.5f, 0);
|
|
|
|
[Header("Colors")]
|
|
[SerializeField] private Color hpHighColor = new Color(0.3f, 0.9f, 0.3f);
|
|
[SerializeField] private Color hpLowColor = new Color(0.9f, 0.3f, 0.3f);
|
|
[SerializeField] private Color energyHighColor = new Color(1f, 0.8f, 0.2f);
|
|
[SerializeField] private Color energyLowColor = new Color(1f, 0.5f, 0.1f);
|
|
#endregion
|
|
|
|
#region References
|
|
private SpriteRenderer _spriteRenderer;
|
|
private Canvas _uiCanvas;
|
|
private TextMeshProUGUI _nameLabel;
|
|
private TextMeshProUGUI _personalityLabel;
|
|
private Image _hpBarFill;
|
|
private Image _energyBarFill;
|
|
private TextMeshProUGUI _hpText;
|
|
private TextMeshProUGUI _energyText;
|
|
private GameObject _deathOverlay;
|
|
private SpeechBubble _speechBubble;
|
|
private Billboard _spriteBillboard;
|
|
private Billboard _uiBillboard;
|
|
private Camera _mainCamera;
|
|
#endregion
|
|
|
|
#region State
|
|
private int _agentId;
|
|
private AgentData _currentData;
|
|
private Coroutine _speechCoroutine;
|
|
#endregion
|
|
|
|
#region Properties
|
|
public int AgentId => _agentId;
|
|
public AgentData CurrentData => _currentData;
|
|
public bool IsAlive => _currentData?.IsAlive ?? false;
|
|
#endregion
|
|
|
|
#region Unity Lifecycle
|
|
private void Awake()
|
|
{
|
|
_mainCamera = Camera.main;
|
|
CreateVisuals();
|
|
}
|
|
|
|
private void OnMouseDown()
|
|
{
|
|
if (!IsAlive)
|
|
{
|
|
Debug.Log($"[AgentVisual] Cannot interact with dead agent: {_currentData?.name}");
|
|
return;
|
|
}
|
|
|
|
NetworkManager.Instance?.FeedAgent(_currentData.name);
|
|
Debug.Log($"[AgentVisual] Clicked to feed: {_currentData?.name}");
|
|
}
|
|
#endregion
|
|
|
|
#region Initialization
|
|
public void Initialize(AgentData data)
|
|
{
|
|
_agentId = data.id;
|
|
_currentData = data;
|
|
gameObject.name = $"Agent_{data.id}_{data.name}";
|
|
|
|
// Apply unique color based on agent ID
|
|
ApplyAgentColor(data.id);
|
|
|
|
// Set UI text
|
|
if (_nameLabel != null) _nameLabel.text = data.name;
|
|
if (_personalityLabel != null) _personalityLabel.text = $"({data.personality})";
|
|
|
|
UpdateStats(data);
|
|
Debug.Log($"[AgentVisual] Initialized: {data.name}");
|
|
}
|
|
|
|
private void ApplyAgentColor(int agentId)
|
|
{
|
|
// Generate unique color per agent
|
|
Color[] agentColors = new Color[]
|
|
{
|
|
new Color(0.3f, 0.6f, 0.9f), // Blue (Jack)
|
|
new Color(0.9f, 0.5f, 0.7f), // Pink (Luna)
|
|
new Color(0.5f, 0.8f, 0.5f), // Green (Bob)
|
|
new Color(0.9f, 0.7f, 0.3f), // Orange
|
|
new Color(0.7f, 0.5f, 0.9f), // Purple
|
|
};
|
|
|
|
int colorIndex = agentId % agentColors.Length;
|
|
placeholderBodyColor = agentColors[colorIndex];
|
|
placeholderOutlineColor = agentColors[colorIndex] * 0.7f;
|
|
|
|
// Update sprite color if using placeholder
|
|
if (_spriteRenderer != null && characterSprite == null)
|
|
{
|
|
RegeneratePlaceholderSprite();
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Visual Creation
|
|
private void CreateVisuals()
|
|
{
|
|
CreateSprite();
|
|
CreateUICanvas();
|
|
CreateSpeechBubble();
|
|
CreateCollider();
|
|
}
|
|
|
|
private void CreateSprite()
|
|
{
|
|
// Create sprite child object
|
|
var spriteObj = new GameObject("CharacterSprite");
|
|
spriteObj.transform.SetParent(transform);
|
|
spriteObj.transform.localPosition = new Vector3(0, 1f, 0);
|
|
spriteObj.transform.localScale = Vector3.one * spriteScale;
|
|
|
|
_spriteRenderer = spriteObj.AddComponent<SpriteRenderer>();
|
|
_spriteRenderer.sortingOrder = sortingOrder;
|
|
|
|
if (characterSprite != null)
|
|
{
|
|
_spriteRenderer.sprite = characterSprite;
|
|
_spriteRenderer.color = spriteColor;
|
|
}
|
|
else
|
|
{
|
|
// Generate placeholder sprite
|
|
RegeneratePlaceholderSprite();
|
|
}
|
|
|
|
// Add billboard
|
|
_spriteBillboard = spriteObj.AddComponent<Billboard>();
|
|
}
|
|
|
|
private void RegeneratePlaceholderSprite()
|
|
{
|
|
if (_spriteRenderer == null) return;
|
|
|
|
// Create a simple character placeholder (circle with body shape)
|
|
Texture2D texture = CreatePlaceholderTexture(64, 64);
|
|
_spriteRenderer.sprite = Sprite.Create(
|
|
texture,
|
|
new Rect(0, 0, texture.width, texture.height),
|
|
new Vector2(0.5f, 0.5f),
|
|
100f
|
|
);
|
|
}
|
|
|
|
private Texture2D CreatePlaceholderTexture(int width, int height)
|
|
{
|
|
Texture2D texture = new Texture2D(width, height, TextureFormat.RGBA32, false);
|
|
texture.filterMode = FilterMode.Point;
|
|
|
|
// Clear to transparent
|
|
Color[] pixels = new Color[width * height];
|
|
for (int i = 0; i < pixels.Length; i++)
|
|
{
|
|
pixels[i] = Color.clear;
|
|
}
|
|
|
|
// Draw simple character shape
|
|
Vector2 center = new Vector2(width / 2f, height / 2f);
|
|
|
|
// Body (ellipse)
|
|
DrawEllipse(pixels, width, height, center + Vector2.down * 8, 14, 20, placeholderBodyColor);
|
|
|
|
// Head (circle)
|
|
DrawCircle(pixels, width, height, center + Vector2.up * 12, 12, placeholderBodyColor);
|
|
|
|
// Outline
|
|
DrawCircleOutline(pixels, width, height, center + Vector2.up * 12, 12, placeholderOutlineColor, 2);
|
|
DrawEllipseOutline(pixels, width, height, center + Vector2.down * 8, 14, 20, placeholderOutlineColor, 2);
|
|
|
|
// Eyes
|
|
DrawCircle(pixels, width, height, center + new Vector2(-4, 14), 2, Color.white);
|
|
DrawCircle(pixels, width, height, center + new Vector2(4, 14), 2, Color.white);
|
|
DrawCircle(pixels, width, height, center + new Vector2(-4, 14), 1, Color.black);
|
|
DrawCircle(pixels, width, height, center + new Vector2(4, 14), 1, Color.black);
|
|
|
|
texture.SetPixels(pixels);
|
|
texture.Apply();
|
|
return texture;
|
|
}
|
|
|
|
private void DrawCircle(Color[] pixels, int width, int height, Vector2 center, float radius, Color color)
|
|
{
|
|
for (int y = 0; y < height; y++)
|
|
{
|
|
for (int x = 0; x < width; x++)
|
|
{
|
|
float dist = Vector2.Distance(new Vector2(x, y), center);
|
|
if (dist <= radius)
|
|
{
|
|
pixels[y * width + x] = color;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawCircleOutline(Color[] pixels, int width, int height, Vector2 center, float radius, Color color, int thickness)
|
|
{
|
|
for (int y = 0; y < height; y++)
|
|
{
|
|
for (int x = 0; x < width; x++)
|
|
{
|
|
float dist = Vector2.Distance(new Vector2(x, y), center);
|
|
if (dist >= radius - thickness && dist <= radius + thickness)
|
|
{
|
|
pixels[y * width + x] = color;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawEllipse(Color[] pixels, int width, int height, Vector2 center, float rx, float ry, Color color)
|
|
{
|
|
for (int y = 0; y < height; y++)
|
|
{
|
|
for (int x = 0; x < width; x++)
|
|
{
|
|
float dx = (x - center.x) / rx;
|
|
float dy = (y - center.y) / ry;
|
|
if (dx * dx + dy * dy <= 1)
|
|
{
|
|
pixels[y * width + x] = color;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrawEllipseOutline(Color[] pixels, int width, int height, Vector2 center, float rx, float ry, Color color, int thickness)
|
|
{
|
|
for (int y = 0; y < height; y++)
|
|
{
|
|
for (int x = 0; x < width; x++)
|
|
{
|
|
float dx = (x - center.x) / rx;
|
|
float dy = (y - center.y) / ry;
|
|
float dist = dx * dx + dy * dy;
|
|
float outer = 1 + (thickness / Mathf.Min(rx, ry));
|
|
float inner = 1 - (thickness / Mathf.Min(rx, ry));
|
|
if (dist >= inner && dist <= outer)
|
|
{
|
|
pixels[y * width + x] = color;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CreateUICanvas()
|
|
{
|
|
// World Space Canvas
|
|
var canvasObj = new GameObject("UICanvas");
|
|
canvasObj.transform.SetParent(transform);
|
|
canvasObj.transform.localPosition = uiOffset;
|
|
canvasObj.transform.localScale = Vector3.one * uiScale;
|
|
|
|
_uiCanvas = canvasObj.AddComponent<Canvas>();
|
|
_uiCanvas.renderMode = RenderMode.WorldSpace;
|
|
_uiCanvas.sortingOrder = sortingOrder + 1;
|
|
|
|
var canvasRect = canvasObj.GetComponent<RectTransform>();
|
|
canvasRect.sizeDelta = new Vector2(400, 150);
|
|
|
|
// Add billboard to canvas (configured for UI - full facing)
|
|
_uiBillboard = canvasObj.AddComponent<Billboard>();
|
|
_uiBillboard.ConfigureForUI();
|
|
|
|
// Create UI panel
|
|
var panel = CreateUIPanel(canvasObj.transform, new Vector2(350, 120));
|
|
|
|
// Name label
|
|
_nameLabel = CreateUIText(panel.transform, "NameLabel", "Agent", 36, Color.white, FontStyles.Bold);
|
|
SetRectPosition(_nameLabel.rectTransform, 0, 45, 320, 45);
|
|
|
|
// Personality label
|
|
_personalityLabel = CreateUIText(panel.transform, "PersonalityLabel", "(Personality)", 20,
|
|
new Color(0.8f, 0.8f, 0.8f), FontStyles.Italic);
|
|
SetRectPosition(_personalityLabel.rectTransform, 0, 15, 320, 25);
|
|
|
|
// HP Bar
|
|
var hpBar = CreateProgressBar(panel.transform, "HPBar", "HP", hpHighColor, out _hpBarFill, out _hpText);
|
|
SetRectPosition(hpBar, 0, -15, 280, 24);
|
|
|
|
// Energy Bar
|
|
var energyBar = CreateProgressBar(panel.transform, "EnergyBar", "Energy", energyHighColor, out _energyBarFill, out _energyText);
|
|
SetRectPosition(energyBar, 0, -45, 280, 24);
|
|
|
|
// Death overlay
|
|
_deathOverlay = CreateDeathOverlay(panel.transform);
|
|
_deathOverlay.SetActive(false);
|
|
}
|
|
|
|
private GameObject CreateUIPanel(Transform parent, Vector2 size)
|
|
{
|
|
var panel = new GameObject("Panel");
|
|
panel.transform.SetParent(parent);
|
|
panel.transform.localPosition = Vector3.zero;
|
|
panel.transform.localRotation = Quaternion.identity;
|
|
panel.transform.localScale = Vector3.one;
|
|
|
|
var rect = panel.AddComponent<RectTransform>();
|
|
rect.sizeDelta = size;
|
|
rect.anchoredPosition = Vector2.zero;
|
|
|
|
var bg = panel.AddComponent<Image>();
|
|
bg.color = new Color(0, 0, 0, 0.6f);
|
|
|
|
return panel;
|
|
}
|
|
|
|
private TextMeshProUGUI CreateUIText(Transform parent, string name, string text,
|
|
float fontSize, Color color, FontStyles style = FontStyles.Normal)
|
|
{
|
|
var textObj = new GameObject(name);
|
|
textObj.transform.SetParent(parent);
|
|
textObj.transform.localPosition = Vector3.zero;
|
|
textObj.transform.localRotation = Quaternion.identity;
|
|
textObj.transform.localScale = Vector3.one;
|
|
|
|
var tmp = textObj.AddComponent<TextMeshProUGUI>();
|
|
tmp.text = text;
|
|
tmp.fontSize = fontSize;
|
|
tmp.color = color;
|
|
tmp.fontStyle = style;
|
|
tmp.alignment = TextAlignmentOptions.Center;
|
|
|
|
return tmp;
|
|
}
|
|
|
|
private RectTransform CreateProgressBar(Transform parent, string name, string label,
|
|
Color fillColor, out Image fillImage, out TextMeshProUGUI valueText)
|
|
{
|
|
var container = new GameObject(name);
|
|
container.transform.SetParent(parent);
|
|
container.transform.localPosition = Vector3.zero;
|
|
container.transform.localRotation = Quaternion.identity;
|
|
container.transform.localScale = Vector3.one;
|
|
|
|
var containerRect = container.AddComponent<RectTransform>();
|
|
|
|
// Background
|
|
var bg = new GameObject("Background");
|
|
bg.transform.SetParent(container.transform);
|
|
var bgImg = bg.AddComponent<Image>();
|
|
bgImg.color = new Color(0.15f, 0.15f, 0.15f, 0.9f);
|
|
var bgRect = bg.GetComponent<RectTransform>();
|
|
bgRect.anchorMin = Vector2.zero;
|
|
bgRect.anchorMax = Vector2.one;
|
|
bgRect.offsetMin = Vector2.zero;
|
|
bgRect.offsetMax = Vector2.zero;
|
|
bgRect.localPosition = Vector3.zero;
|
|
bgRect.localScale = Vector3.one;
|
|
|
|
// Fill
|
|
var fill = new GameObject("Fill");
|
|
fill.transform.SetParent(container.transform);
|
|
fillImage = fill.AddComponent<Image>();
|
|
fillImage.color = fillColor;
|
|
var fillRect = fill.GetComponent<RectTransform>();
|
|
fillRect.anchorMin = Vector2.zero;
|
|
fillRect.anchorMax = Vector2.one;
|
|
fillRect.pivot = new Vector2(0, 0.5f);
|
|
fillRect.offsetMin = new Vector2(2, 2);
|
|
fillRect.offsetMax = new Vector2(-2, -2);
|
|
fillRect.localPosition = Vector3.zero;
|
|
fillRect.localScale = Vector3.one;
|
|
|
|
// Text
|
|
valueText = CreateUIText(container.transform, "Text", $"{label}: 100", 16, Color.white);
|
|
var textRect = valueText.rectTransform;
|
|
textRect.anchorMin = Vector2.zero;
|
|
textRect.anchorMax = Vector2.one;
|
|
textRect.offsetMin = Vector2.zero;
|
|
textRect.offsetMax = Vector2.zero;
|
|
|
|
return containerRect;
|
|
}
|
|
|
|
private GameObject CreateDeathOverlay(Transform parent)
|
|
{
|
|
var overlay = new GameObject("DeathOverlay");
|
|
overlay.transform.SetParent(parent);
|
|
overlay.transform.localPosition = Vector3.zero;
|
|
overlay.transform.localRotation = Quaternion.identity;
|
|
overlay.transform.localScale = Vector3.one;
|
|
|
|
var rect = overlay.AddComponent<RectTransform>();
|
|
rect.anchorMin = Vector2.zero;
|
|
rect.anchorMax = Vector2.one;
|
|
rect.offsetMin = Vector2.zero;
|
|
rect.offsetMax = Vector2.zero;
|
|
|
|
var img = overlay.AddComponent<Image>();
|
|
img.color = new Color(0.2f, 0f, 0f, 0.8f);
|
|
|
|
var deathText = CreateUIText(overlay.transform, "DeathText", "DEAD", 32, Color.red, FontStyles.Bold);
|
|
deathText.rectTransform.anchorMin = Vector2.zero;
|
|
deathText.rectTransform.anchorMax = Vector2.one;
|
|
deathText.rectTransform.offsetMin = Vector2.zero;
|
|
deathText.rectTransform.offsetMax = Vector2.zero;
|
|
|
|
return overlay;
|
|
}
|
|
|
|
private void CreateSpeechBubble()
|
|
{
|
|
// Create speech bubble canvas
|
|
var bubbleCanvas = new GameObject("SpeechCanvas");
|
|
bubbleCanvas.transform.SetParent(transform);
|
|
bubbleCanvas.transform.localPosition = speechOffset;
|
|
bubbleCanvas.transform.localScale = Vector3.one * uiScale;
|
|
|
|
var canvas = bubbleCanvas.AddComponent<Canvas>();
|
|
canvas.renderMode = RenderMode.WorldSpace;
|
|
canvas.sortingOrder = sortingOrder + 2;
|
|
|
|
var canvasRect = bubbleCanvas.GetComponent<RectTransform>();
|
|
canvasRect.sizeDelta = new Vector2(400, 100);
|
|
|
|
// Add billboard (configured for UI - full facing)
|
|
var bubbleBillboard = bubbleCanvas.AddComponent<Billboard>();
|
|
bubbleBillboard.ConfigureForUI();
|
|
|
|
// Create speech bubble
|
|
_speechBubble = SpeechBubble.Create(bubbleCanvas.transform, Vector3.zero);
|
|
_speechBubble.DisplayDuration = speechDuration;
|
|
}
|
|
|
|
private void CreateCollider()
|
|
{
|
|
if (GetComponent<Collider>() == null)
|
|
{
|
|
var col = gameObject.AddComponent<CapsuleCollider>();
|
|
col.height = 2.5f;
|
|
col.radius = 0.6f;
|
|
col.center = new Vector3(0, 1.25f, 0);
|
|
}
|
|
}
|
|
|
|
private void SetRectPosition(RectTransform rect, float x, float y, float width, float height)
|
|
{
|
|
rect.anchoredPosition = new Vector2(x, y);
|
|
rect.sizeDelta = new Vector2(width, height);
|
|
rect.localPosition = new Vector3(x, y, 0);
|
|
rect.localScale = Vector3.one;
|
|
}
|
|
#endregion
|
|
|
|
#region Stats Update
|
|
public void UpdateStats(AgentData data)
|
|
{
|
|
_currentData = data;
|
|
|
|
// Update HP bar
|
|
float hpPercent = data.hp / 100f;
|
|
if (_hpBarFill != null)
|
|
{
|
|
_hpBarFill.rectTransform.anchorMax = new Vector2(hpPercent, 1);
|
|
_hpBarFill.color = Color.Lerp(hpLowColor, hpHighColor, hpPercent);
|
|
}
|
|
if (_hpText != null)
|
|
{
|
|
_hpText.text = $"HP: {data.hp}";
|
|
}
|
|
|
|
// Update Energy bar
|
|
float energyPercent = data.energy / 100f;
|
|
if (_energyBarFill != null)
|
|
{
|
|
_energyBarFill.rectTransform.anchorMax = new Vector2(energyPercent, 1);
|
|
_energyBarFill.color = Color.Lerp(energyLowColor, energyHighColor, energyPercent);
|
|
}
|
|
if (_energyText != null)
|
|
{
|
|
_energyText.text = $"Energy: {data.energy}";
|
|
}
|
|
|
|
// Update death state
|
|
if (!data.IsAlive)
|
|
{
|
|
OnDeath();
|
|
}
|
|
else
|
|
{
|
|
OnAlive();
|
|
}
|
|
}
|
|
|
|
private void OnDeath()
|
|
{
|
|
if (_deathOverlay != null) _deathOverlay.SetActive(true);
|
|
if (_speechBubble != null) _speechBubble.Hide();
|
|
|
|
// Gray out sprite
|
|
if (_spriteRenderer != null)
|
|
{
|
|
_spriteRenderer.color = new Color(0.3f, 0.3f, 0.3f, 0.7f);
|
|
}
|
|
}
|
|
|
|
private void OnAlive()
|
|
{
|
|
if (_deathOverlay != null) _deathOverlay.SetActive(false);
|
|
|
|
// Restore sprite color
|
|
if (_spriteRenderer != null)
|
|
{
|
|
_spriteRenderer.color = Color.white;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Speech
|
|
public void ShowSpeech(string text)
|
|
{
|
|
if (_speechBubble == null || !IsAlive) return;
|
|
|
|
_speechBubble.Setup(text);
|
|
Debug.Log($"[AgentVisual] {_currentData?.name} says: \"{text}\"");
|
|
}
|
|
|
|
public void HideSpeech()
|
|
{
|
|
if (_speechBubble != null)
|
|
{
|
|
_speechBubble.Hide();
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Public API
|
|
/// <summary>
|
|
/// Set the character sprite at runtime.
|
|
/// </summary>
|
|
public void SetSprite(Sprite sprite)
|
|
{
|
|
characterSprite = sprite;
|
|
if (_spriteRenderer != null)
|
|
{
|
|
_spriteRenderer.sprite = sprite;
|
|
_spriteRenderer.color = spriteColor;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the character color (for placeholder or tinting).
|
|
/// </summary>
|
|
public void SetColor(Color bodyColor, Color outlineColor)
|
|
{
|
|
placeholderBodyColor = bodyColor;
|
|
placeholderOutlineColor = outlineColor;
|
|
|
|
if (characterSprite == null)
|
|
{
|
|
RegeneratePlaceholderSprite();
|
|
}
|
|
else
|
|
{
|
|
_spriteRenderer.color = bodyColor;
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|