Mark Geschrieben 20. August 2015 Melden Share Geschrieben 20. August 2015 Ich möchte euch ein etwas größeres Snippet für RPG Stats und Modifikationen (Items, buffs, etc) vorstellen. Das ganze ist ein Grundgerüst und es muss darauf aufbauend von der Nutzerseite noch etwas gemacht werden, liegt aber daran dass ich unmöglich alle Bereiche die man haben möchte abdecken kann. Was das System aber so in dieser Grundversion schon kann ist: - Beleibige Charakterstats/attribute darstellen, sei es ein Float Attribut, Int, Bool, String oder was anderes. - Stats können verändert werden, sei es permanent oder temporär - Stat Veränderungen können beliebig rückgängig gemacht werden (Buff verfällt, Item wird abgelegt) - Stat Veränderungen und Stats sind so transparent gestaltet dass man eine beliebige UI drauf ansetzen kann um dem Spieler alle wichtigen Infos zu zeigen - Stat Veränderungen und Veränderungen an den Veränderungen können durch Events verarbeitet werden. Zuerst etwas Zucker, wie man das ganze in einer simplen Art und Weise verwenden kann: var health = new Stat<float>("Health", 100); Debug.Log(health.Value); // 100 HP // HP reduzieren var baseModification = (IStatModification<float>)health.Modifications.First(); baseModification.Value -= 10; // leben um 10 HP reduzieren Debug.Log(health.Value); // 90 HP // HP um 50 HP boosten, zB durch ein Item var healthBuff = new StatModification<float>("HealthBuff", 0, new RPG.Stats.Accumulators.Add(), 50); healthBuff.Attach(health); Debug.Log(health.Value); // 140 HP // Buff wieder entfernen healthBuff.Detach(); Debug.Log(health.Value); // 90 HP // Auf Veränderungen reagieren health.Modified += (sender, arguments) => { var floatArguments = (StatModifiedEventArgs<float>)arguments; if (floatArguments.OldValue < floatArguments.NewValue) Debug.Log("Ich wurde geheilt!"); else Debug.Log("Ich wurde verletzt!"); }; baseModification.Value += 20; // Ich wurde geheilt! baseModification.Value -= 10; // Ich wurde verletzt! healthBuff.Attach(health); // Ich wurde geheilt! Stats und StatModifications haben eine ID die eindeutig sein sollte, aber vom System selbst nicht weiter verwendet wird. Durch die ID können extra Informationen von einem Drittsystem abgerufen werden um zB Beschreibungstexte zu den einzelnen Elementen anzuzeigen. Stats können wie man sehen kann durch Modifikationen mit Werten befüllt werden, also auch der Grundwert ist eine Modifikation welche wie im Health Beispiel oben implizit erstellt wird. Die Modifikationen können eine Priorität haben (die Grundmodifikation hat eine Priorität die dafür sorgt dass diese immer zuerst angewandt wird) Prioritäten dienen dazu die Stackmechaniken zu managen, so kann zB dafür gesorgt werden dass alle Wertveränderungen die durch Items verursacht werden (Rüstungsplus, etc) vor Wertveränderungen angewandt werden die durch Verzauberungen kommen (+50% HP). Modifikationen können die Stats dabei auf unterschiedlichste Art und Weise beeinflussen. Im System selbst gibt es 4 Möglichkeiten: - setzen des Wertes - addieren des Wertes - subtrahieren des Wertes - Multiplizieren des Wertes Im Health Beispiel wird das Setzen und Addieren gezeigt. Das wichtigste ist aber das Hinzufügen und Entfernen der Modifikationen selbst, denn darauf basiert das ganze System und ermöglicht es schnell und einfach beliebige Änderungen vorzunehmen. Auch CharakterStats Wachstum beim Levelup ist damit ideal umsetzbar wenn man denn noch die Möglichkeit haben möchte den Charakter zu respeccen. Sprich wenn ich ein LevelDown vornehmen möchte oder gar alle Werte zurücksetzen möchte ist das sehr einfach machbar: var levelUpStatModifications = stat.Modifications.Where(stat => stat.ID == "Levelup").ToArray(); foreach (var modification in levelUpStatModifications) { modification.Detach(); availableSkillPoints++; } Ich hoffe ihr findet es nützlich und könnt damit irgendetwas sinnvolles anfangen. Das ganze besteht aus einigen C# Files, aber davon muss man sich nicht abschrecken lassen um zu wissen was möglich ist reicht ein Blick auf die Interfaces. Im Anhang findet ihr das ganze als zip. Stats.zip Zuerst der Accumulator, welcher benötigt wird um die Verschiedenen Modifikationen miteinander zu verrechnen: IAccumulator.cs namespace RPG.Stats { public interface IAccumulator<T> { T Accumulate(T left, T right); } } Add.cs namespace RPG.Stats.Accumulators { public class Add : IAccumulator<float> { public float Accumulate(float left, float right) { return left + right; } } } Multiply.cs namespace RPG.Stats.Accumulators { public class Multiply : IAccumulator<float> { public float Accumulate(float left, float right) { return left * right; } } } Set.cs namespace RPG.Stats.Accumulators { public class Set<T> : IAccumulator<T> { public T Accumulate(T left, T right) { return right; } } } Subtract.cs namespace RPG.Stats.Accumulators { public class Subtract : IAccumulator<float> { public float Accumulate(float left, float right) { return left - right; } } } Dann die Stats selbst: IStat.cs (enthält auch gleich die eventHandler und EventArgumente für die Stat Veränderungs Events) using System; using System.Collections.Generic; namespace RPG.Stats { public class StatModifiedEventArgs : EventArgs { public enum ModifyType { ModificationAdded, ModificationRemoved, ModificationChanged } public readonly ModifyType Type; public readonly IStatModification Modification; public StatModifiedEventArgs(ModifyType type, IStatModification modification) { Type = type; Modification = modification; } } public class StatModifiedEventArgs<T> : StatModifiedEventArgs { public readonly T OldValue; public readonly T NewValue; public StatModifiedEventArgs(ModifyType type, IStatModification modification, T oldValue, T newValue) : base(type, modification) { this.OldValue = oldValue; this.NewValue = newValue; } } public delegate void StatModifiedEventHandler(object sender, StatModifiedEventArgs e); public interface IStat { string Id { get; } event StatModifiedEventHandler Modified; IEnumerable<IStatModification> Modifications { get; } } public interface IStat<T> : IStat { T Value { get; } } } Stat.cs using System.Collections.Generic; namespace RPG.Stats { public class Stat<T> : IStat<T> { private List<IStatModification<T>> modifications = new List<IStatModification<T>>(); private bool isDirty = true; private T value; public Stat(string id, IStatModification baseModification = null) { this.Id = id; if (baseModification != null) baseModification.Attach(this); } public Stat(string id, T baseValue) : this(id, new StatModification<T>(string.Empty, int.MinValue, new Accumulators.Set<T>(), baseValue)) { } public string Id { get; private set; } public event StatModifiedEventHandler Modified; public IEnumerable<IStatModification> Modifications { get { return modifications; } } public T Value { get { if (isDirty) { isDirty = false; value = default(T); foreach (var modification in modifications) value = (modification as StatModification<T>).Accumulate(value); } return value; } } internal void AddModification(IStatModification<T> modification) { isDirty = true; modifications.Add(modification); modifications.Sort((a, => a.Priority.CompareTo(b.Priority)); if (Modified != null) Modified(this, new StatModifiedEventArgs<T>(StatModifiedEventArgs.ModifyType.ModificationAdded, modification, value, Value)); } internal void RemoveModification(IStatModification<T> modification) { isDirty = true; modifications.Remove(modification); if (Modified != null) Modified(this, new StatModifiedEventArgs<T>(StatModifiedEventArgs.ModifyType.ModificationRemoved, modification, value, Value)); } internal void FireModificationChanged(IStatModification<T> modification) { isDirty = true; if (Modified != null) Modified(this, new StatModifiedEventArgs<T>(StatModifiedEventArgs.ModifyType.ModificationChanged, modification, value, Value)); } } } Und zu guter letzt die Modifikationen: IStatModification.cs using System; namespace RPG.Stats { public class ModificationModifiedEventArgs : EventArgs { public enum ModifyType { Added, Removed, Changed } public readonly ModifyType Type; public readonly IStat Owner; public ModificationModifiedEventArgs(ModifyType type, IStat owner) { Type = type; Owner = owner; } } public class ModificationModifiedEventArgs<T> : ModificationModifiedEventArgs { public readonly T OldValue; public readonly T NewValue; public ModificationModifiedEventArgs(ModifyType type, IStat owner, T oldValue, T newValue) : base(type, owner) { this.OldValue = oldValue; this.NewValue = newValue; } } public delegate void ModificationModifiedEventHandler(object sender, ModificationModifiedEventArgs e); public interface IStatModification { string Id { get; } int Priority { get; } event ModificationModifiedEventHandler Modified; IStat Assignee { get; } void Attach(IStat stat); void Detach(); } public interface IStatModification<T> : IStatModification { T Value { get; set; } } } StatModification.cs namespace RPG.Stats { public class StatModification<T> : IStatModification<T> { private T value; private IAccumulator<T> accumulator; public StatModification(string id, int priority, IAccumulator<T> accumulator, T value) { this.Id = id; this.Priority = priority; this.accumulator = accumulator; this.Value = value; } public string Id { get; private set; } public int Priority { get; private set; } public event ModificationModifiedEventHandler Modified; public IStat Assignee { get; private set; } public T Value { get { return value; } set { if (object.Equals(this.value, value)) return; var oldValue = this.value; this.value = value; if (Assignee != null) (Assignee as Stat<T>).FireModificationChanged(this); if (Modified != null) Modified(this, new ModificationModifiedEventArgs<T>(ModificationModifiedEventArgs.ModifyType.Changed, Assignee, oldValue, value)); } } public void Attach(IStat stat) { if (Assignee != null) return; Assignee = stat; (Assignee as Stat<T>).AddModification(this); if (Modified != null) Modified(this, new ModificationModifiedEventArgs<T>(ModificationModifiedEventArgs.ModifyType.Added, stat, value, value)); } public void Detach() { if (Assignee == null) return; var oldAssignee = Assignee; Assignee = null; (oldAssignee as Stat<T>).RemoveModification(this); if (Modified != null) Modified(this, new ModificationModifiedEventArgs<T>(ModificationModifiedEventArgs.ModifyType.Removed, oldAssignee, value, value)); } internal T Accumulate(T currentValue) { return accumulator.Accumulate(currentValue, value); } } } 4 Zitieren Link zu diesem Kommentar Auf anderen Seiten teilen More sharing options...
AgentCodeMonk Geschrieben 20. August 2015 Melden Share Geschrieben 20. August 2015 Es wird Zeit, dass das Syntax Highlighting wieder Einzug ins Forum hält... Interessanter Ansatz! Werde ich mir nochmal genau durchlesen. Ohne Syntax Highlighting... ist das nicht so cool Da ich an ähnlichem werkel, ist das Thema sehr spanned für mich. Trotzdem habe ich mich für ein eher starreres System entschieden und nutze enums. Auch dort gibt es nur ein paar Stellen, an denen man Hand anlegen muss, wenn man es in einem anderen Projekt mit anderen Stats nutzen will. 1 Zitieren Link zu diesem Kommentar Auf anderen Seiten teilen More sharing options...
Silveryard Geschrieben 20. August 2015 Melden Share Geschrieben 20. August 2015 Ich hab auch mal wieder vor, mein System etwas dynamischer zu machen. Finde deinen Ansatz super Zitieren Link zu diesem Kommentar Auf anderen Seiten teilen More sharing options...
Mark Geschrieben 20. August 2015 Autor Melden Share Geschrieben 20. August 2015 ACM: Mit Enums meinst du EnumValues anstatt Namen? Ich glaube irgendwo hier im Forum hatten wir das schonmal in der Art und ich finde Enums in dem Fall auch viel besser. Musste aber strings für die Erweiterbarkeit nehmen. Das Problem kann ich aber weitgehendst umgehen indem ich beides kombiniere: enum StatNames { Health, Strength, Dextery NutSize Mana } ... var healthStat = new Stat<float>(StatNames.Health.ToString(), 100); Auch möglich wäre dass ich den Stat CTor Parameter für die ID von string auf object ändere und dann das hier mache: public Stat(object id, T baseValue) { this.ID = id.ToString(); } Aber das würde dann wieder verdecken dass die ID die reingegeben wird zu einen String wird. Oder ich arbeite einfach mit const strings, wäre auch machbar. 1 Zitieren Link zu diesem Kommentar Auf anderen Seiten teilen More sharing options...
AgentCodeMonk Geschrieben 20. August 2015 Melden Share Geschrieben 20. August 2015 Genau das meinte ich. Fällt mir gerade auf, dass ich das hätte genauer schreiben können Zitieren Link zu diesem Kommentar Auf anderen Seiten teilen More sharing options...
Mark Geschrieben 20. August 2015 Autor Melden Share Geschrieben 20. August 2015 Man kann den Code so anpassen dass anstatt einer string ID einfach ein enum genommen wird, oder noch cooler die ID per Generic typisieren. Hab auch wirklich mit dem Gedanken gespielt das so zu machen, aber war mir dann doch etwas zuviel des Guten Besonders da es unter Umständen echt viele Stats gibt (man kann dadurch auch Skillsets darstellen, Level, Skillpoints, etc) Ausserdem sollte es ein ähnliches System auch für die Modifikationen selbst geben und da kann es auch wieder hunderte geben. Die Möglichkeit die IDs der Modifikationen und Stats mit einer DB für Beschreibungen etc zu kombinieren hat mich dann doch sehr zu diesem string basierten IDs getrieben. Zitieren Link zu diesem Kommentar Auf anderen Seiten teilen More sharing options...
Ismoh Geschrieben 20. August 2015 Melden Share Geschrieben 20. August 2015 Ich finde es sehr großzügig von dir, dass du uns dein Grundgerüst zur Verfügung stellst. Ich bin selber gerade dabei etwas ähnliches zu erstellen. Ich werde sicher einige male nachsehen wie du das umgesetzt hast. Vielen Dank. Zitieren Link zu diesem Kommentar Auf anderen Seiten teilen More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.