TL; DR
Câu trả lời này hơi điên rồ. Nhưng đó là vì tôi thấy bạn đang nói về việc thực hiện các khả năng của mình dưới dạng "Lệnh", ngụ ý các mẫu thiết kế C ++ / Java / .NET, ngụ ý một cách tiếp cận nặng về mã. Sự chấp thuận đó là hợp lệ, nhưng có một cách tốt hơn. Có lẽ bạn đã làm theo cách khác. Nếu vậy, oh tốt. Hy vọng những người khác thấy nó hữu ích nếu đó là trường hợp.
Nhìn vào Cách tiếp cận dựa trên dữ liệu dưới đây để cắt theo đuổi. Nhận CustomAssetUility của Jacob Pennock tại đây và đọc bài đăng của anh ấy về nó .
Làm việc với Unity
Giống như những người khác đã đề cập, đi qua danh sách 100-300 mặt hàng không phải là vấn đề lớn như bạn nghĩ. Vì vậy, nếu đó là một cách tiếp cận trực quan cho bạn, thì hãy làm điều đó. Tối ưu hóa cho hiệu quả não. Nhưng từ điển, như @Norguard đã thể hiện trong câu trả lời của mình , là cách dễ dàng không cần đến trí tuệ để loại bỏ vấn đề đó kể từ khi bạn nhận được chèn và truy xuất liên tục. Bạn có lẽ nên sử dụng nó.
Về mặt làm cho nó hoạt động tốt trong Unity, ruột của tôi nói với tôi rằng một MonoBehaviour cho mỗi khả năng là một con đường nguy hiểm để đi xuống. Nếu bất kỳ khả năng nào của bạn duy trì trạng thái theo thời gian chúng thực thi, bạn sẽ cần quản lý rằng cung cấp một cách để đặt lại trạng thái đó. Coroutines làm giảm bớt vấn đề này, nhưng bạn vẫn đang quản lý một tài liệu tham khảo IEnumerator trên mọi khung cập nhật của tập lệnh đó và phải đảm bảo chắc chắn rằng bạn có một cách chắc chắn để thiết lập lại các khả năng trong trường hợp không hoàn thành và bị mắc kẹt các khả năng lặng lẽ bắt đầu làm hỏng sự ổn định của trò chơi của bạn khi chúng không được chú ý. "Tất nhiên tôi sẽ làm điều đó!" bạn nói, "Tôi là một 'Lập trình viên giỏi'!". Nhưng thực sự, bạn biết đấy, tất cả chúng ta đều là những lập trình viên khách quan khủng khiếp và thậm chí cả những nhà nghiên cứu và nhà biên dịch AI vĩ đại nhất luôn vặn vẹo mọi lúc.
Trong tất cả các cách bạn có thể thực hiện khởi tạo và truy xuất lệnh trong Unity, tôi có thể nghĩ đến hai cách: một là ổn và sẽ không cung cấp cho bạn chứng phình động mạch, và cách khác cho phép TẠO ĐỘNG LỰC TUYỆT VỜI . Sắp xếp
Phương pháp tiếp cận trung tâm mã
Đầu tiên là một cách tiếp cận chủ yếu trong mã. Điều tôi khuyên là bạn nên tạo cho mỗi lệnh một lớp đơn giản, kế thừa từ lớp rút gọn BaseCommand hoặc thực hiện giao diện ICommand (Tôi giả sử vì sự ngắn gọn mà các Lệnh này sẽ chỉ là khả năng của nhân vật, không khó để kết hợp sử dụng khác). Hệ thống này giả định rằng mỗi lệnh là một ICommand, có một hàm tạo công khai không có tham số và yêu cầu cập nhật từng khung trong khi nó đang hoạt động.
Mọi thứ sẽ đơn giản hơn nếu bạn sử dụng một lớp cơ sở trừu tượng, nhưng phiên bản của tôi sử dụng các giao diện.
Điều quan trọng là MonoBehaviours của bạn gói gọn một hành vi cụ thể hoặc một hệ thống các hành vi liên quan chặt chẽ. Có rất nhiều MonoBehaviours chỉ cần ủy quyền cho các lớp C # đơn giản, nhưng nếu bạn thấy mình làm quá có thể cập nhật các cuộc gọi đến tất cả các loại đối tượng khác nhau đến mức nó bắt đầu giống như một trò chơi XNA, thì bạn ' lại gặp rắc rối nghiêm trọng và cần thay đổi kiến trúc của bạn.
// 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);
}
}
Điều này hoạt động hoàn toàn tốt, nhưng bạn có thể làm tốt hơn (cũng List<T>
không phải là cấu trúc dữ liệu tối ưu để lưu trữ các khả năng theo thời gian, bạn có thể muốn một LinkedList<T>
hoặc mộtSortedDictionary<float, T>
).
Phương pháp tiếp cận dựa trên dữ liệu
Có lẽ bạn có thể giảm các hiệu ứng khả năng của mình xuống thành các hành vi logic có thể được tham số hóa. Đây là những gì Unity đã thực sự được xây dựng cho. Bạn, với tư cách là một lập trình viên, thiết kế một hệ thống mà sau đó bạn hoặc nhà thiết kế có thể đi và thao tác trong trình chỉnh sửa để tạo ra nhiều hiệu ứng khác nhau. Điều này sẽ đơn giản hóa rất nhiều việc "gian lận" mã và tập trung hoàn toàn vào việc thực thi một khả năng. Không cần phải sắp xếp các lớp cơ sở hoặc giao diện và khái quát ở đây. Tất cả sẽ hoàn toàn là dữ liệu điều khiển (cũng đơn giản hóa việc khởi tạo các thể hiện lệnh).
Điều đầu tiên bạn cần là một ScriptableObject có thể mô tả khả năng của bạn. ScriptableObjects là tuyệt vời. Chúng được thiết kế để hoạt động như MonoBehaviours ở chỗ bạn có thể đặt các trường công khai của chúng trong trình kiểm tra của Unity và những thay đổi đó sẽ được tuần tự hóa vào đĩa. Tuy nhiên, chúng không được gắn vào bất kỳ đối tượng nào và không phải gắn vào đối tượng trò chơi trong một cảnh hoặc được khởi tạo. Chúng là các thùng dữ liệu bắt tất cả của Unity. Họ có thể tuần tự hóa các loại cơ bản, enum và các lớp đơn giản (không kế thừa) được đánh dấu[Serializable]
. Các cấu trúc không thể được tuần tự hóa trong Unity và tuần tự hóa là thứ cho phép bạn chỉnh sửa các trường đối tượng trong thanh tra, vì vậy hãy nhớ điều đó.
Đây là một ScriptableObject cố gắng làm rất nhiều. Bạn có thể chia nó thành các lớp và ScriptableObject được tuần tự hóa hơn, nhưng điều này được cho là chỉ cho bạn ý tưởng về cách thực hiện nó. Thông thường, điều này có vẻ xấu trong một ngôn ngữ hướng đối tượng hiện đại tốt đẹp như C #, vì nó thực sự giống như một số C89 với tất cả các enum đó, nhưng sức mạnh thực sự ở đây là bây giờ bạn có thể tạo ra tất cả các khả năng khác nhau mà không cần phải viết mã mới để hỗ trợ họ Và nếu định dạng đầu tiên của bạn không làm những gì bạn cần, hãy tiếp tục thêm vào cho đến khi nó được thực hiện. Miễn là bạn không thay đổi tên trường, tất cả các tệp tài sản tuần tự cũ của bạn sẽ vẫn hoạt động.
// 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();
}
Bạn có thể trừu tượng thêm phần Thiệt hại thành một lớp Nối tiếp để bạn có thể xác định các khả năng gây sát thương hoặc hồi máu và có nhiều loại sát thương trong một khả năng. Quy tắc duy nhất là không kế thừa trừ khi bạn sử dụng nhiều đối tượng tập lệnh và tham chiếu các tệp cấu hình thiệt hại phức tạp khác nhau trên đĩa.
Bạn vẫn cần MonoBehaviour của abilityActivator, nhưng bây giờ anh ấy làm việc nhiều hơn một chút.
// 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)
}
}
Phần COOLEST
Vì vậy, giao diện và thủ thuật chung trong cách tiếp cận đầu tiên sẽ hoạt động tốt. Nhưng để thực sự tận dụng tối đa Unity, ScriptableObjects sẽ đưa bạn đến nơi bạn muốn. Unity tuyệt vời ở chỗ nó cung cấp một môi trường rất phù hợp và hợp lý cho các lập trình viên, nhưng cũng có tất cả các đặc tính nhập dữ liệu cho các nhà thiết kế và nghệ sĩ bạn nhận được từ GameMaker, UDK, et. al.
Tháng trước, nghệ sĩ của chúng tôi đã sử dụng loại ScriptableObject mạnh mẽ được cho là để xác định hành vi cho các loại tên lửa khác nhau, kết hợp nó với AnimationCurve và một hành vi khiến tên lửa bay lơ lửng trên mặt đất và tạo ra cú xoáy-khúc côn cầu mới điên rồ này vũ khí tử thần.
Tôi vẫn cần quay lại và thêm hỗ trợ cụ thể cho hành vi này để đảm bảo nó hoạt động hiệu quả. Nhưng bởi vì chúng tôi đã tạo ra giao diện mô tả dữ liệu chung chung này, anh ấy đã có thể rút ý tưởng này ra khỏi không khí mỏng và đưa nó vào trò chơi mà không cần lập trình viên biết rằng anh ấy đang cố gắng thực hiện cho đến khi anh ấy đến và nói: "Này các bạn, nhìn này tại điều tuyệt vời này! " Và bởi vì nó rõ ràng tuyệt vời, tôi rất vui khi được hỗ trợ nhiều hơn cho nó.