TL; DR
คำตอบนี้บ้าไปหน่อย แต่เป็นเพราะฉันเห็นว่าคุณกำลังพูดถึงการใช้ความสามารถของคุณในฐานะ "คำสั่ง" ซึ่งหมายถึงรูปแบบการออกแบบ C ++ / Java / .NET ซึ่งแสดงถึงวิธีการที่ใช้รหัสหนัก Approah นั้นถูกต้อง แต่ก็มีวิธีที่ดีกว่า บางทีคุณอาจกำลังทำอย่างอื่นอยู่แล้ว ถ้าเป็นเช่นนั้น หวังว่าคนอื่นจะพบว่ามีประโยชน์ถ้าเป็นเช่นนั้น
ดูที่ Data-Driven Approach ด้านล่างเพื่อตัดการไล่ล่า รับ CustomAssetUility ที่กำหนดเองของ Jacob Pennock ที่นี่และอ่านโพสต์ของเขาเกี่ยวกับเรื่องนี้
การทำงานกับ Unity
เช่นเดียวกับคนอื่น ๆ ที่กล่าวถึงการสำรวจรายการ 100-300 รายการนั้นไม่ใช่เรื่องใหญ่อย่างที่คุณคิด ดังนั้นถ้านั่นเป็นวิธีการที่ใช้งานง่ายสำหรับคุณก็แค่ทำเช่นนั้น ปรับให้เหมาะสมเพื่อประสิทธิภาพของสมอง แต่พจนานุกรมดังที่ @Norguard แสดงให้เห็นในคำตอบของเขาเป็นวิธีที่ไม่ต้องใช้สมองในการกำจัดปัญหานั้นได้ง่ายเนื่องจากคุณได้รับการแทรกและเรียกค้นเวลาอย่างต่อเนื่อง คุณควรใช้มัน
ในแง่ของการทำให้งานนี้เป็นไปได้ดีใน Unity ไส้ในของฉันบอกฉันว่า MonoBehaviour ต่อความสามารถเป็นเส้นทางที่อันตรายที่จะลงไป หากความสามารถใด ๆ ของคุณรักษาสถานะเมื่อเวลาผ่านไปที่พวกเขาดำเนินการคุณจะต้องจัดการที่ให้วิธีการตั้งค่าสถานะนั้น Coroutines บรรเทาปัญหานี้ แต่คุณยังคงจัดการการอ้างอิง IEnumerator ในทุกเฟรมการอัพเดทของสคริปต์นั้นและต้องทำให้แน่ใจว่าคุณมีวิธีที่แน่นอนในการรีเซ็ตความสามารถเพื่อมิให้เกิดความไม่สมบูรณ์ ความสามารถในการเริ่มต้นอย่างเงียบ ๆ ทำให้เสียความมั่นคงของเกมของคุณเมื่อพวกเขาไปไม่มีใครสังเกตเห็น "แน่นอนฉันจะทำอย่างนั้น!" คุณพูดว่า "ฉันเป็น 'โปรแกรมเมอร์ที่ดี'!" แต่คุณรู้ไหมว่าพวกเราทุกคนเป็นนักเขียนโปรแกรมที่แย่มากและแม้แต่นักวิจัย AI และนักเขียนคอมไพเลอร์ที่ยิ่งใหญ่ที่สุดก็ทำอะไรผิดพลาดอยู่ตลอดเวลา
ทุกวิธีที่คุณสามารถใช้ instantiation คำสั่งและการดึงในความสามัคคีผมจะคิดว่าสองหนึ่งคือดีและจะไม่ให้หลอดเลือดโป่งพองและอื่น ๆ ที่จะช่วยให้การมากมาย MAGICAL ความคิดสร้างสรรค์ เรียงจาก
แนวทางการใช้รหัสเป็นศูนย์กลาง
ครั้งแรกเป็นวิธีการส่วนใหญ่ในรหัส สิ่งที่ฉันแนะนำคือคุณทำให้แต่ละคำสั่งเป็นคลาสแบบง่าย ๆ ซึ่งสืบทอดมาจาก BaseCommand abtract class หรือใช้ ICommand อินเตอร์เฟส (ฉันสมมติว่าเพื่อความกะทัดรัดที่คำสั่งเหล่านี้จะเป็นความสามารถของตัวละครเท่านั้นไม่ยากที่จะรวม การใช้งานอื่น ๆ ) ระบบนี้อนุมานว่าคำสั่งแต่ละคำสั่งเป็น ICommand มีตัวสร้างสาธารณะที่ไม่มีพารามิเตอร์และต้องอัปเดตแต่ละเฟรมในขณะที่ใช้งานอยู่
สิ่งต่าง ๆ นั้นง่ายกว่าถ้าคุณใช้คลาสพื้นฐานที่เป็นนามธรรม แต่เวอร์ชันของฉันใช้อินเทอร์เฟซ
สิ่งสำคัญคือ MonoBehaviours ของคุณจะสรุปพฤติกรรมหนึ่งอย่างหรือระบบของพฤติกรรมที่เกี่ยวข้องอย่างใกล้ชิด มันก็โอเคที่จะมี MonoBehaviours มากมายที่มีประสิทธิภาพเพียงแค่พร็อกซีออกไปสู่คลาส C # ธรรมดา แต่ถ้าคุณคิดว่าตัวเองกำลังทำอยู่ก็อาจอัพเดทการโทรออกไปยังวัตถุต่าง ๆ ทุกประเภทจนถึงจุดที่มันเริ่มเป็นเกม 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>
ไม่ใช่โครงสร้างข้อมูลที่ดีที่สุดสำหรับการจัดเก็บความสามารถตามเวลาที่กำหนดคุณอาจต้องการLinkedList<T>
หรือ a SortedDictionary<float, T>
)
วิธีการที่ขับเคลื่อนด้วยข้อมูล
อาจเป็นไปได้ว่าคุณสามารถลดผลกระทบของความสามารถของคุณลงไปสู่พฤติกรรมเชิงตรรกะที่สามารถกำหนดค่าได้ นี่คือสิ่งที่ Unity สร้างขึ้นเพื่อ คุณในฐานะโปรแกรมเมอร์ออกแบบระบบที่คุณหรือนักออกแบบสามารถไปและปรับเปลี่ยนในตัวแก้ไขเพื่อสร้างเอฟเฟกต์ที่หลากหลาย สิ่งนี้จะช่วยลดความซับซ้อนของ "rigging" ของโค้ดและเน้นไปที่การใช้งานความสามารถเฉพาะ ไม่จำเป็นต้องเล่นคลาสพื้นฐานหรืออินเทอร์เฟซและข้อมูลทั่วไปที่นี่ มันจะถูกขับเคลื่อนด้วยข้อมูลล้วนๆ
สิ่งแรกที่คุณต้องการคือ ScriptableObject ที่สามารถอธิบายความสามารถของคุณ ScriptableObjects ยอดเยี่ยม พวกมันถูกออกแบบมาให้ทำงานเหมือนกับ MonoBehaviours โดยที่คุณสามารถตั้งค่าเขตข้อมูลสาธารณะของพวกเขาในผู้ตรวจสอบของ Unity และการเปลี่ยนแปลงเหล่านั้นจะได้รับการเรียงลำดับเป็นดิสก์ อย่างไรก็ตามพวกเขาไม่ได้ยึดติดกับวัตถุใด ๆ และไม่จำเป็นต้องแนบกับวัตถุเกมในฉากหรืออินสแตนซ์ พวกมันคือที่เก็บข้อมูลทั้งหมดของ Unity พวกเขาสามารถเป็นอันดับประเภทพื้นฐาน, enums และชั้นเรียนง่าย (มรดก) [Serializable]
ทำเครื่องหมาย โครงสร้างไม่สามารถต่อเนื่องใน Unity และการทำให้เป็นอันดับเป็นสิ่งที่ช่วยให้คุณแก้ไขฟิลด์วัตถุในตัวตรวจสอบดังนั้นโปรดจำไว้ว่า
นี่คือ ScriptableObject ที่พยายามทำสิ่งต่างๆมากมาย คุณสามารถแบ่งออกเป็นคลาสที่ต่อเนื่องกันมากขึ้นและ ScriptableObjects แต่สิ่งนี้ควรจะให้แนวคิดเกี่ยวกับการทำมัน ปกติแล้วมันจะดูน่าเกลียดในภาษาเชิงวัตถุที่ทันสมัยอย่าง C # เพราะมันให้ความรู้สึกเหมือน C89 บางอันที่มี enums ทั้งหมด แต่พลังที่แท้จริงของที่นี่คือตอนนี้คุณสามารถสร้างความสามารถที่หลากหลายได้โดยไม่ต้องเขียนโค้ดใหม่ พวกเขา และหากรูปแบบแรกของคุณไม่ได้ทำตามที่คุณต้องการให้เพิ่มไปเรื่อย ๆ จนกว่ามันจะเป็นเช่นนั้น ตราบใดที่คุณไม่เปลี่ยนชื่อฟิลด์ไฟล์สินทรัพย์ที่ต่อเนื่องของคุณทั้งหมดจะยังคงใช้งานได้
// 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();
}
คุณสามารถแยกส่วนของความเสียหายออกเป็นคลาส 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 จะนำคุณไปสู่ที่ที่คุณต้องการ ความสามัคคีเป็นสิ่งที่ยอดเยี่ยมในการให้สภาพแวดล้อมที่สอดคล้องและสมเหตุสมผลสำหรับโปรแกรมเมอร์ แต่ยังมีแหล่งป้อนข้อมูลทั้งหมดสำหรับนักออกแบบและศิลปินที่คุณได้รับจาก GameMaker, UDK และอื่น ๆ อัล
เมื่อเดือนที่แล้วศิลปินของเราใช้ powerup ScriptableObject ซึ่งควรจะกำหนดพฤติกรรมสำหรับขีปนาวุธประเภทต่าง ๆ รวมกับ AnimationCurve และพฤติกรรมที่ทำให้ขีปนาวุธลอยไปตามพื้น อาวุธแห่งความตาย
ฉันยังต้องย้อนกลับไปและเพิ่มการสนับสนุนเฉพาะสำหรับพฤติกรรมนี้เพื่อให้แน่ใจว่าทำงานได้อย่างมีประสิทธิภาพ แต่เนื่องจากเราได้สร้างอินเทอร์เฟซคำอธิบายข้อมูลทั่วไปเขาจึงสามารถดึงความคิดนี้ออกมาจากอากาศและวางลงในเกมโดยที่เราไม่ได้เขียนโปรแกรมแม้กระทั่งรู้ว่าเขาพยายามทำมันจนกว่าเขาจะมาและพูดว่า "เฮ้พวก ในสิ่งที่ยอดเยี่ยมนี้! " และเพราะมันยอดเยี่ยมมากฉันจึงตื่นเต้นที่จะได้รับการสนับสนุนที่แข็งแกร่งยิ่งขึ้น