TL; DR
이 답변은 약간 미쳐갑니다. 그러나 C ++ / Java / .NET 디자인 패턴을 의미하는 코드로 접근하는 방법을 의미하는 "명령"으로 기능을 구현하는 것에 대해 이야기하고 있기 때문입니다. 그 접근법은 유효하지만 더 좋은 방법이 있습니다. 어쩌면 당신은 이미 다른 방법을하고있을 것입니다. 그렇다면, 오 잘. 희망이 있다면 다른 사람들이 유용하다고 생각합니다.
추격을 줄이기 위해 아래의 데이터 중심 접근 방식을보십시오. Jacob Pennock의 CustomAssetUility를 여기에서 받아 보시고 그의 게시물을 읽으 십시오 .
Unity 작업
다른 사람들이 언급했듯이 100-300 개의 항목을 순회하는 것은 생각만큼 큰 것이 아닙니다. 이것이 직관적 인 접근 방법이라면 그렇게하십시오. 두뇌 효율성을 최적화하십시오. 그러나 @Norguard가 그의 답변 에서 입증 한 것처럼 Dictionary 는 일정한 시간 동안 삽입하고 검색하기 때문에 문제를 제거하기위한 쉬운 방법입니다. 아마 그것을 사용해야합니다.
Unity 내 에서이 작업을 잘 수행한다는 측면에서 내 직감은 능력 당 하나의 MonoBehaviour가 내려가는 위험한 길이라고 말합니다. 어떤 능력이 시간이 지남에 따라 상태를 유지하는 경우 해당 상태를 재설정 할 수있는 방법을 제공해야합니다. 코 루틴은이 문제를 완화하지만, 여전히 해당 스크립트의 모든 업데이트 프레임에서 IEnumerator 참조를 관리하고 있으며, 불완전하고 상태 루프에 빠지지 않도록 기능을 재설정 할 수있는 확실한 방법이 있는지 확인해야합니다. 게임이 눈에 띄지 않을 때 조용히 게임의 안정성을 망치기 시작합니다. "물론 내가 할게!" "나는 '좋은 프로그래머'입니다!"라고 말합니다. 그러나 실제로, 우리는 모두 객관적으로 끔찍한 프로그래머이며 위대한 AI 연구원과 컴파일러 작성자조차도 항상 문제를 일으 킵니다.
Unity에서 명령 인스턴스화 및 검색을 구현할 수있는 모든 방법 중 하나는 괜찮고 동맥류를주지 않으며 다른 하나는 무제한 마법 창조를 허용합니다 . 일종의.
코드 중심 접근법
첫 번째는 대부분 코드 내 방식입니다. 내가 추천하는 것은 각 명령을 BaseCommand abtract 클래스에서 상속하거나 ICommand 인터페이스를 구현하는 간단한 클래스로 만드는 것입니다. 다른 용도). 이 시스템은 각 명령이 ICommand라고 가정하고 매개 변수를 사용하지 않는 공용 생성자를 가지며 각 프레임이 활성화 된 동안 업데이트해야합니다.
추상 기본 클래스를 사용하면 상황이 더 간단하지만 내 버전은 인터페이스를 사용합니다.
MonoBehaviours는 하나의 특정 행동 또는 밀접한 관련 행동 시스템을 캡슐화하는 것이 중요합니다. 평범한 C # 클래스로 효과적으로 프록시하는 많은 MonoBehaviours를 갖는 것은 괜찮지 만, 너무 자신을 발견하면 XNA 게임처럼 보이기 시작하는 시점까지 모든 종류의 다른 객체에 대한 호출을 업데이트 할 수 있습니다. 심각한 문제가 발생하여 아키텍처를 변경해야합니다.
// ICommand.cs
public interface ICommand
{
public void Execute(AbilityActivator originator, TargetingInfo targets);
public void Update();
public bool IsActive { get; }
}
// CommandList.cs
// Attach this to a game object in your loading screen
public static class CommandList
{
public static ICommand GetInstance(string key)
{
return commandDict[key].GetRef();
}
static CommandListInitializerScript()
{
commandDict = new Dictionary<string, ICommand>() {
{ "SwordSpin", new CommandRef<SwordSpin>() },
{ "BellyRub", new CommandRef<BellyRub>() },
{ "StickyShield", new CommandRef<StickyShield>() },
// Add more commands here
};
}
private class CommandRef<T> where T : ICommand, new()
{
public ICommand GetNew()
{
return new T();
}
}
private static Dictionary<string, ICommand> commandDict;
}
// AbilityActivator.cs
// Attach this to your character objects
public class AbilityActivator : MonoBehaviour
{
List<ICommand> activeAbilities = new List<ICommand>();
void Update()
{
string activatedAbility = GetActivatedAbilityThisFrame();
if (!string.IsNullOrEmpty(acitvatedAbility))
ICommand command = CommandList.Get(activatedAbility).GetRef();
command.Execute(this, this.GetTargets());
activeAbilities.Add(command);
}
foreach (var ability in activeAbilities) {
ability.Update();
}
activeAbilities.RemoveAll(a => !a.IsActive);
}
}
이것은 완전히 잘 작동하지만 더 잘 할 수 있습니다 (또한 List<T>
시간 능력을 저장하기위한 최적의 데이터 구조가 아니므로 a LinkedList<T>
또는 a를 원할 수도 있습니다 SortedDictionary<float, T>
).
데이터 중심 접근
능력의 영향을 매개 변수화 할 수있는 논리적 행동으로 줄일 수 있습니다. 이것이 Unity가 실제로 구축 한 것입니다. 프로그래머는 자신이나 디자이너가 편집기에서 다양한 효과를 낼 수있는 시스템을 설계합니다. 이를 통해 코드의 "리깅"을 크게 단순화하고 기능 실행에만 집중할 수 있습니다. 기본 클래스 나 인터페이스 및 제네릭을 여기 저글링 할 필요가 없습니다. 그것은 모두 순전히 데이터 중심이 될 것입니다 (이는 명령 인스턴스 초기화를 단순화합니다).
가장 먼저 필요한 것은 능력을 설명 할 수있는 ScriptableObject입니다. ScriptableObjects는 훌륭합니다. Unity의 인스펙터에서 공개 필드를 설정할 수 있다는 점에서 MonoBehaviours와 같이 작동하도록 설계되었으며 이러한 변경 사항은 디스크에 직렬화됩니다. 그러나 어떤 오브젝트에도 부착되지 않으며 장면에서 게임 오브젝트에 부착하거나 인스턴스화 할 필요가 없습니다. 그것들은 Unity의 모든 데이터 버킷입니다. 표시된 기본 유형, 열거 형 및 단순 클래스 (상속 없음)를 직렬화 할 수 있습니다 [Serializable]
. Structs는 Unity에서 직렬화 할 수 없으며 직렬화를 사용하면 검사기에서 객체 필드를 편집 할 수 있으므로 기억하십시오.
많은 것을 시도하는 ScriptableObject가 있습니다. 이것을 직렬화 된 클래스와 ScriptableObjects로 나눌 수는 있지만 수행하는 방법에 대한 아이디어를 제공해야합니다. 일반적으로 이것은 C #과 같은 멋진 현대 객체 지향 언어에서 추악하게 보입니다. 모든 열거 형에서 C89 똥과 같은 느낌이 들기 때문에 실제로 강력한 힘은 지원하기 위해 새로운 코드를 작성하지 않고도 모든 종류의 다른 능력을 만들 수 있다는 것입니다 그들. 그리고 첫 번째 형식이 필요한 작업을 수행하지 않으면 필요할 때까지 계속 추가하십시오. 필드 이름을 변경하지 않는 한 모든 이전의 직렬화 된 자산 파일은 계속 작동합니다.
// CommandAbilityDescription.cs
public class CommandAbilityDecription : ScriptableObject
{
// Identification and information
public string displayName; // Name used for display purposes for the GUI
// We don't need an identifier field, because this will actually be stored
// as a file on disk and thus implicitly have its own identifier string.
// Description of damage to targets
// I put this enum inside the class for answer readability, but it really belongs outside, inside a namespace rather than nested inside a class
public enum DamageType
{
None,
SingleTarget,
SingleTargetOverTime,
Area,
AreaOverTime,
}
public DamageType damageType;
public float damage; // Can represent either insta-hit damage, or damage rate over time (depend)
public float duration; // Used for over-time type damages, or as a delay for insta-hit damage
// Visual FX
public enum EffectPlacement
{
CenteredOnTargets,
CenteredOnFirstTarget,
CenteredOnCharacter,
}
[Serializable]
public class AbilityVisualEffect
{
public EffectPlacement placement;
public VisualEffectBehavior visualEffect;
}
public AbilityVisualEffect[] visualEffects;
}
// VisualEffectBehavior.cs
public abtract class VisualEffectBehavior : MonoBehaviour
{
// When an artist makes a visual effect, they generally make a GameObject Prefab.
// You can extend this base class to support different kinds of visual effects
// such as particle systems, post-processing screen effects, etc.
public virtual void PlayEffect();
}
Damage 섹션을 Serializable 클래스로 추가로 추상화하여 피해를 입히거나 치유하는 능력을 정의하고 한 가지 능력으로 여러 가지 피해 유형을 가질 수 있습니다. 스크립트 가능한 여러 개체를 사용하고 디스크에서 서로 다른 복잡한 손상 구성 파일을 참조하지 않는 한 유일한 규칙은 상속이 아닙니다.
여전히 AbilityActivator MonoBehaviour가 필요하지만 이제는 더 많은 작업을 수행합니다.
// AbilityActivator.cs
public class AbilityActivator : MonoBehaviour
{
public void ActivateAbility(string abilityName)
{
var command = (CommandAbilityDescription) Resources.Load(string.Format("Abilities/{0}", abilityName));
ProcessCommand(command);
}
private void ProcessCommand(CommandAbilityDescription command)
{
foreach (var fx in command.visualEffects) {
fx.PlayEffect();
}
switch(command.damageType) {
// yatta yatta yatta
}
// and so forth, whatever your needs require
// You could even make a copy of the CommandAbilityDescription
var myCopy = Object.Instantiate(command);
// So you can keep track of state changes (ie: damage duration)
}
}
가장 멋진 부분
따라서 첫 번째 접근 방식의 인터페이스와 일반적인 속임수가 잘 작동합니다. 그러나 Unity를 최대한 활용하기 위해 ScriptableObjects를 사용하면 원하는 위치로 이동할 수 있습니다. Unity는 프로그래머에게 일관되고 논리적 인 환경을 제공한다는 점에서 훌륭하지만 GameMaker, UDK 등에서 얻은 디자이너와 아티스트를위한 모든 데이터 입력 기능도 갖추고 있습니다. 알.
지난 달, 우리 아티스트는 여러 종류의 원점 미사일에 대한 동작을 정의하는 파워 업 ScriptableObject 유형을 가져 와서 AnimationCurve와 결합하여 미사일을지면을 맴돌 게하여이 미친 새로운 회전 하키 퍽을 만들었습니다. 죽음의 무기.
여전히 돌아가서이 동작에 대한 특정 지원을 추가하여 효율적으로 실행되도록해야합니다. 그러나 우리는이 일반적인 데이터 기술 인터페이스를 만들었 기 때문에 그는이 아이디어를 허공에서 끌어내어 프로그래머가 자신이 와서 말할 때까지 시도하지 않았다는 것을 알지 못하고 게임에 넣을 수있었습니다. 이 멋진 일에! " 그리고 그것은 분명히 대단했기 때문에 그것에 대한 더 강력한 지원을 추가하게되어 기쁩니다.