ProjectDDD/Packages/SLUnity/UnitSytem/View/UnitView.cs
2025-06-25 11:33:17 +09:00

1188 lines
38 KiB (Stored with Git LFS)
C#

using System.Collections.Generic;
using System.Linq;
using Superlazy;
using UnityEngine;
public abstract class UnitViewComponent : MonoBehaviour
{
private UnitView unitView;
protected SLEntity Entity => unitView.Entity;
protected IZoneView Zone => unitView.Zone;
protected Vector3 Position => unitView.Position;
protected Quaternion Rotation => unitView.Rotation;
protected Vector3 Scale => unitView.Scale;
protected Vector3 EffectScale => unitView.EffectScale;
protected void HitEffect(SLEntity context, SLEntity playEffect)
{
unitView.HitEffect(context, playEffect);
}
protected void BuffEffect(SLEntity context, SLEntity playEffect)
{
unitView.BuffEffect(context, playEffect);
}
public void Init(UnitView unitView)
{
this.unitView = unitView;
}
public abstract void Init();
public abstract void Bind();
public abstract void Unbind();
}
public class UnitView : MonoBehaviour, IUnitAnimatorBinder, IUnitSpriteBinder
{
private static Queue<UnitView> unused;
private static Transform unusedRoot;
private static List<System.Type> unitViewComponents;
private static int useSceneLightId;
private static int outlineId;
private static int enemyId;
private static int[] shaderKeywords;
private class ShaderInfo
{
public int shader;
public float onSpeed = -1;
public float offSpeed = -1;
public float max = -1;
public float activeValue = -1;
public string valueContext = null;
}
private static ShaderInfo hitGlowShaderInfo;
private static Dictionary<string, ShaderInfo> shaderInfos;
private class ShaderCheck
{
public ShaderInfo shaderInfo;
public string checkKey;
public string checkValue;
}
private static List<List<ShaderCheck>> shaderChecks;
public static void InitGlobal()
{
unused = new Queue<UnitView>();
unusedRoot = new GameObject("UnusedUnitView").transform;
unusedRoot.gameObject.SetActive(false);
unitViewComponents = new List<System.Type>();
unitViewComponents.CreateTypeList<UnitViewComponent>();
useSceneLightId = Shader.PropertyToID("UseSceneLight");
outlineId = Shader.PropertyToID("Outline");
enemyId = Shader.PropertyToID("Enemy");
var propertyIDs = new Dictionary<string, int>();
foreach (var shader in SLSystem.Data["Shaders"])
{
propertyIDs[shader["Keyword"]] = Shader.PropertyToID(shader["Keyword"]);
}
shaderKeywords = propertyIDs.Values.ToArray();
shaderInfos = new Dictionary<string, ShaderInfo>();
foreach (var shader in SLSystem.Data["Shaders"])
{
var shaderInfo = new ShaderInfo();
shaderInfo.shader = Shader.PropertyToID(shader["Keyword"]);
if (shader["Keyword"] == "HitGlow")
{
hitGlowShaderInfo = shaderInfo;
}
if (shader["OnSpeed"])
{
shaderInfo.onSpeed = shader["OnSpeed"];
}
if (shader["OffSpeed"])
{
shaderInfo.offSpeed = shader["OffSpeed"];
}
if (shader["Max"])
{
shaderInfo.max = shader["Max"];
}
if (shader["ActiveValue"])
{
shaderInfo.activeValue = shader["ActiveValue"];
}
if (shader["ValueContext"])
{
shaderInfo.valueContext = shader["ValueContext"];
}
shaderInfos.Add(shader.ID, shaderInfo);
}
shaderChecks = new List<List<ShaderCheck>>();
foreach (var ss in SLSystem.Data["Shaders"].Where(e => e["CheckKey"]).GroupBy(e => e["Keyword"]))
{
var group = new List<ShaderCheck>();
foreach (var s in ss)
{
var check = new ShaderCheck();
check.shaderInfo = shaderInfos[s.ID];
check.checkKey = s["CheckKey"];
if (s["CheckValue"])
{
check.checkValue = s["CheckValue"];
}
group.Add(check);
}
group.Reverse();
shaderChecks.Add(group);
}
}
public static UnitView MakeUnit(IZoneView zoneView, SLEntity unit)
{
UnitView newUnit;
if (unused != null && unused.Count != 0)
{
newUnit = unused.Dequeue();
}
else
{
var newObject = new GameObject();
newObject.SetActive(false);
newUnit = newObject.AddComponent<UnitView>();
newUnit.Init();
}
newUnit.transform.SetParent(zoneView.Root, false);
newUnit.gameObject.SetActive(true);
newUnit.BindUnit(zoneView, unit);
// TODO: 확장 컴포넌트 사용 가능한 구조로 개선
if (newUnit.Entity["UseSpawnEffect"])
{
newUnit.gameObject.AddComponent<SpawnEffect>();
}
return newUnit;
}
public static void RemoveUnit(UnitView unitView)
{
unitView.UnbindUnit();
unused.Enqueue(unitView);
unitView.gameObject.SetActive(false);
unitView.transform.SetParent(unusedRoot);
}
private GameObject offsetRoot;
private SLResourceObject baseRoot;
private SLResourceObject shadow;
public SLEntity Entity { get; private set; }
public IZoneView Zone { get; private set; }
public Vector3 Position => transform.position; // offset만 미적용
public Quaternion Rotation => transform.rotation;
public Vector3 Scale => offsetRoot.transform.localScale;
public Vector3 EffectScale => Entity.HasChild("EffectScale") ? Vector3.one * Entity["EffectScale"] : Vector3.one;
// animation
public bool UseAnimation => Entity.HasChild("CurrentAction");
public string Animation => Entity["CurrentAction"]["Animation"];
public bool RequireReset => Entity["CurrentAction"]["RequireReset"];
public int CurrentFrame => Entity["CurrentAction"]["CurrentFrame"];
public bool UseActionAnimationBegin => Entity["CurrentAction"].HasChild("AnimationBegin");
public float ActionAnimationBegin => Entity["CurrentAction"]["AnimationBegin"];
public bool UseCrossFade => SLGame.Session.HasChild("Camp") == false && Entity.HasChild("ActionStop") == false && Entity["CurrentAction"].HasChild("NoBeginBlending") == false;
public float CrossFade => Entity["CurrentAction"].HasChild("CrossFade") ? (float)Entity["CurrentAction"]["CrossFade"] : 0.1f;
public bool ActionStop => Entity["ActionStop"];
public bool Loop => Entity["CurrentAction"].HasChild("Loop");
// sprite
public string SpritePath => Entity["Sprite"];
private Dictionary<int, float> currentShaderValues;
private Dictionary<int, ShaderInfo> currentShaderInfos;
private Dictionary<int, bool> currentShaderOnOffs;
private Dictionary<string, EffectView> buffEffects;
private Dictionary<string, EffectView> actionEffects;
private Dictionary<string, EffectView> customEffects;
private Dictionary<string, EffectView> attachParts;
private Dictionary<string, string> buffs;
private List<UnitViewComponent> components;
public void Init()
{
offsetRoot = new GameObject("OffsetRoot");
offsetRoot.transform.SetParent(transform, false);
currentShaderValues = new Dictionary<int, float>();
currentShaderInfos = new Dictionary<int, ShaderInfo>();
currentShaderOnOffs = new Dictionary<int, bool>();
buffEffects = new Dictionary<string, EffectView>();
actionEffects = new Dictionary<string, EffectView>();
customEffects = new Dictionary<string, EffectView>();
attachParts = new Dictionary<string, EffectView>();
buffs = new Dictionary<string, string>();
updators = new List<IUnitViewUpdator>();
components = new List<UnitViewComponent>();
foreach (var unitViewComponent in unitViewComponents)
{
var component = gameObject.AddComponent(unitViewComponent) as UnitViewComponent;
components.Add(component);
component.Init(this);
}
}
public void OnDisable()
{
offsetRoot.transform.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
}
private void BindUnit(IZoneView zoneView, SLEntity unit)
{
Zone = zoneView;
Entity = unit;
gameObject.name = Entity["UnitType"] + Entity["Handle"];
// 특수 처리들 추가
if (Entity["Components"]["Projectile"]) // 프로젝타일 전용 특수 처리 추가
{
foreach (var buff in Entity["Context"]["TargetBuffs"])
{
var effect = SLSystem.Data["Effects"][buff["BuffEffect"]];
if (effect["Projectile"] && customEffects.ContainsKey(buff["BuffEffect"]) == false) // 투사체용 이펙트
{
customEffects.Add(buff["BuffEffect"], EffectView.MakeUnitEffect(this, effect["Projectile"]["Effect"], effect["Projectile"]));
}
}
}
Update();
}
private void UnbindUnit()
{
if (Entity["EndEffect"])
{
var position = Entity["EndEffectPosition"] ? Entity["EndEffectPosition"].ToVector3() : Entity["Position"].ToVector3();
var effect = EffectView.MakeEffect(Zone, Entity["EndEffect"]["Effect"], Entity["EndEffect"], position, Quaternion.Euler(Entity["Forward"].ToVector3()), Vector3.one);
effect.gameObject.SetActive(true);
}
if (Entity["EndAreaEffect"])
{
var context = Entity["EndAreaEffect"];
var position = context["Position"] ? context["Position"].ToVector3() : Entity["Position"].ToVector3();
var area = SLSystem.Data["Ranges"][Entity["TargetContext"]["Area"]];
var minArea = SLEntity.Empty;
if (Entity["TargetContext"]["MinArea"])
{
minArea = SLSystem.Data["Ranges"][Entity["TargetContext"]["MinArea"]];
}
foreach (var range in area)
{
if (minArea && minArea.Any(ma => ma["X"] == range["X"] && ma["Y"] == range["Y"])) continue;
//var cellForward = Entity["Forward"].ForwardToCellForward();
//var ax = CellUtil.CellRotation(cellForward, range["X"], range["Y"], "X");
//var ay = CellUtil.CellRotation(cellForward, range["X"], range["Y"], "Y");
//var newPosition = position + new Vector3(ax, 0, ay);
//var effect = EffectView.MakeEffect(Zone, context["Effect"], context, newPosition, Quaternion.Euler(Entity["Forward"].ToVector3()), Vector3.one);
//effect.gameObject.SetActive(true);
}
}
Update();
foreach (var customEffect in customEffects.Values)
{
customEffect.EndEffect();
}
customEffects.Clear();
UnbindBaseRoot();
foreach (var component in components)
{
component.Unbind();
}
gameObject.SetActive(false);
}
private void UnbindBaseRoot()
{
if (baseRoot == null) return;
currentShaderValues.Clear();
currentShaderInfos.Clear();
currentShaderOnOffs.Clear();
//TODO: ObjectOffs도 별도 리스트로 관리하기(엔티티는 오염될가능성이 있어서)
foreach (var offs in Entity["ObjectOffs"])
{
var t = baseRoot.GetTransform(offs.ID);
t.gameObject.SetActive(true);
}
baseRoot.Destroy();
baseRoot = null;
if (shadow)
{
shadow.Destroy();
shadow = null;
}
foreach (var attachPart in attachParts)
{
attachPart.Value.EndEffect();
}
attachParts.Clear();
}
public void UpdateAppearance()
{
if (baseRoot && Entity["Base"] != baseRoot.name)
{
UnbindBaseRoot();
}
if (baseRoot == null && Entity["Base"])
{
baseRoot = SLResources.CreateInstance(Entity["Base"], offsetRoot.transform);
// BaseSwap이 일어날때만 업데이트 해주는게 맞을까?
foreach (var attachEffect in Entity["AttachEffects"])
{
attachParts.Add(attachEffect.ID, EffectView.MakeUnitEffect(this, attachEffect["Effect"], attachEffect));
}
var isEnemy = false;
if (Entity["UnitType"] == "Monster" && Entity.HasChild("FieldData") && Entity["FieldData"]["Character"] &&
SLSystem.Data["Heroes"][Entity["FieldData"]["Character"]] && SLSystem.Data["Heroes"][Entity["FieldData"]["Character"]]["Ready"] == false) // TEMP: 영웅몹 체크용 임시 처리, 추후 확장 및 데이터기반으로 조정
{
isEnemy = true;
}
float useSceneLight = Entity.HasChild("UseSceneLight") ? (float)Entity["UseSceneLight"] : Zone.UseSceneLight ? 1 : 0;
var outlineDepth = Zone.Outline;
foreach (var r in baseRoot.renderers)
{
r.gameObject.layer = Zone.Layer;
r.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off;
foreach (var mat in r.materials)
{
mat.SetFloat(useSceneLightId, useSceneLight);
mat.SetFloat(outlineId, outlineDepth);
mat.SetFloat(enemyId, isEnemy ? 1 : 0);
foreach (var shaderKeyword in shaderKeywords)
{
mat.SetFloat(shaderKeyword, 0);
}
}
}
}
if (Entity.HasChild("ShadowRadius"))
{
if (shadow == null)
{
shadow = SLResources.CreateInstance("Shadow", transform);
}
// TODO: 성능상 이슈가 있다면 별도 객체로 분리
if (baseRoot && Entity.HasChild("ShadowBind"))
{
var shadowBind = baseRoot.GetTransform(Entity["ShadowBind"]);
shadow.transform.position = new Vector3(shadowBind.position.x, Zone.WorldHeight, shadowBind.position.z);
}
else
{
shadow.transform.position = new Vector3(transform.position.x, Zone.WorldHeight, transform.position.z);
}
var shadowRenderer = shadow.renderers.First();
if (transform.position.y - Zone.WorldHeight > 0) // 지상
{
var diff = transform.position.y - Zone.WorldHeight;
var ips = Entity["ShadowRadius"] <= 0.001 ? 0.001f : (float)Entity["ShadowRadius"];
shadow.transform.localScale = Entity["ShadowRadius"] * Vector3.one;
shadowRenderer.material.color = new Color(1, 1, 1, 1 - Mathf.Clamp01(diff / ips * 0.5f)); // 그림자 진하기가 점점 감소
}
else // 지하 // 사망 등
{
// 크기가 점점감소, HitHeight 만큼 파이면 없어짐
var diff = Zone.WorldHeight - transform.position.y;
var ips = Entity["ShadowRadius"] <= 0.001 ? 0.001f : (float)Entity["ShadowRadius"];
shadow.transform.localScale = Entity["ShadowRadius"] * Mathf.Clamp01(1 - 2 * (diff / ips)) * Vector3.one;
shadowRenderer.material.color = new Color(1, 1, 1, 1);
}
if (Entity.HasChild("ShadowAlpha"))
{
shadowRenderer.material.color = new Color(1, 1, 1, shadowRenderer.material.color.a * Entity["ShadowAlpha"]);
}
shadowRenderer.gameObject.layer = Zone.Layer;
}
else
{
if (shadow)
{
shadow.Destroy();
shadow = null;
}
}
if (baseRoot && Entity.HasChild("Base"))
{
UpdateShader();
UpdateAction();
UpdateUpdators();
}
}
private void UpdateAction()
{
var action = Entity["CurrentAction"];
if (action["ActionID"] != Entity["CurrentActionID"])
{
foreach (var eff in actionEffects)
{
eff.Value.EndEffect();
}
actionEffects.Clear();
foreach (var actionEffect in action["Effects"])
{
actionEffects.Add(actionEffect.ID, EffectView.MakeUnitEffect(this, actionEffect["Effect"], actionEffect));
}
foreach (var offs in Entity["ObjectOffs"])
{
var t = baseRoot.GetTransform(offs.ID);
t.gameObject.SetActive(true);
}
Entity["ObjectOffs"] = false;
Entity["CurrentActionID"] = action["ActionID"];
if (action["ObjectOnOff"])
{
foreach (var obj in action["ObjectOnOff"])
{
if (Entity[obj.ID] && Entity[obj.ID].IsValue == false) // TODO: IsValue 안쓰고 바인딩 여부 체크 필요
{
foreach (var bind in Entity[obj.ID])
{
var t = baseRoot.GetTransform(bind);
if (t != baseRoot.transform)
{
Entity["ObjectOffs"][bind] = obj != "On";
t.gameObject.SetActive(obj == "On");
}
}
}
else
{
var t = baseRoot.GetTransform(obj.ID);
if (t != baseRoot.transform)
{
Entity["ObjectOffs"][obj.ID] = obj != "On";
t.gameObject.SetActive(obj == "On");
}
}
}
}
}
}
private void SetShader(ShaderInfo info, SLEntity context, bool on = true)
{
currentShaderOnOffs[info.shader] = info.activeValue == -1 && on; // 액티브 밸류 있으면 힛처럼 바로 꺼지는 사례일듯
currentShaderInfos[info.shader] = info;
var value = 0f;
currentShaderValues.TryGetValue(info.shader, out value);
var newValue = on ? 1.0f : 0.0f;
if (info.valueContext != null && context.HasChild(info.valueContext))
{
newValue = context[info.valueContext];
}
else if (info.activeValue != -1)
{
newValue = info.activeValue;
}
if (newValue != value)
{
ApplyShader(info.shader, newValue);
}
currentShaderValues[info.shader] = newValue;
}
private void UpdateShader()
{
foreach (var ss in shaderChecks)
{
for (var i = 0; i < ss.Count; ++i)
{
var shaderCheck = ss[i];
if (shaderCheck.checkValue == null)
{
if (Entity[shaderCheck.checkKey])
{
SetShader(shaderCheck.shaderInfo, Entity, true);
break;
}
}
else
{
if (Entity[shaderCheck.checkKey] == shaderCheck.checkValue)
{
SetShader(shaderCheck.shaderInfo, Entity, true);
break;
}
}
if (i == ss.Count - 1)
{
SetShader(shaderCheck.shaderInfo, Entity, false);
}
}
}
foreach (var shaderKeyword in shaderKeywords)
{
if (currentShaderValues.ContainsKey(shaderKeyword) == false) continue;
var info = currentShaderInfos[shaderKeyword];
var value = currentShaderValues[shaderKeyword];
if (currentShaderOnOffs[shaderKeyword])
{
if (info.onSpeed > 0)
{
var newValue = value + Time.deltaTime * info.onSpeed;
if (newValue > info.max)
{
newValue = info.max;
}
if (value != newValue)
{
currentShaderValues[shaderKeyword] = newValue;
ApplyShader(shaderKeyword, newValue);
}
}
else if (info.max > 0 && info.max != value)
{
currentShaderValues[shaderKeyword] = info.max;
ApplyShader(shaderKeyword, info.max);
}
}
else
{
if (info.offSpeed > 0)
{
var newValue = value - Time.deltaTime * info.offSpeed;
if (newValue < 0)
{
newValue = 0;
}
if (value != newValue)
{
currentShaderValues[shaderKeyword] = newValue;
ApplyShader(shaderKeyword, newValue);
}
}
else if (0 != value)
{
currentShaderValues[shaderKeyword] = 0;
ApplyShader(shaderKeyword, 0);
}
}
}
}
private void ApplyShader(int key, float value)
{
foreach (var renderer in baseRoot.renderers)
{
foreach (var mat in renderer.materials)
{
mat.SetFloat(key, value);
}
}
}
private void UpdateBuffs()
{
var removeBuffs = new List<string>();
foreach (var buff in buffs)
{
if (Entity["Buffs"].HasChild(buff.Key) == false)
{
if (buffEffects.ContainsKey(buff.Key))
{
buffEffects[buff.Key].EndEffect();
}
removeBuffs.Add(buff.Key);
}
else
{
var buffEntity = Entity["Buffs"][buff.Key];
var countType = SLSystem.Data["Buffs"][buffEntity["BuffKey"]]["CountType"];
if (countType)
{
buffEntity["ViewCount"] = buffEntity.Get(countType);
}
}
}
foreach (var removeBuff in removeBuffs)
{
EndBuff(removeBuff);
buffs.Remove(removeBuff);
if (buffEffects.ContainsKey(removeBuff))
{
buffEffects.Remove(removeBuff);
}
}
}
public void Update()
{
if (Entity.HasChild("KeywordBase") && Entity["KeywordBase"] && baseRoot)
{
baseRoot.SetKeyword(Entity["CurrentAction"]["Tag"], true);
}
if (Entity.HasChild("DragPosition"))
{
var position = Entity["DragPosition"].ToVector3();
transform.localPosition = position;
}
else
{
transform.localPosition = Entity["Position"].ToVector3();
if (Entity.HasChild("Offset"))
{
var offset = Entity["Offset"].ToVector3();
offsetRoot.transform.localPosition = offset;
}
else
{
offsetRoot.transform.localPosition = Vector3.zero;
}
}
// TODO: 뷰에서는 엔티티 할당 안하도록 하고 공통 컴포넌트를 만드는게 좋을듯
if (Entity.HasChild("Position"))
{
Entity["HitPosition"] = Entity["Position"];
Entity["HitPosition"]["Y"] += Entity["HitHeight"];
Entity["TopPosition"] = Entity["Position"];
Entity["TopPosition"]["Y"] += Entity["TopHeight"];
Entity["PositionDepth"] = Entity["Position"]["X"] - Entity["Position"]["Z"];
}
if (Entity.HasChild("DragForward"))
{
var forward = Entity["DragForward"].ToVector3();
transform.localRotation = Quaternion.LookRotation(forward, Vector3.up);
}
else if (Entity.HasChild("Forward"))
{
var forward = Entity["Forward"].ToVector3();
transform.localRotation = Quaternion.LookRotation(forward, Vector3.up);
}
else
{
transform.localRotation = Quaternion.identity;
}
UpdateScale();
UpdateAppearance();
UpdateBuffs();
UpdateChat();
}
private void UpdateScale()
{
if (Entity.HasChild("DragPosition"))
{
if (Entity.HasChild("Scale"))
{
offsetRoot.transform.localScale = Entity["Scale"] * SLSystem.Data["Default"]["DragScale"] * Vector3.one;
}
else
{
offsetRoot.transform.localScale = Vector3.one * SLSystem.Data["Default"]["DragScale"];
}
}
else
{
if (Entity.HasChild("Scale"))
{
offsetRoot.transform.localScale = Vector3.one * Entity["Scale"];
}
else
{
offsetRoot.transform.localScale = Vector3.one;
}
}
}
private void Effect(SLEntity context)
{
EffectView.MakeUnitEffect(this, context["Effect"], context);
}
private void Sound(SLEntity context)
{
if (context["Volume"] == false)
{
context["Volume"] = 1;
}
if (context["Tag"] == false)
{
context["Tag"] = "Common";
}
SLSound.PlaySound(context["Sound"], context["Tag"], context["Loop"], context["Unique"], context["Reset"], context["Volume"]);
}
private void ObjectOnOff(SLEntity context)
{
foreach (var obj in context["Binds"])
{
if (Entity[obj.ID] && Entity[obj.ID].IsValue == false) // TODO: IsValue 안쓰고 바인딩 여부 체크 필요
{
foreach (var bind in Entity[obj.ID])
{
var t = baseRoot.GetTransform(bind);
t.gameObject.SetActive(obj == "On");
}
}
else
{
var t = baseRoot.GetTransform(obj.ID);
t.gameObject.SetActive(obj == "On");
}
}
}
private void Hit(SLEntity context)
{
var playEffect = SLEntity.Empty;
if (context["HitResult"])
{
playEffect = SLSystem.Data["Effects"][context["HitResult"]];
}
else if (context["SenderContext"])
{
playEffect = SLSystem.Data["Effects"][context["SenderContext"]];
}
else
{
if (context["Heal"]) // 대미지와 힐과 실드를 같이 주면 이펙트를 다 써야하나..
{
playEffect = SLSystem.Data["Effects"]["Heal"];
}
else if (context["Shield"])
{
playEffect = SLSystem.Data["Effects"]["Shield"];
}
// else // Buff는 AddBuff에서 독립으로 처리
}
if (playEffect)
{
HitEffect(context, playEffect);
}
}
public void HitEffect(SLEntity context, SLEntity playEffect)
{
var position = Entity["Position"].ToVector3();
var forward = (context["Sender"]["Position"].ToVector3() - position).normalized; // TODO: 센더와의 거리
forward.y = 0;
var rotation = Quaternion.identity;
if (forward != Vector3.zero) // 불기둥같이 같은 자리에서 맞는 경우가 있어 context안에 Forward가 있을 때만 처리
{
rotation = Quaternion.LookRotation(forward, Vector3.up);
}
if (Entity["To"] && Entity["ActionStop"] == false) // 이동 예측 ActionStop 있을 때는 안하도록
{
var dir = (Entity["To"].ToVector3() - Entity["Position"].ToVector3()).normalized;
position += 5.0f * Entity["MoveSpeed"] * Time.deltaTime * dir;
}
if (playEffect["HitSound"])
{
SLSound.PlaySound(playEffect["HitSound"], "Effect", false, false, false);
}
if (playEffect["Hit"])
{
var hitEffect = context["HitEffect"];
if (hitEffect["Effect"]) // 스킬전용 히트이펙트
{
playEffect = playEffect.Override();
playEffect["Hit"]["Effect"] = hitEffect["Effect"];
}
if (playEffect["Hit"]["Bind"])
{
EffectView.MakeUnitEffect(this, playEffect["Hit"]["Effect"], playEffect["Hit"]);
}
else
{
EffectView.MakeEffect(Zone, playEffect["Hit"]["Effect"], playEffect["Hit"], position, rotation, Vector3.one);
}
}
// 힛글로우
if (context["HitGlow"])
{
if (Entity["Dead"])
{
context["HitGlow"] = SLSystem.Data["Default"]["DeadHitGlow"];
}
SetShader(hitGlowShaderInfo, context);
}
else if (playEffect["HitGlow"])
{
// 힛글로우
context["HitGlow"] = playEffect["HitGlow"];
SetShader(hitGlowShaderInfo, context);
}
if (context["Shake"])
{
context["Shake"]["Direction"] = forward.normalized.ToEntity();
ZoneController.PlayCameraOperation(context);
}
else if (playEffect["HitCamera"])
{
if (playEffect["HitCamera"]["Shake"])
{
playEffect["HitCamera"]["Shake"]["Direction"] = forward.normalized.ToEntity();
}
ZoneController.PlayCameraOperation(playEffect["HitCamera"]);
}
if (playEffect["HitText"])
{
var eventContext = SLEntity.Empty;
if (context["Damage"])
{
eventContext["Value"] = context["Damage"];
}
else if (context["Heal"]) // 대미지와 힐과 실드를 같이 주면 이펙트를 다 써야하나..
{
eventContext["Value"] = context["Heal"];
}
else if (context["Shield"])
{
eventContext["Value"] = context["Shield"];
}
// else // Buff는 AddBuff에서 독립으로 처리
eventContext["Text"] = playEffect["HitText"];
eventContext["Position"] = Entity["HitPosition"];
eventContext["PositionDepth"] = eventContext["Position"]["X"] - eventContext["Position"]["Z"];
eventContext["Type"] = playEffect["HitTextType"];
MakeDamageText(eventContext);
}
}
private void MakeDamageText(SLEntity context)
{
if (Entity["LastDamageTextTime"])
{
if (Time.unscaledTime > Entity["LastDamageTextTime"] + SLSystem.Data["Default"]["DamageTextStackDuration"])
{
Entity["DamageTextCount"] = 0;
}
}
Entity["LastDamageTextTime"] = Time.unscaledTime;
context["Position"]["Y"] += Entity["DamageTextCount"] * SLSystem.Data["Default"]["DamageTextStackHeight"];
Entity["DamageTextCount"] += 1;
SLGame.Event($"BattleText.{Entity["Handle"]}", context);
}
private void AddBuff(SLEntity context)
{
if (context["Immune"])
{
BuffEffect(context, SLSystem.Data["Effects"]["Immune"]);
return;
}
var playEffect = SLSystem.Data["Effects"][context["BuffKey"]];
if (playEffect == false)
{
SLLog.Error($"[{context["BuffKey"]}]버프 데이터 없음");
return;
}
if (playEffect["Icon"] == false)
{
context["NoIcon"] = true;
}
BuffEffect(context, playEffect);
if (buffs.ContainsKey(context["BuffKey"]) == false)
{
buffs.Add(context["BuffKey"], context["BuffName"]);
}
// shader 처리
}
public void BuffEffect(SLEntity context, SLEntity playEffect)
{
var position = Entity["Position"].ToVector3();
var forward = Entity["Forward"].ToVector3().normalized;
var rotation = Quaternion.LookRotation(forward, Vector3.up);
if (playEffect["StartSound"])
{
SLSound.PlaySound(playEffect["StartSound"], "Effect", false, false, true);
}
if (playEffect["Start"])
{
if (playEffect["Start"]["Bind"])
{
EffectView.MakeUnitEffect(this, playEffect["Start"]["Effect"], playEffect["Start"]);
}
else
{
EffectView.MakeEffect(Zone, playEffect["Start"]["Effect"], playEffect["Start"], position, rotation, Vector3.one);
}
}
if (context["HitGlow"])
{
if (Entity["Dead"])
{
context["HitGlow"] = SLSystem.Data["Default"]["DeadHitGlow"];
}
SetShader(hitGlowShaderInfo, context);
}
else if (playEffect["HitGlow"])
{
// 힛글로우
context["HitGlow"] = playEffect["HitGlow"];
SetShader(hitGlowShaderInfo, context);
}
if (playEffect["Play"])
{
if (buffEffects.ContainsKey(context["BuffKey"]) == false) // 갱신
{
buffEffects.Add(context["BuffKey"], EffectView.MakeUnitEffect(this, playEffect["Play"]["Effect"], playEffect["Play"]));
}
}
if (context["Shake"])
{
context["Shake"]["Direction"] = forward.normalized.ToEntity();
ZoneController.PlayCameraOperation(context);
}
else if (playEffect["Camera"])
{
if (playEffect["Camera"]["Shake"])
{
playEffect["Camera"]["Shake"]["Direction"] = forward.normalized.ToEntity();
}
ZoneController.PlayCameraOperation(playEffect["Camera"]);
}
if (playEffect["Text"])
{
var eventContext = SLEntity.Empty;
eventContext["Text"] = playEffect["Text"];
eventContext["Position"] = Entity["TopPosition"];
eventContext["Type"] = playEffect["TextType"];
var textForward = 0.25f * Random.Range(0.5f, 1.5f) * forward;
eventContext["Position"]["X"] -= textForward.x;
eventContext["Position"]["Z"] -= textForward.z;
eventContext["PositionDepth"] = eventContext["Position"]["X"] - eventContext["Position"]["Z"];
SLGame.Event($"BattleText.{Entity["Handle"]}", eventContext);
}
}
private void EndBuff(string buffName)
{
var playEffect = SLSystem.Data["Effects"][buffName];
var position = Entity["Position"].ToVector3();
var forward = Entity["Forward"].ToVector3().normalized;
var rotation = Quaternion.LookRotation(forward, Vector3.up);
if (playEffect["End"])
{
if (playEffect["End"]["Bind"])
{
EffectView.MakeUnitEffect(this, playEffect["End"]["Effect"], playEffect["End"]);
}
else
{
EffectView.MakeEffect(Zone, playEffect["End"]["Effect"], playEffect["End"], position, rotation, Vector3.one);
}
}
}
private void ControlUnit(SLEntity context)
{
// Shader
if (context["Shaders"])
{
foreach (var shader in context["Shaders"])
{
SetShader(shaderInfos[shader.ID], shader, shader["On"]);
}
}
// Effect
if (context["Effects"])
{
foreach (var effect in context["Effects"])
{
Effect(effect);
}
}
// Chat
if (context["Text"] || context["Emotion"])
{
Entity["Chat"] = false;
if (context["Emotion"])
{
var emotion = SLSystem.Data["Emotions"][context["Emotion"]].Clone();
if (emotion["Animation"])
{
Entity["Chat"]["Animation"] = context["Emotion"];
}
else
{
Entity["Chat"]["Icon"] = emotion["EmotionIcon"];
}
if (emotion["Sound"])
{
var sound = emotion["Sound"];
if (sound["Volume"] == false)
{
sound["Volume"] = 1;
}
if (sound["Tag"] == false)
{
sound["Tag"] = "Common";
}
SLSound.PlaySound(sound["Sound"], sound["Tag"], sound["Loop"], sound["Unique"], sound["Reset"], sound["Volume"]);
}
Entity["Chat"]["Type"] = emotion["Type"];
Entity["Chat"]["Frame"] = emotion["Frame"];
Entity["Chat"]["Scale"] = emotion["Scale"];
}
else
{
Entity["Chat"]["Type"] = "Round";
Entity["Chat"]["Frame"] = 180;
Entity["Chat"]["Scale"] = 1;
}
if (context["Text"]) Entity["Chat"]["Text"] = context["Text"];
if (context["Type"]) Entity["Chat"]["Type"] = context["Type"];
if (context["Frame"]) Entity["Chat"]["Frame"] = context["Frame"];
if (context["Scale"]) Entity["Chat"]["Scale"] = context["Scale"];
}
}
private void UpdateChat()
{
if (Entity["Chat"])
{
if (Entity["Chat"]["Loop"]) return;
if (Entity["Chat"]["Frame"])
{
if (Entity["Chat"]["Frame"] > 0)
{
Entity["Chat"]["Frame"] -= Time.deltaTime * 60;
}
if (Entity["Chat"]["Frame"] <= 0)
{
Entity["Chat"] = false;
}
}
}
}
// Chapter2 Special
private void MakeShip(SLEntity context)
{
gameObject.AddComponent<SpawnEffect>();
}
// Udpators
private List<IUnitViewUpdator> updators;
public void AddUpdate(IUnitViewUpdator updator)
{
updators.Add(updator);
}
public void RemoveUpdate(IUnitViewUpdator updator)
{
updators.Remove(updator);
}
private void UpdateUpdators()
{
foreach (var u in updators)
{
u.OnUpdate();
}
}
}