Im ersten Teil habe ich darüber gesprochen, warum Prototyping und allgemein wo ich anfangen soll
-
Teil 1Im zweiten Teil ein wenig durch die Schlüsselklassen laufen
und Architektur -
Teil 2Und der dritte Teil - es wird tatsächlich eine kleine Diskussion geben, wir werden analysieren, wie Modifikatoren funktionieren, und auf das Spielfeld fallen (das ist nicht schwierig, aber es gibt Nuancen). Und ein wenig über Architektur werde ich ohne Langeweile versuchen.
Screenshots über die Rakete sind im Allgemeinen langweilig, daher schlage ich vor, ein Video eines anderen Prototyps anzusehen, der in zwei Wochen zusammen mit den Grafiken zusammengestellt wurde und aufgrund der Tatsache, dass es im Genre der Plattformer keinen Weg gibt, aufgegeben wurde. Dies ist übrigens eine der Schlüsselideen rund um den Prototyp - zusammenzubauen, um zu sehen, ob dies Scheiße ist, ist es notwendig? Und werfen Sie ehrlich in den Korb, wenn Ihnen die Antworten nicht überzeugend erscheinen. Aber! Dies gilt nicht für kreative Projekte - manchmal geschieht Kreativität aus Gründen der Kreativität.
Also vidosik, du musst dir die nach Level geordneten Texte ansehen und nicht das Gameplay):
Kleiner Exkurs
Im letzten Artikel habe ich den Überprüfungscode erhalten und bin dafür dankbar. Kritik hilft bei der Entwicklung, auch wenn Sie nicht damit einverstanden sind.
Aber ich möchte für die Architektur und Syntax in Bezug auf Prototypen bereitstellen:- Egal wie cool Sie sind, Sie können nicht einfach vorhersehen, dass es nicht verpfändet wird, und es kann viel verpfändet werden, was nicht benötigt wird. Daher ist in jedem Fall ein Refactoring oder eine Erweiterung erforderlich. Wenn Sie die spezifischen Vorteile des Codes / Ansatzes nicht beschreiben können, ist es besser, nicht viel Zeit mit diesem Code zu verbringen.
- Warum ist OOP / Event Model / Composition meiner Meinung nach für Prototypen einfacher als ECS, Unity COOP, DI FrameWorks, Reactive Frameworks usw. Weniger kritzeln, alle Verbindungen sind im Code sichtbar, da die Hauptaufgabe des Prototyps darin besteht, die Hauptfrage zu beantworten - ist es möglich, sie zu spielen, und eine Reihe von sekundären - was für das Gameplay, dies oder das, besser ist. Daher müssen Sie die erforderlichen Funktionen so schnell wie möglich implementieren. Warum ein Framework für ein kleines Projekt einführen, den gesamten Müll vorschreiben, um drei Spieleinheiten zu implementieren? Jede davon ist eine Klasse von 50-100 Zeilen. Die Architektur sollte als Teil der Prototyp-Aufgaben und als Teil einer möglichen Erweiterung auf Alpha betrachtet werden. Die zweite wird jedoch mehr im Kopf als im Code benötigt, damit Sie beim Hinzufügen von Code nicht brennen
Über Modifikatoren:
Und schließlich zum Modifikator selbst:Hier und früher nenne ich die Modifikatoren die Kraftfelder, die die Rakete berührt und die ihren Weg beeinflussen. Im Prototyp gibt es zwei Arten von ihnen - Beschleunigung und Ablenkung.
Modifikatorklassepublic class PushSideModificator : MonoBehaviour { [SerializeField] TypeOfForce typeOfForce = TypeOfForce.Push; [SerializeField] private float force; [SerializeField] DropPanelConfig dropPanelConfig; private float boundsOfCollider; private void OnTriggerEnter(Collider other) { boundsOfCollider = other.bounds.extents.x; GlobalEventAggregator.EventAggregator.Invoke(new SpaceForces { TypeOfForce = typeOfForce, Force = force, ColliderBound = boundsOfCollider, CenterOfObject = transform.position, IsAdded = true }); } private void OnTriggerExit(Collider other) { GlobalEventAggregator.EventAggregator.Invoke(new SpaceForces { CenterOfObject = transform.position, IsAdded = false }); } }
Dies ist eine sehr einfache Klasse, deren Aufgabe es ist, zwei Ereignisse zu bestehen:
- Der Treffer des Spielers auf dem Feld (was eigentlich ein Auslöser für die physische Einheit ist) und der gesamte erforderliche Kontext - die Art des Modifikators, seine Position, die Größe des Kolliders, die Stärke des Modifikators usw. In diesem und dem Fall des Aggregators kann er Interessenten jeden Kontext vermitteln. In diesem Sinne ist dies ein Raketenmodell, das den Modifikator verarbeitet.
- Das zweite Ereignis - der Spieler hat das Feld verlassen. Um seinen Einfluss auf die Flugbahn des Spielers zu entfernen
Wofür ist das Ereignismodell? Wer könnte diese Veranstaltung noch brauchen?
Im aktuellen Projekt ist dies nicht implementiert, aber:- Sprachausgabe (erhielt ein Ereignis, dass jemand das Feld betrat - wir spielen den entsprechenden Ton, jemand ging aus - ähnlich)
- UI-Markierungen, sagen wir für jedes Feld, wir werden etwas Treibstoff aus der Rakete entfernen, Tooltips sollten erscheinen, dass wir das Feld betreten und Treibstoff verloren haben, na ja, oder Punkte für jeden Treffer auf dem Feld verdienen, es gibt viele Optionen, die die Benutzeroberfläche für den Spieler interessiert das Feld.
- Besonderes Effekte - Wenn sie in einem anderen Feldtyp getroffen werden, können verschiedene Effekte überlagert werden, sowohl auf die Rakete selbst als auch auf den Raum um die Rakete / das Feld. Besonderes Effekte können von einer separaten Entität / Steuerung verarbeitet werden, die auch Ereignisse von Modifikatoren abonniert.
- Nun, dies ist ein Minimum an Code, Service Locators, Aggregation, Abhängigkeiten usw. werden nicht benötigt.
Gameplay Basis
In diesem Prototyp besteht die Essenz des Spiels darin, Modifikatoren auf dem Spielfeld zu platzieren, die Flugbahn der Rakete anzupassen, um Hindernisse herumzufliegen und den Zielpunkt / Planeten zu treffen. Zu diesem Zweck haben wir rechts ein Feld, in dem sich die Modifikatorsymbole befinden.

Panel-Klasse [RequireComponent (typeof(CanvasGroup))] public class DragAndDropModifiersPanel : MonoBehaviour { [SerializeField] private DropModifiersIcon iconPrfb; [SerializeField] private DropPanelConfig config; private CanvasGroup canvasGroup; private void Awake() { GlobalEventAggregator.EventAggregator.AddListener<ButtonStartPressed>(this, RocketStarted); canvasGroup = GetComponent<CanvasGroup>(); } private void RocketStarted(ButtonStartPressed obj) { canvasGroup.DOFade(0, 1); (canvasGroup.transform as RectTransform).DOAnchorPosX(100, 1); } private void Start() { for (var x = 0; x< 3; x++) { var mod = config.GetModifierByType(TypeOfForce.Push); var go = Instantiate(iconPrfb, transform); go.Init(mod); } for (var x = 0; x< 1; x++) { var mod = config.GetModifierByType(TypeOfForce.AddSpeed); var go = Instantiate(iconPrfb, transform); go.Init(mod); } } }
Fragen antizipieren:
for (var x = 0; x< 3; x++) for (var x = 0; x< 1; x++)
3 und 1 - die sogenannten magischen Zahlen, die einfach aus dem Kopf genommen und in den Code eingefügt werden, sollten vermieden werden, aber warum sind sie hier? Das Prinzip, nach dem das rechte Panel gebildet wird, ist noch nicht festgelegt, und es wurde einfach beschlossen, den Prototyp mit nur so vielen Modifikatoren am Prototyp zu testen.
Wie mache ich es richtig? - Geben Sie es zumindest in serialisierbare Felder ein und stellen Sie die erforderlichen Mengen durch den Inspektor ein. Warum bin ich zu faul und solltest du es tun? Hier müssen wir vom Gesamtbild ausgehen, eine separate Entität und Konfiguration werden weiterhin für die Bildung der erforderlichen Anzahl von Modifikatoren verantwortlich sein, daher war ich hier zu faul und habe in Zukunft viel Refactoring erwartet. Aber besser nicht faul! )
Über Konfigurationen - Als die ersten Vorlesungen über ScriptableObject erschienen, gefiel mir die Idee, Daten als Asset zu speichern. Sie erhalten die erforderlichen Daten dort, wo Sie sie benötigen, ohne an eine Einzelkopie-Instanz gebunden zu sein. Dann gab es einen Vortrag über einen Ansatz zur Spieleentwicklung mit ScriptableObject, in dem Instanzeinstellungen gespeichert wurden. Tatsächlich sind die Voreinstellungen / Einstellungen von etwas, das im Asset gespeichert ist, die Konfiguration.
Betrachten Sie die Konfigurationsklasse:Konfigurationsklasse [CreateAssetMenu(fileName = "DropModifiersPanel", menuName = "Configs/DropModifier", order = 2)] public class DropPanelConfig : ScriptableObject { [SerializeField] private ModifierBluePrintSimple[] modifierBluePrintSimples; public DropModifier GetModifierByType(TypeOfForce typeOfModifiers) { return modifierBluePrintSimples.FirstOrDefault(x => x.GetValue.TypeOfModifier == typeOfModifiers).GetValue; } } [System.Serializable] public class DropModifier { public TypeOfForce TypeOfModifier; public Sprite Icon; public GameObject Modifier; public Material Material; }
Was ist das Wesentliche seiner Arbeit? Es speichert eine angepasste Modifikator-Datenklasse.
public class DropModifier { public TypeOfForce TypeOfModifier; public Sprite Icon; public GameObject Modifier; public Material Material; }
Der Modifikatortyp wird zur Identifizierung benötigt, ein Symbol für die Benutzeroberfläche, der Spielmodus des Spielobjekts des Modifikators und das Material hier, damit es während der Konfiguration geändert werden kann. Modifikatoren befinden sich möglicherweise bereits auf dem Spielfeld, und sagen wir, der Spieledesigner ändert seinen Typ. Jetzt gibt er die Beschleunigung an, der Modifikator wird aus der Konfiguration entfernt und aktualisiert alle Felder, einschließlich des Materials, entsprechend dieser Art von Modifikator.
Die Arbeit mit der Konfiguration ist sehr einfach - wir wenden uns an die Konfiguration für Daten für einen bestimmten Typ. Wir erhalten diese Daten, intime visuelle und mögliche Einstellungen aus diesen Daten.
Wo ist der Gewinn?Der Vorteil ist eine sehr große Flexibilität, wenn Sie beispielsweise das Material und das Symbol im Beschleunigungsmodifikator ändern oder sagen möchten, dass Sie das gesamte Spielprojekt ersetzen können. Anstatt neu zu schreiben und an die Felder des Inspektors weiterzuleiten, ändern wir diese Daten einfach in einer Konfiguration und voila - alles wird mit uns auf allen Szenen / Ebenen / Panels aktualisiert.
Und wenn es mehrere Daten für den Beschleuniger-Modifikator in der Konfiguration gibt?Im Prototyp können Sie ihn einfach manuell verfolgen, damit keine Daten dupliziert werden. In einem Arbeitsentwurf benötigen Sie Tests und Datenvalidierung.
Vom Symbol zum Spielfeld
Modifikator-Symbolklasse public class DropModifiersIcon : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler { [SerializeField] private Image icon; [Header(" ")] [SerializeField] private RectTransform canvas; private CanvasGroup canvasGroup; private DropModifier currentModifier; private Vector3 startPoint; private Vector3 outV3; private GameObject currentDraggedObj; private void Start() { canvasGroup = GetComponent<CanvasGroup>(); startPoint = transform.position; canvas = GetComponentInParent<Canvas>().transform as RectTransform; } public void Init(DropModifier dropModifier) { icon.sprite = dropModifier.Icon; currentModifier = dropModifier; } public void OnBeginDrag(PointerEventData eventData) { BlockRaycast(false); currentDraggedObj = Instantiate(currentModifier.Modifier, WorldSpaceCoord(), Quaternion.identity); GlobalEventAggregator.EventAggregator.Invoke(new ImOnDragEvent { IsDragging = true }); } private void BlockRaycast(bool state) { canvasGroup.blocksRaycasts = state; } public void OnDrag(PointerEventData eventData) { Vector2 outV2; RectTransformUtility.ScreenPointToLocalPointInRectangle(canvas, Input.mousePosition, null, out outV2); transform.position = canvas.transform.TransformPoint(outV2); if (currentDraggedObj != null) currentDraggedObj.transform.position = WorldSpaceCoord(); } private Vector3 WorldSpaceCoord() { RectTransformUtility.ScreenPointToWorldPointInRectangle(canvas, Input.mousePosition, Camera.main, out outV3); return outV3; } public void OnEndDrag(PointerEventData eventData) { GlobalEventAggregator.EventAggregator.Invoke(new ImOnDragEvent { IsDragging = false }); if (eventData.pointerCurrentRaycast.gameObject != null && eventData.pointerCurrentRaycast.gameObject.layer == 5) { Destroy(currentDraggedObj); transform.SetAsLastSibling(); canvasGroup.blocksRaycasts = true; } else Destroy(gameObject); } } public struct ImOnDragEvent { public bool IsDragging; }
Was ist hier los?Wir nehmen das Symbol aus dem Panel und erstellen darunter ein Spielblog des Modifikators. Und tatsächlich setzen wir die Koordinate von der Klick- / Schubkarre zum Spielbereich, also verschieben wir den Modifikator im Spielbereich zusammen mit dem Symbol in der Benutzeroberfläche. Ich rate Ihnen, über RectTransformUtility zu lesen. Dies ist eine großartige Hilfsklasse, in der es viele Funktionen für die Benutzeroberfläche gibt.
Nehmen wir an, wir ändern unsere Meinung über das Einfügen eines Modifikators und geben ihn an das Panel zurück.
if (eventData.pointerCurrentRaycast.gameObject != null && eventData.pointerCurrentRaycast.gameObject.layer == 5)
Mit diesem Code können wir verstehen, was sich unter dem Klick befindet. Warum wird hier auch die Ebenenprüfung angezeigt? Und warum ist wieder die magische Nummer 5? Wie wir uns aus dem zweiten Teil erinnern, verwenden wir das Rakester-Diagramm nicht nur für die Benutzeroberfläche, sondern auch für die Schaltfläche in der Szene. Wenn wir die Funktion hinzufügen, bereits platzierte Modifikatoren auf dem Feld zu löschen oder zu verschieben, fallen sie daher auch unter das Rake-Diagramm Es gibt auch eine zusätzliche Prüfung für die Zugehörigkeit zur UI-Ebene. Dies ist die Standardebene, und ihre Reihenfolge ändert sich nicht. Daher ist die Zahl 5 hier im Allgemeinen keine magische Zahl.
Infolgedessen stellt sich heraus, dass das Symbol gelöscht wird, wenn wir das Symbol über dem Bedienfeld loslassen und zum Bedienfeld zurückkehren. Wenn der Modifikator über dem Spielfeld auf dem Spielfeld verbleibt, wird das Symbol gelöscht.
1 Arbeitstag wurde für den Prototypcode aufgewendet. Plus ein bisschen Aufhebens um Datei und Grafik. Im Allgemeinen wurde das Gameplay trotz der vielen Fragen zu Kunst- und Spieldesign-Chips als geeignet befunden. Mission abgeschlossen.
Schlussfolgerungen und Empfehlungen
- Legen Sie minimale Architektur, aber dennoch Architektur
- Befolgen Sie die Grundprinzipien, aber ohne Fanatismus.
- Wählen Sie einfache Lösungen
- Zwischen Vielseitigkeit und Geschwindigkeit - es ist besser, eine Geschwindigkeit für den Prototyp zu wählen
- Bei großen / mittleren Projekten ist es besser, das Projekt von Grund auf neu zu schreiben. Zum Beispiel ist der Trend in Unity jetzt DOTS, Sie müssen viele Komponenten und Systeme schreiben, es ist schlecht für kurze Auflagen, Sie verlieren Zeit, für lange Auflagen - wenn alle Komponenten und Systeme registriert sind, beginnt der Zeitgewinn. Ich finde es nicht cool, viel Zeit mit dem Trend der Architektur zu verbringen und herauszufinden, was der Prototyp ist
Erfolgreiche Prototypen an alle.