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 unused; private static Transform unusedRoot; private static List 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 shaderInfos; private class ShaderCheck { public ShaderInfo shaderInfo; public string checkKey; public string checkValue; } private static List> shaderChecks; public static void InitGlobal() { unused = new Queue(); unusedRoot = new GameObject("UnusedUnitView").transform; unusedRoot.gameObject.SetActive(false); unitViewComponents = new List(); unitViewComponents.CreateTypeList(); useSceneLightId = Shader.PropertyToID("UseSceneLight"); outlineId = Shader.PropertyToID("Outline"); enemyId = Shader.PropertyToID("Enemy"); var propertyIDs = new Dictionary(); foreach (var shader in SLSystem.Data["Shaders"]) { propertyIDs[shader["Keyword"]] = Shader.PropertyToID(shader["Keyword"]); } shaderKeywords = propertyIDs.Values.ToArray(); shaderInfos = new Dictionary(); 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>(); foreach (var ss in SLSystem.Data["Shaders"].Where(e => e["CheckKey"]).GroupBy(e => e["Keyword"])) { var group = new List(); 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(); newUnit.Init(); } newUnit.transform.SetParent(zoneView.Root, false); newUnit.gameObject.SetActive(true); newUnit.BindUnit(zoneView, unit); // TODO: 확장 컴포넌트 사용 가능한 구조로 개선 if (newUnit.Entity["UseSpawnEffect"]) { newUnit.gameObject.AddComponent(); } 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 currentShaderValues; private Dictionary currentShaderInfos; private Dictionary currentShaderOnOffs; private Dictionary buffEffects; private Dictionary actionEffects; private Dictionary customEffects; private Dictionary attachParts; private Dictionary buffs; private List components; public void Init() { offsetRoot = new GameObject("OffsetRoot"); offsetRoot.transform.SetParent(transform, false); currentShaderValues = new Dictionary(); currentShaderInfos = new Dictionary(); currentShaderOnOffs = new Dictionary(); buffEffects = new Dictionary(); actionEffects = new Dictionary(); customEffects = new Dictionary(); attachParts = new Dictionary(); buffs = new Dictionary(); updators = new List(); components = new List(); 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(); 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(); } // Udpators private List 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(); } } }