ProjectDDD/Packages/SLUnity/SLUI/SLUIValue.cs
2025-06-25 11:33:17 +09:00

514 lines
18 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace Superlazy.UI
{
public class SLUIValue : SLUIComponent
{
public string prefix;
public string postfix;
public bool useRaw;
public bool userInput;
public bool useUpdate;
private bool UseTag => tagID != string.Empty;
public string tagID;
public string bindingValue;
public string bindingMaxValue;
[NonSerialized]
public TextMeshProUGUI text;
[NonSerialized]
public Image image;
[NonSerialized]
public Slider slider;
private SLEntity oldValue = false;
private float oldMaxValue = -10000;
// text only
public bool useTypingEffect = false;
public float startDelay = 0.2f;
public float delay = 0.03f;
public string typingEndState = string.Empty;
public string typingEndBind = "Skipped";
public bool ignoreColorTag = false;
public string textSound = "SoundEffect/SE_Text";
// smoothing
public bool useSmoothing = false;
public float smoothingDelay = 1.0f;
private float smoothingValue = 0;
public string beforeSmoothingValue = string.Empty;
protected override void Validate()
{
text = GetComponent<TextMeshProUGUI>();
image = GetComponent<Image>();
slider = GetComponent<Slider>();
}
protected override void Init()
{
}
protected override void Enable()
{
if (text != null)
{
SLUIUtil.UpdateFont(text);
}
oldValue = false;
oldMaxValue = 0.0f;
if (useUpdate && useRaw && bindingValue.Contains("{"))
{
useTypingEffect = false;
useUpdate = true;
}
if (useUpdate)
{
SLGame.AddNotify("Time", OnChange);
}
if (useRaw)
{
OnChange();
return;
}
SLGame.AddNotify(bindParent.BindPath.CombinePath(bindingValue), OnChange);
if (bindingMaxValue != string.Empty)
{
SLGame.AddNotify(bindParent.BindPath.CombinePath(bindingMaxValue), OnChange);
}
if (text != null && useTypingEffect && typingEndBind != string.Empty)
{
SLGame.AddNotify(bindParent.BindPath.CombinePath(typingEndBind), OnSkipped);
}
}
protected override void Disable()
{
if (oldMaxValue <= -1000) return; // TEMP: SLUIValue Disabled가 최초 Enabled도 불리기 전에 불릴 가능성이 있음
if (text != null && useTypingEffect && typingEndBind != string.Empty)
{
SLGame.RemoveNotify(bindParent.BindPath.CombinePath(typingEndBind), OnSkipped);
}
if (useTypingEffect && string.IsNullOrEmpty(typingEndState) == false)
{
SLGame.Session["UIState"][typingEndState] = false;
}
if (useUpdate)
{
SLGame.RemoveNotify("Time", OnChange);
}
if (useRaw) return;
SLGame.RemoveNotify(bindParent.BindPath.CombinePath(bindingValue), OnChange);
if (bindingMaxValue != string.Empty)
{
SLGame.RemoveNotify(bindParent.BindPath.CombinePath(bindingMaxValue), OnChange);
}
}
private void OnChange()
{
if (bindParent.Active == false) return;
var sessionRoot = SLGame.SessionGet(bindParent.BindPath);
if (sessionRoot == false) return; // TEMP: 세션루트가 삭제되었지만, 삭제되되기전 코루틴 처리가 있을 수 있음
if (text != null)
{
OnChangeText();
}
else if (image != null)
{
OnChangeImage();
}
else if (slider != null)
{
OnChangeSlider();
}
}
private void OnChangeText()
{
if (useRaw)
{
text.text = SLString.GetString(bindingValue, SLGame.SessionGet(bindParent.BindPath));
}
else if (userInput)
{
text.text = new StringBuilder(SLString.GetString(prefix, SLGame.SessionGet(bindParent.BindPath)))
.Append(SLGame.SessionGet(bindParent.BindPath).Get(bindingValue).ToString())
.Append(SLString.GetString(postfix, SLGame.SessionGet(bindParent.BindPath))).ToString();
}
else if (UseTag)
{
text.text = new StringBuilder(SLString.GetString(prefix, SLGame.SessionGet(bindParent.BindPath)))
.Append(SLString.GetString(SLTag.Apply(SLGame.SessionGet(bindParent.BindPath).Get(bindingValue), tagID), SLGame.SessionGet(bindParent.BindPath)))
.Append(SLString.GetString(postfix, SLGame.SessionGet(bindParent.BindPath))).ToString();
}
else
{
text.text = new StringBuilder(SLString.GetString(prefix, SLGame.SessionGet(bindParent.BindPath)))
.Append(SLString.GetString(SLGame.SessionGet(bindParent.BindPath).Get(bindingValue), SLGame.SessionGet(bindParent.BindPath)))
.Append(SLString.GetString(postfix, SLGame.SessionGet(bindParent.BindPath))).ToString();
}
if (ignoreColorTag)
{
text.text = RemoveColorTag(text.text);
}
if (useTypingEffect)
{
StopAllCoroutines();
StartCoroutine(ShowText());
}
if (useSmoothing)
{
StopAllCoroutines();
StartCoroutine(SmoothingText());
}
}
private void OnChangeImage()
{
var sessionRoot = SLGame.SessionGet(bindParent.BindPath);
var newValue = sessionRoot.Get(bindingValue);
if (UseTag)
{
newValue = SLTag.Apply(newValue, tagID);
}
if (oldValue == newValue)
{
return;
}
oldValue = newValue;
image.sprite = SLResources.GetSprite(newValue);
if (image.sprite == null)
{
SLLog.Error($"Can't Find Sprite. [{bindParent.BindPath}.{bindingValue}]: [{newValue}]");
}
}
private void OnChangeSlider()
{
var sessionRoot = SLGame.SessionGet(bindParent.BindPath);
float newValue = sessionRoot.Get(bindingValue);
float newMaxValue = SLGame.SessionGet(bindParent.BindPath).Get(bindingMaxValue);
if (oldValue == newValue && oldMaxValue == newMaxValue) return;
if (newValue < 0.0)
{
SLLog.Warn($"value can't negative. {bindParent.BindPath}.{bindingValue} : {newValue}", this);
return;
}
if (newMaxValue <= 0.0)
{
SLLog.Warn($"max value can't negative or zero. {bindParent.BindPath}.{bindingMaxValue} : {newMaxValue}", this);
return;
}
if (useSmoothing && smoothingValue < 0)
{
smoothingValue = oldValue;
}
oldValue = newValue;
oldMaxValue = newMaxValue;
if (useSmoothing)
{
StopAllCoroutines();
StartCoroutine(SmoothingSlider());
}
else
{
slider.value = oldValue / oldMaxValue;
slider.maxValue = 1;
}
}
// TypingEffect
private void OnSkipped()
{
var skipped = SLGame.SessionGet(bindParent.BindPath).Get(typingEndBind);
if (skipped)
{
StopAllCoroutines();
if (useRaw)
{
text.text = SLString.GetString(bindingValue, SLGame.SessionGet(bindParent.BindPath));
}
else if (userInput)
{
text.text = new StringBuilder(SLString.GetString(prefix, SLGame.SessionGet(bindParent.BindPath)))
.Append(SLGame.SessionGet(bindParent.BindPath).Get(bindingValue).ToString())
.Append(SLString.GetString(postfix, SLGame.SessionGet(bindParent.BindPath))).ToString();
}
else if (UseTag)
{
text.text = new StringBuilder(SLString.GetString(prefix, SLGame.SessionGet(bindParent.BindPath)))
.Append(SLString.GetString(SLTag.Apply(SLGame.SessionGet(bindParent.BindPath).Get(bindingValue), tagID), SLGame.SessionGet(bindParent.BindPath)))
.Append(SLString.GetString(postfix, SLGame.SessionGet(bindParent.BindPath))).ToString();
}
else
{
text.text = new StringBuilder(SLString.GetString(prefix, SLGame.SessionGet(bindParent.BindPath)))
.Append(SLString.GetString(SLGame.SessionGet(bindParent.BindPath).Get(bindingValue), SLGame.SessionGet(bindParent.BindPath)))
.Append(SLString.GetString(postfix, SLGame.SessionGet(bindParent.BindPath))).ToString();
}
if (string.IsNullOrEmpty(typingEndState) == false)
{
SLGame.Session["UIState"][typingEndState] = true;
}
}
}
private IEnumerator ShowText()
{
var originalText = text.text;
var displayedText = new StringBuilder();
displayedText.Append("<color=#00000000>");
displayedText.Append(RemoveColorTag(originalText));
displayedText.Append("</color>");
text.text = displayedText.ToString();
yield return new WaitForSeconds(startDelay);
var currentIndex = -1;
var index = -1;
var openTags = new Stack<string>();
var startTime = Time.time;
while (true)
{
var elapsedTime = Time.time - startTime;
var newIndex = Mathf.FloorToInt(elapsedTime / delay);
if (currentIndex == -1) // 최초 0문자지만 여백을 다 채워두기 위한 트릭코드
{
newIndex = 0;
}
if (newIndex > currentIndex)
{
for (var i = 0; i < newIndex - currentIndex; i++)
{
if (index + 1 >= originalText.Length)
{
break;
}
index += 1;
while (index < originalText.Length && originalText[index] == '<')
{
var endIndex = originalText.IndexOf('>', index);
var tag = originalText.Substring(index + 1, endIndex - index - 1);
if (tag.StartsWith("/") == false)
{
openTags.Push(tag);
}
else
{
openTags.Pop();
}
index = endIndex + 1;
}
}
currentIndex = newIndex;
displayedText.Clear();
displayedText.Append(originalText.Substring(0, Math.Min(originalText.Length, index + 1)));
if (index < originalText.Length)
{
foreach (var tag in openTags)
{
var openTag = tag;
if (openTag.Contains("=")) openTag = openTag.Substring(0, openTag.IndexOf("="));
displayedText.Append($"</{openTag}>");
}
displayedText.Append("<color=#00000000>");
displayedText.Append(RemoveColorTag(originalText.Substring(index + 1, originalText.Length - index - 1)));
displayedText.Append("</color>");
}
text.text = displayedText.ToString();
SLSound.PlaySound(textSound, "TextSound", false, false, true, 0.5f);
}
if (index + 1 >= originalText.Length)
{
break;
}
yield return null;
}
if (bindParent.Active && string.IsNullOrEmpty(typingEndBind) == false)
{
SLGame.SessionGet(bindParent.BindPath).Set(typingEndBind, true);
}
if (string.IsNullOrEmpty(typingEndState) == false)
{
SLGame.Session["UIState"][typingEndState] = true;
}
}
private string RemoveColorTag(string text)
{
var result = new StringBuilder(text.Length);
var index = 0;
while (index < text.Length)
{
if (text[index] == '<' && text.Substring(index, 6).Equals("<color", StringComparison.OrdinalIgnoreCase))
{
var endIndex = text.IndexOf('>', index) + 1;
index = endIndex;
}
else if (text[index] == '<' && text.Substring(index, 7).Equals("</color", StringComparison.OrdinalIgnoreCase))
{
var endIndex = text.IndexOf('>', index) + 1;
index = endIndex;
}
else
{
result.Append(text[index]);
index++;
}
}
return result.ToString();
}
private IEnumerator SmoothingSlider()
{
var startTime = Time.time;
while (true)
{
var time = Time.time - startTime;
smoothingValue = Mathf.Lerp(smoothingValue, oldValue, time / smoothingDelay);
if (time >= smoothingDelay)
{
break;
}
slider.value = smoothingValue / oldMaxValue;
slider.maxValue = 1;
yield return null;
}
slider.value = oldValue / oldMaxValue;
slider.maxValue = 1;
}
private IEnumerator SmoothingText()
{
var startTime = Time.time;
smoothingValue = SLGame.SessionGet(bindParent.BindPath).Get(beforeSmoothingValue);
float destValue = SLGame.SessionGet(bindParent.BindPath).Get(bindingValue);
while (true)
{
var time = Time.time - startTime;
smoothingValue = Mathf.Lerp(smoothingValue, destValue, time / smoothingDelay);
if (time >= smoothingDelay)
{
break;
}
if (useRaw)
{
text.text = smoothingValue.ToString();
}
else if (userInput)
{
text.text = new StringBuilder(SLString.GetString(prefix, SLGame.SessionGet(bindParent.BindPath)))
.Append(smoothingValue.ToString())
.Append(SLString.GetString(postfix, SLGame.SessionGet(bindParent.BindPath))).ToString();
}
else if (UseTag)
{
text.text = new StringBuilder(SLString.GetString(prefix, SLGame.SessionGet(bindParent.BindPath)))
.Append(SLString.GetString(SLTag.Apply(smoothingValue, tagID), SLGame.SessionGet(bindParent.BindPath)))
.Append(SLString.GetString(postfix, SLGame.SessionGet(bindParent.BindPath))).ToString();
}
else
{
text.text = new StringBuilder(SLString.GetString(prefix, SLGame.SessionGet(bindParent.BindPath)))
.Append(SLString.GetString(smoothingValue.ToString(), SLGame.SessionGet(bindParent.BindPath)))
.Append(SLString.GetString(postfix, SLGame.SessionGet(bindParent.BindPath))).ToString();
}
yield return null;
}
if (useRaw)
{
text.text = SLString.GetString(bindingValue, SLGame.SessionGet(bindParent.BindPath));
}
else if (userInput)
{
text.text = new StringBuilder(SLString.GetString(prefix, SLGame.SessionGet(bindParent.BindPath)))
.Append(SLGame.SessionGet(bindParent.BindPath).Get(bindingValue).ToString())
.Append(SLString.GetString(postfix, SLGame.SessionGet(bindParent.BindPath))).ToString();
}
else if (UseTag)
{
text.text = new StringBuilder(SLString.GetString(prefix, SLGame.SessionGet(bindParent.BindPath)))
.Append(SLString.GetString(SLTag.Apply(SLGame.SessionGet(bindParent.BindPath).Get(bindingValue), tagID), SLGame.SessionGet(bindParent.BindPath)))
.Append(SLString.GetString(postfix, SLGame.SessionGet(bindParent.BindPath))).ToString();
}
else
{
text.text = new StringBuilder(SLString.GetString(prefix, SLGame.SessionGet(bindParent.BindPath)))
.Append(SLString.GetString(SLGame.SessionGet(bindParent.BindPath).Get(bindingValue), SLGame.SessionGet(bindParent.BindPath)))
.Append(SLString.GetString(postfix, SLGame.SessionGet(bindParent.BindPath))).ToString();
}
}
}
}