졸업까지 100턴은 전투 스킬마다 클래스를 만들지 않습니다. 표에 적힌 문자열로 발동 조건 8종과 효과 23종을 골라 조합합니다. 새 스킬을 만들 때 코드를 거의 고치지 않는 구조와, 웹 빌드에서 만난 함정을 함께 보겠습니다.
스킬마다 클래스를 만들면 조합이 폭발합니다

"적이 가까이 오면 광역 피해", "아군이 다치면 회복", "길게 누르면 화상" 같은 스킬은 결국 두 질문의 조합입니다. 언제 쓰는가, 그리고 무엇을 하는가.
스킬마다 클래스
- 조건 8개와 효과 23개를 섞으면 수백 가지가 됩니다.
- 비슷한 코드가 클래스마다 반복됩니다.
- 기획자가 조합을 바꾸려면 프로그래머를 기다려야 합니다.
조건과 효과를 따로
- 조건 8개와 효과 23개만 만들면 됩니다.
- 조합은 표의 문자열로 정합니다.
- 새 조합에는 코드가 필요 없습니다.
구조
조건의 계약은 짧습니다. 발동 여부와 대상을 함께 돌려주므로 효과 쪽은 대상 선정을 신경 쓰지 않아도 됩니다.
public abstract class TDSkillCondition
{
protected int conditionValue1;
protected int conditionValue2;
protected TDField field = null;
protected TDCharacter owner = null;
public void Initialize(TDField field, TDCharacter owner, int conditionValue1, int conditionValue2)
{
this.field = field;
this.owner = owner;
this.conditionValue1 = conditionValue1;
this.conditionValue2 = conditionValue2;
}
public abstract TDCharacter[] CheckCondition(ElStateTypes currentType, out bool check);
}효과의 계약도 짧습니다. 대상을 받아 대상을 반환합니다. 이 모양이 뒤에서 효과를 이어 붙이는 열쇠가 됩니다.
public abstract TDField.TeamTypes GetTargetTeamType();
public abstract TDCharacter[] RunSkill(TDCharacter[] targetCharacters);문자열에서 클래스로
표에 EnemyNear 라고 적혀 있으면 TDSkillCondition_EnemyNear 클래스를 찾아 인스턴스를 만듭니다. 이름 규칙이 곧 등록 절차입니다. switch 문은 어디에도 없습니다.
string conditionClassName = "TDSkillCondition_" + skillInfo.ConditionType;
Type type = Type.GetType(conditionClassName);
if (type != null)
{
object instance = Activator.CreateInstance(type);
if (instance is TDSkillCondition myClassInstance)
{
condition = (TDSkillCondition)instance;
}
}
if (condition == null)
condition = new TDSkillCondition_Whenever();
condition.Initialize(field, owner, skillInfo.ConditionValue1, skillInfo.ConditionValue2);
CreateRunner(skillInfo.SkillType, 1, ref runner);
CreateRunner(skillInfo.SecondarySkillType, 2, ref runner2);새 효과를 추가할 때 할 일은 두 가지입니다. TDSkillRunner_ 로 시작하는 클래스를 만들고, 표의 칸에 그 이름을 적습니다.
효과를 이어 붙입니다
효과가 대상 배열을 반환하기 때문에, 첫 효과가 맞힌 대상에게 두 번째 효과를 그대로 넘길 수 있습니다. "광역 피해를 주고, 맞은 적에게 화상을 남긴다" 같은 스킬이 표 두 칸으로 만들어집니다.
if (runner.CanChangeTarget)
{
skillDelaying = true;
DoAction(delegate
{
targetCharacters = runner.RunSkill(targetCharacters);
if (!string.IsNullOrEmpty(skillInfo.TargetEffectPath) && skillInfo.TargetEffectPath != "-" && targetCharacters != null && targetCharacters.Length > 0)
{
foreach (var target in targetCharacters)
{
var eff = CreateEffect(skillInfo.TargetEffectPath, target.transform);
OnCreateTargetEffect(eff, target, runner);
}
}
if (runner2 != null)
{
targetCharacters = runner2.RunSkill(targetCharacters);
}
skillDelaying = false;
});- 조건적이 가까이 왔다
- 효과 1광역 피해, 맞은 대상 반환
- 효과 2같은 대상에게 화상
비슷한 효과는 상속으로 줄입니다
화상, 공포, 빙결, 중독, 기절은 모두 상태 이상을 거는 효과입니다. 공통 동작은 부모 클래스가 맡고, 자식은 어떤 상태 이상인지만 말합니다.
public class TDSkillRunner_Burn : TDSkillRunner_AbnormalStatus
{
public TDSkillRunner_Burn() { }
public override TDCharacter.AbnormalStatusTypes GetAbnormalStatus()
{
return TDCharacter.AbnormalStatusTypes.Burn;
}화상
Burn
공포
Fear
빙결
Freezing
중독
Poisoning
기절
Stun
웹 빌드에서 클래스가 사라지는 함정

Unity 의 웹 빌드는 IL2CPP 로 컴파일하면서 쓰이지 않는 코드를 걷어 냅니다. 그런데 이 스킬 클래스들은 코드 어디에서도 이름으로 불리지 않습니다. 문자열로만 찾기 때문이죠.
에디터에서는 잘 동작하던 스킬이 웹 빌드에서만 기본 효과로 바뀝니다. 오류도 나지 않습니다.
그래서 link.xml 에 조건 8개와 효과 쪽 24개(상태 이상 부모 클래스 포함), 모두 32개 타입을 지우지 말라고 적어 둡니다.
<assembly fullname="Assembly-CSharp">
<type fullname="TDSkillCondition_EnemyBack" preserve="all"/>
<type fullname="TDSkillCondition_EnemyFar" preserve="all"/>
<type fullname="TDSkillCondition_EnemyNear" preserve="all"/>
<type fullname="TDSkillCondition_EnemyStraight" preserve="all"/>
<type fullname="TDSkillCondition_FriendlyNear" preserve="all"/>
<type fullname="TDSkillCondition_LongPressEnemy" preserve="all"/>
<type fullname="TDSkillCondition_LongPressFriendly" preserve="all"/>
<type fullname="TDSkillCondition_Whenever" preserve="all"/>
<type fullname="TDSkillRunner_AbnormalStatus" preserve="all"/>
<type fullname="TDSkillRunner_AccuracyPointBuff" preserve="all"/>
<type fullname="TDSkillRunner_AreaDamage" preserve="all"/>돌아보며
잘된 점
- 새 효과는 클래스를 만들고 표에 이름을 적어 추가합니다.
- 조건과 효과를 따로 두어 조합을 데이터로 정합니다.
- 효과가 대상을 반환해 두 효과를 이어 붙일 수 있습니다.
아쉬운 점
- 이름을 잘못 적으면 조용히 기본값으로 바뀝니다. 조건은 항상 발동, 효과는 광역 피해가 되어 오타가 "항상 쓰는 광역기" 로 보입니다. 개발 중에는 오류로 알려야 했습니다.
- link.xml 에 손으로 등록해야 해서, 빠뜨리면 웹 빌드에서만 문제가 납니다.
- 피해량 계산이 효과 클래스마다 반복되어 있습니다.
정리
- 조건과 효과를 나눕니다. 스킬은 "언제" 와 "무엇을" 의 조합입니다.
- 이름 규칙으로 등록합니다. 문자열 접두사와 리플렉션으로 switch 문 없이 클래스를 찾습니다.
- 리플렉션은 빌드 설정과 짝입니다. IL2CPP 가 지우지 않도록 link.xml 에 남깁니다.
다음 편에서는 보드판의 주사위를 서버가 굴리고, 클라이언트가 그 결과를 연출하는 구조를 보겠습니다.