Hallo Habr! Heute, am 26. September, habe ich Geburtstag, was für mich ein guter Grund ist, einen Artikel über die Fortsetzung meines Puzzles herauszubringen. Ich warne Sie, dass ich ein Amateur bin, was bedeutet, dass es in ALLEN Aspekten der Entwicklung viele Fehler geben wird (wenn Sie es finden, schreiben Sie, werde ich es gerne berücksichtigen). In diesem Artikel möchte ich alles (naja oder fast alles) darüber erzählen, wie ich die Fortsetzung gemacht habe, wie ich dazu gekommen bin und wozu ich gekommen bin.
Um nicht verwirrt zu werden, meine ich hier die Bedeutung der Begriffe im Artikel:
Das Original ist der erste Teil, ein Spiel mit einem Underground-Drive der Techno-Demo. Sie können hier darüber lesen.
Die Fortsetzung ist der zweite Teil der Serie, das Spiel, das in diesem Artikel behandelt wird.
Ich werde das ursprüngliche Spiel regelmäßig mit der Fortsetzung vergleichen, um den Unterschied zwischen den beiden hervorzuheben.
Kurz über die EntwicklungIch habe Ende Januar mit der Arbeit am Spiel begonnen und Ende März war der technische Teil abgeschlossen (2 Monate). Nachdem ich ein anderes Spiel aufgenommen und zurückgekehrt war, um dieses Spiel Mitte Mai weiterzuentwickeln. Ich bin am Ende des Sommers klar fertig geworden und habe die ganze Zeit (3,5 Monate) das Spiel mit Inhalten gefüllt. Infolgedessen wurde die Fortsetzung von mir noch schneller als das ursprüngliche Spiel (6 Monate gegenüber 5,5 Monaten).
Ich habe ein Spiel mit der Unity Engine gemacht. Ich möchte, dass
diese Jungs ihre eigene Engine entwickeln und die Programmierung vorantreiben, aber etwas ging schief und entschied sich trotzdem, das Spiel auf einem Standard zu machen, aber von mir getestet.
Zwischen dem Original und der FortsetzungDie Idee, eine Fortsetzung zu erstellen, kam mir einen Monat vor der Veröffentlichung des Originalspiels (irgendwo im August). Angesichts der Fehler, die ich gemacht habe, wollte ich alles löschen und wieder mit erfolgreichen Erfolgen arbeiten. Aber ich begann nichts zu ändern, da es viel Problemcode gab, der gesamte Inhalt fertig war und ich die Entwicklung nur verzögerte. Es war notwendig, zur Freilassung zu gehen.
Nach der Veröffentlichung wurde ich erneut von der Idee einer Fortsetzung gequält. Dieses Mal habe ich nicht angefangen, weil ich moralisch faul war, nach der Veröffentlichung war es völlig weich. Ich wollte etwas Neues und Interessantes. Massenexperimente begannen.
In den nächsten 3 Monaten habe ich versucht, jede Idee, jede Einstellung, jedes Konzept in Spiele umzusetzen. Ich tat es trotz des Ausmaßes der Ambitionen, der Schwierigkeiten bei der Ausführung und manchmal auch trotz der Logik und des gesunden Menschenverstandes. Als Ergebnis habe ich ungefähr 50 Projekte bekommen. Sie waren alle aus verschiedenen Genres: von Schützen zu Strategien, von Plattformspielern zu RPG-Spielen.
Also würden die Experimente fortgesetzt, bis ich müde wurde. Und ich bin es leid, keine Spiele zu machen, sondern die Unvollständigkeit der Spiele, die ich gemacht habe. Ich gab mir ein Ziel: mindestens eine Woche vor dem Ende eine Art Spiel zu machen. Und so erschien mein zweites Spiel.
Pro 2 SpielDieses Spiel ist sehr einfach und gleichzeitig komplex. Es ist notwendig, Linien und keine Graphen zu schneiden. Die Bedeutung des Spiels ist, dass jede Schnittlinie durch 2 geteilt wurde und in der Mitte ein Diagramm angezeigt wurde. Das Merkmal des Spiels war, dass die gesamte Geometrie dynamisch war. Diagramme wurden verschoben, und Linien verbanden immer bestimmte Diagramme.
Danach war ich motiviert (ich bin motiviert) und bereit für ein neues Projekt. Ich spürte einen Kraftanstieg und nahm immer noch die Fortsetzung meines Spiels auf.
IdeeBevor ich anfing, etwas zu tun, beschloss ich, mir das Original vollständig anzusehen. Und entsetzt. Von der Qualität. Das Spiel wurde vor allem unter Standard-Rätseln gemäht: die Notwendigkeit, Level freizuschalten, Sterne zu sammeln, einen Timer zu haben, zu beenden, aber all dies wurde ohne Budget und sehr geschmacklos gemacht. Dem Original fehlten wirklich Animationen! Obwohl etwas Originelles darin war und etwas, das wahrscheinlich aufrichtig war. Obwohl sie es auch hier geschafft haben, mich zu übertreffen.
Ich habe etwas Ähnliches gefundenEs stellt sich heraus, dass es ein sehr ähnliches Spiel mit einem fast identischen Namen gibt. Und sie sieht aus wie eine erfolgreichere Variante meines Spiels. Ich habe in diesem
Video von ihr
erfahren .
Nachdem ich herausgefunden hatte, dass es sich bei diesem Spiel um einen exklusiven LG Smart TV handelt. Es wurde 2014 von der russischen Abteilung des LG R & D Lab erstellt:
Die Steuerung erfolgt über die Pfeile "links" und "rechts" auf der Fernbedienung. Auf die gleiche Weise wie in meinem Spiel (2 Teile des Bildschirms). Was soll ich sagen, der Neigungswinkel ist der gleiche - 30 °. Rein technisch kann man sagen, dass mein Spiel dadurch plagiiert wird. Obwohl ich ungefähr 2 Monate nach der Veröffentlichung des ersten Spiels von ihr erfahren habe.
Als ich die sehr bedauerliche Position des Originals verstand, beschloss ich, das Spiel mit radikalen Änderungen wiederzubeleben, um es besser zu machen. Und dann flog die Fantasie: Lass es eine Handlung geben und es wird eine Wahl geben, es wird Bosse mit nachdenklichen Angriffen geben, es wird eine Produktion geben, die dem Original so fehlte, es wird die Sensation eines einzelnen abgeschlossenen Abenteuers geben, usw. Im Allgemeinen die besten Ideen, die mir in der Zeit zwischen dem Original und der Fortsetzung gekommen sind. Und alles, was nicht oder schlecht funktionierte, war für mich bestimmt, um es aus der Fortsetzung herauszuwerfen.
Erste DemoBei ihm fing natürlich alles an. Ich entschied: "Wenn Sie Probleme lösen, dann tun Sie es gründlich." Und das erste Opfer solcher Veränderungen war das Management. Ich könnte es einfach aus einem ähnlichen Spiel stehlen (siehe oben). Dies ist genau das Management, das ich ursprünglich wollte, aber nicht wusste, wie es geht. Eine Ergänzung wäre ganz einfach: Fügen Sie bei jedem Klick Rotationsanimationen hinzu. Aber das war nichts für mich. Zumindest für die Wahrnehmung der Steuerung sowie in einem ähnlichen Spiel war es notwendig, dieselbe statische Kamera herzustellen und offensichtlich die Level zusammen mit dem Tempo des Spiels zu reduzieren. Aber ich wollte Action, Dynamik und Geschwindigkeit, also habe ich die ursprüngliche Steuerung logisch weiterentwickelt. Anstatt ein bestimmtes Maß zu drücken und zu drehen, gab es nun eine Klemmung, und der Grad der endgültigen Drehung wurde durch seine Dauer bestimmt. Es sah deutlich besser aus als im Original.
Weil ich normalerweise die Kontrolle kontrollierte, verschwand der Hauptfehler des Originals und jetzt war es möglich, Levels VIEL stärker als im Original zu laden, ohne Angst vor Verzögerungen und Friesen zu haben. Und dann kam der experimentelle Teil.
Grafische DemoIch wusste nie, wie man normale Grafiken zeichnet, und fast immer wurde sie durch den technologischen Teil bzw. dessen normale Ausführung ersetzt. Und dieses Spiel war keine Ausnahme. Anstelle einfacher normaler Sprites erschien realistisches Licht. Es war eine Illusion von 2d Licht. Tatsächlich ist dies dreidimensionales Licht gegen eine Metalloberfläche, und alle Objekte hatten Materialien mit spezifischen Shadern. Es sah ziemlich gut aus:
In Tests produzierte es stabile 60 fps, aber am Telefon lag es sogar auf meinem Sony Xperia bei etwa 20 fps und sank auf 10 fps. Und ich bin auf eine Leistungsdecke gestoßen. Ich musste einen anderen Weg gehen, den Weg der Zerstörbarkeit ...
ZerstörbarkeitAnfangs schien mir alles eine schlechte Idee zu sein. Aber ich habe mich entschlossen, es zu versuchen und jetzt ist dies meine Hauptspielshow. Nach dem Plan wollte ich wieder mehr Realismus, nämlich die Zerstörung der erzeugten Fragmente, abhängig von der Richtung und Stärke des Aufpralls. Aber der Plan ruhte wieder an der Decke, diesmal meines Wissens. Ich musste zu einem einfacheren vereinfachen.
Die Zerstörbarkeit basierte nun auf einem einfacheren Funktionsprinzip, nämlich einer Kopie von sich selbst, nur aus physischen Fragmenten, und das ursprüngliche Objekt entfernte die Komponenten von SpriteRenderer, Collider2D und, falls vorhanden, Rigidbody2D.
Aber es stellte sich eine andere Frage - Collider. Einerseits könnten Sie PolygonCollider2D verwenden und nicht gequält werden, andererseits müssten Sie später unter Spieldesign und -optimierung leiden. Daher hatten alle Fragmente der zerstörten Blöcke BoxCollider2D (sogar Fragmente runder Objekte).
Ein wesentlicher Beitrag zur Optimierung wurde auch durch die korrekte Einstellung des zeitlich festgelegten Schrittparameters geleistet (er wurde gleich 0,0 (3) oder 30 pro Sekunde). Aber jetzt flog das Objekt mit hoher Geschwindigkeit durch das Objekt, was sich definitiv auf das Spieldesign auswirkte.
Diese Elemente haben die Optimierung auf ein akzeptables Niveau gebracht und jetzt können sich bis zu Hunderte von physischen Objekten auf der Bühne befinden! Nach dem Original war es definitiv ein Durchbruch, eine Revolution usw. Als ich merkte, dass ich mich in die richtige Richtung bewegte, beschloss ich, ein weiteres langjähriges Spielproblem zu beheben: überwältigenden Hardcore. Um das Spiel, das ich gemacht habe, irgendwie zu provozieren ...
SchadenssystemFür mich ist dies der dunkelste Teil der Entwicklung, der zweimal umgeschrieben wurde. Die Arbeiten daran waren noch nicht abgeschlossen. Infolgedessen kam ein äußerst ausgeklügeltes System heraus, das jedoch recht umfangreich funktionierte.
Zunächst ist jedoch zu erwähnen, wie die Schadenswahrnehmung hier funktioniert. Es mag scheinen, dass es nach dem Prinzip funktioniert: "Je härter du schlägst, desto stärker ist der Schaden", aber das ist nicht so. In den meisten Fällen funktioniert es nach dem Prinzip „Je länger der Kontakt - desto größer der Schaden“, wobei der Ort einer so wichtigen Sache wie „Aufprallkraft“ durch einen Schadensfaktor ersetzt wurde, der je nach Situation für jedes Objekt, das Schaden verursacht, manuell konfiguriert wurde. Dies geschah aufgrund der Tatsache, dass sich der festgelegte Zeitschritt als so groß herausstellte, dass ein mächtiger Fehler entstand: Das Spiel schaffte es nicht, Enter2D zu verarbeiten. Und dies führte zu Situationen wie: Absturz mit hoher Geschwindigkeit - kein Schaden. Warum habe ich es nicht behoben? Auch das kann ich nicht sagen.
Wo also begann das Schadenssystem? Aus Gesundheit. Der Spieler hat eine Gesundheit von 1 (später auf 2 erhöht). Ja, das ist immer noch nicht genug, und bei der ersten starken Berührung mit der Falle wird er sterben, aber zumindest bei niedriger Geschwindigkeit besteht eine Überlebenschance (sogar mehrmals). Ich wollte das Original nicht ändern. "Aber was wird dem Spieler Schaden zufügen?" - Ich dachte und fand die Hauptfallen.
HauptfallenDie Basis meines Puzzles waren Fallen, aber sie widersprachen dem Namen des Spiels. Aus dem Namen folgt, dass es sich bei dem Spiel um Bälle handeln sollte, die unter dem Einfluss der Schwerkraft stehen. Aber es gab nicht so viele von ihnen. Stattdessen gab es mehr Standardrätsel.
Die Haupt- und die erste war eine Säge. Einfaches und klares Puzzle. Es wurde nicht sehr optimal geschrieben, während der Postproduktion habe ich es behoben.
Saw Scriptusing UnityEngine; public class Saw : GlobalFunctions { public AudioClip setClip; private TypePlaying typePlaying = TypePlaying.Sound; private AudioBase audioBase; private float speed = 4f; private Transform tr; private void Awake() { audioBase = GameObject.FindWithTag("MainCamera").GetComponent<AudioBase>(); tr = transform; } private void Update() { float s = Time.fixedDeltaTime / 0.03f * (Time.deltaTime / 0.03f); tr.localEulerAngles = new Vector3(0f, 0f, tr.localEulerAngles.z - speed * s); } private void OnCollisionEnter2D(Collision2D collision) { if (collision.collider.tag == "Player") { audioBase.SetSound(setClip, 1, 0.2f, typePlaying, false); } } public float GetSpeed() { return speed; } }
Als nächstes kam ein Laser, der alles sehr stark belastete. Wenn Sie 40 Teile auf die Bühne legen, wird das Spiel erheblich verzögert. Ich hatte aber auch den Wunsch, vollwertige physikalische Lichtgesetze hinzuzufügen, nämlich Reflexion oder sogar Brechung. Aber es war keine Zeit, ich habe es nicht beendet. Obwohl ich einige Dinge optimiert habe, hat es nicht viel geholfen.

Laserskript using UnityEngine; public class Laser : MonoBehaviour { public Vector2 vector2; public bool active = true; public GameObject laserActive; public LineRenderer lr1; public Transform tr; public BoxCollider2D bcl; public Damage dmg; private void Start() { lr1.startColor = lr1.endColor = LaserColor(); } public Color LaserColor() { Color c = new Color(0f, 0f, 0f, 1f); switch (dmg.GetTypeLaser().Type2int()) { case 1: c = new Color(1f, 0f, 0f, 1f); break; case 2: c = new Color(0f, 1f, 0f, 1f); break; case 3: c = new Color(0f, 0f, 0f, 0.4901961f); break; case 4: c = new Color(1f, 0.8823529f, 0f, 1f); break; case 5: c = new Color(0.6078432f, 0.8823529f, 0f, 1f); break; case 6: c = new Color(1f, 0.2745098f, 0f, 1f); break; } return c; } private void Update() { LaserUpdate(); } private void LaserUpdate() { if (active == true) { Vector2[] act1 = Points(tr.position, -tr.up); lr1.SetPosition(0, act1[0]); lr1.SetPosition(1, act1[1]); bcl.size = new Vector2(0.1f, 0.1f); bcl.offset = act1[2]; } return; } private Vector2[] Points(Vector2 start, Vector2 end) { Vector2[] ret = new Vector2[3]; RaycastHit2D hit = Physics2D.Raycast(tr.position, -tr.up, 200f); ret[0] = tr.position; ret[1] = hit.point; vector2 = ret[1]; float distance = Vector2.Distance(tr.position, hit.point); bcl.size = new Vector2(0.1f, 0.1f); if (hit.collider == bcl) { ret[2] = new Vector2(0f, 0.5f); } else { ret[2] = new Vector2(0f, -distance - 0.2f); } return ret; } }
Die Bombe war die letzte Falle, und bevor ich sie hinzufügte, habe ich das Schadenssystem neu geschrieben und insbesondere alles, was mit der Gesundheit des Spielers zu tun hat, in ein separates HealthBar-Skript übertragen (nützlich für andere Zwecke). Nachdem die Bombe noch aufgetaucht war und ihre Physik zu wünschen übrig ließ, wurde sie wieder fertiggestellt. Und am Ende stellte sich heraus, dass es wieder ziemlich würdig war.

Explosionsskript using System.Collections; using UnityEngine; public class Explosion : GlobalFunctions { public float power = 1f; public float radius = 5f; public float health = 20f; public float timeOffsetExplosion = 1f; public GameObject[] contacts = new GameObject[0]; public Animator expAnim; public bool writeContacs = true; public AudioClip setClip; private float timeOffsetExplosionCount; private float alphaTimer; private bool isTimerOn = false; private bool firstAPEvirtual = true; private Collider2D cl; private Rigidbody2D rb; private SpriteRenderer sr; private AudioBase audioBase; private Explosion explosion; private void Awake() { audioBase = GameObject.FindWithTag("MainCamera").GetComponent<AudioBase>(); cl = GetComponent<Collider2D>(); rb = GetComponent<Rigidbody2D>(); sr = GetComponent<SpriteRenderer>(); explosion = GetComponent<Explosion>(); } private void Start() { alphaTimer = sr.color.a; StartCoroutineTimerOffsetExplosion(); } private void OnCollisionEnter2D(Collision2D collision) { if (writeContacs == true) { int cont = contacts.Length; GameObject[] n = new GameObject[cont + 1]; if (cont != 0) { for (int i = 0; i < cont; i++) { n[i] = contacts[i]; } } n[cont] = collision.gameObject; contacts = n; } } private void OnCollisionExit2D(Collision2D collision) { if (writeContacs == true) { int cont = contacts.Length; if (cont != 1) { int counter = 0; GameObject[] n = new GameObject[cont - 1]; for (int i = 0; i < cont; i++) { if (contacts[i] != collision.gameObject) { n[counter] = contacts[i]; counter++; } } contacts = n; } else { contacts = new GameObject[0]; } } } public void ActionExplosionEmulation(GameObject obj) { float damage = 0f; if (obj.CompareTag("Laser")) { damage = obj.GetComponent<Damage>().senDamage; } else { damage = obj.GetComponent<Power>().power; } health = health - damage; StartCoroutineTimerOffsetExplosion(); return; } public void StartCoroutineTimerOffsetExplosion() { if (health <= 0f && isTimerOn == false) { isTimerOn = true; timeOffsetExplosionCount = timeOffsetExplosion; StartCoroutine(TimerOffsetExplosion(0.1f)); } } private IEnumerator TimerOffsetExplosion(float timeTick) { yield return new WaitForSeconds(timeTick); timeOffsetExplosionCount = timeOffsetExplosionCount - timeTick; if (timeOffsetExplosionCount > 0f) { float c = timeOffsetExplosionCount / timeOffsetExplosion; sr.color = new Color(1f, c, c, alphaTimer); StartCoroutine(TimerOffsetExplosion(timeTick)); } else { ExplosionAction(); } } private void ExplosionAction() { rb.gravityScale = 0f; rb.velocity = Vector2.zero; audioBase.SetSound(setClip, 2, 1f, TypePlaying.Sound, false); Destroy(cl); CircleCollider2D c = gameObject.AddComponent<CircleCollider2D>(); c.isTrigger = true; c.radius = radius; tag = "Explosion"; if (PlayerPrefs.GetString("graphicsquality") != "high") { Destroy(sr); StartCoroutine(Off()); } else { expAnim.enabled = true; StartCoroutine(Off2High()); } } public IEnumerator Off() { yield return new WaitForSecondsRealtime(0.1f); gameObject.SetActive(false); } public IEnumerator OffHigh(CircleCollider2D c) { yield return new WaitForSecondsRealtime(0.1f); c.enabled = false; } public IEnumerator Off2High() { yield return new WaitForSecondsRealtime(1.5f); gameObject.SetActive(false); } public void APEvirtual() { int cont = contacts.Length; if (cont != 0 && firstAPEvirtual == true) { firstAPEvirtual = false; for (int i = 0; i < cont; i++) { if (contacts[i] != null) { if (contacts[i].GetComponent<PhysicsEmulation>()) { contacts[i].GetComponent<PhysicsEmulation>().ExplosionPhysicsEmulation(explosion); } } } } } public void AnimFull() { sr.color = new Color(1f, 1f, 1f, 1f); sr.size = new Vector2(3f * radius, 3f * radius); return; } }
Nachdem ich mir das gesamte Schadenssystem angesehen hatte, beschloss ich, es gründlich umzuschreiben. Und dieses Mal hat Damage alle möglichen Schadensvariationen in einem Damage-Skript zusammengefasst und eine ähnliche ActionPhysicsEmulation-Methode für zerstörbare Blöcke erstellt (am Ende wurde für jede einzelne Schadensart eine eigene optimierte Methode geschrieben). Die Intensität des Schadens wurde auch durch die Intensität der "Stärke" des Objekts bestimmt (das Skript befand sich nur auf dem Spieler).
Und am Ende waren nur diese 3 Rätsel ein Schnitt über dem Original. Dies war jedoch kein Grund aufzuhören: Ich habe auch nicht vergessen, während der gesamten Entwicklung zu experimentieren. So schien es.
Kraftfeld (deaktiviert die Schwerkraft, verlangsamt sich und tötet langsam)

Skript VelocityField using UnityEngine; public class VelocityField : GlobalFunctions { public float percent = 10f; public float damage = 0.01f; public float heal = 0.01f; public GameObject[] contacts = new GameObject[0]; private HealthBar hb; private void Awake() { hb = GameObject.FindWithTag("MainCamera").GetComponent<Management>().healthBar; } private void FixedUpdate() { if (contacts.Length != 0) { for (int i = 0; i < contacts.Length; i++) { if (contacts[i] != null) { if (contacts[i].GetComponent<Rigidbody2D>()) { float s = Time.fixedDeltaTime / 0.03f; Vector2 vel = contacts[i].GetComponent<Rigidbody2D>().velocity; contacts[i].GetComponent<Rigidbody2D>().velocity = vel / 100f * (100f - percent * s); } } else { contacts = Remove(contacts, i); } } } } private void OnTriggerEnter2D(Collider2D collision) { if (collision.GetComponent<Rigidbody2D>()) { Rigidbody2D rb2 = collision.GetComponent<Rigidbody2D>(); if (rb2.isKinematic == false) { VelocityInput vi = collision.GetComponent<VelocityInput>(); vi.fields = Add(vi.fields, gameObject); rb2.gravityScale = 0f; rb2.freezeRotation = true; vi.inVelocityField = true; if (collision.GetComponent<Destroy>()) { collision.GetComponent<Destroy>().ActiveTimerDeleteChange(300f); } if (collision.tag == "Player") { hb.StartVFRad(damage); } contacts = Add(contacts, collision.gameObject); } } } public void OnTriggerExit2D(Collider2D collision) { if (collision.GetComponent<Rigidbody2D>()) { Rigidbody2D rb2 = collision.GetComponent<Rigidbody2D>(); if (rb2.isKinematic == false) { VelocityInput vi = collision.GetComponent<VelocityInput>(); vi.fields = Remove(vi.fields, gameObject); if (vi.fields.Length != 0) { rb2.gravityScale = 0f; rb2.freezeRotation = true; vi.inVelocityField = true; } else { rb2.gravityScale = 1f; rb2.freezeRotation = false; vi.inVelocityField = false; } if (collision.GetComponent<Destroy>()) { collision.GetComponent<Destroy>().ActiveTimerDeleteChange(60f); } if (collision.tag == "Player") { hb.EndVFRad(heal); } contacts = Remove(contacts, collision.gameObject); } } } }
Stomp (er hat die Spieler getötet und sie vernichtet)

Tramp-Skript using UnityEngine; public class TrampAnim : MonoBehaviour { public float speed = 0.1f; public float speedOffset = 0.01f; public float damage = 1f; private float sc; private float maxDis; public Vector3 start; public Vector3 end; public TrampAnim ender; public bool active = true; public bool trigPlayer = false; private AudioSet audioSet; private bool audioAct; private Transform tr; private HealthBar hb; public int count = 0; public void Start() { if (active) { tr = transform; maxDis = Vector2.Distance(start, end); sc = Vector2.Distance(tr.localPosition, start) / maxDis; hb = Camera.main.GetComponent<Management>().healthBar; audioAct = GetComponent<AudioSet>(); if (audioAct) { audioSet = GetComponent<AudioSet>(); } } } public void Update() { if (active) { float s = Time.fixedDeltaTime / 0.03f * (Time.deltaTime / 0.03f); if (count == 0) { tr.localPosition = Vector2.MoveTowards(tr.localPosition, end, (speed * sc + speedOffset) * s); if (tr.localPosition == end) { count = 1; if (trigPlayer && ender.trigPlayer) { hb.Damage(100f, tag, Vector2.zero); } if (audioAct) { audioSet.SetMusic(); } } } else { tr.localPosition = Vector2.MoveTowards(tr.localPosition, start, (speed * sc + speedOffset) * s); if (tr.localPosition == start) { count = 0; } } sc = Vector2.Distance(tr.localPosition, start) / maxDis; } } public void OnCollisionEnter2D(Collision2D collision) { Transform trans = collision.transform; string tag = trans.tag; if (tag == "Player") { trigPlayer = true; } else if (active == false) { if (trans.GetComponent<PhysicsEmulation>()) { trans.GetComponent<PhysicsEmulation>().TrampAnimPhysicsEmulation(GetComponent<TrampAnim>()); } } } public void OnCollisionExit2D(Collision2D collision) { string tag = collision.transform.tag; if (tag == "Player") { trigPlayer = false; } } }
Strahlung (die langsam die Gesundheit verringert)

Skriptstrahlung using System.Collections; using UnityEngine; public class Radiation : MonoBehaviour { public bool isActiveRadiation = false; private Management m; private HealthBar hb; private void Awake() { gameObject.SetActive(PlayerPrefs.GetString("ai") == "off"); m = GameObject.FindWithTag("MainCamera").GetComponent<Management>(); hb = m.healthBar; } private void Start() { StartCoroutine(RadiationDamage()); } public IEnumerator RadiationDamage() { yield return new WaitForSeconds(0.0002f); if (isActiveRadiation) { hb.StraightDamage(0.0002f, "Radiation"); } StartCoroutine(RadiationDamage()); } public void OnTriggerEnter2D(Collider2D collision) { if (collision.tag == "Player") { isActiveRadiation = true; hb.animator.SetBool("isVisible", true); } } public void OnTriggerExit2D(Collider2D collision) { if (collision.tag == "Player") { isActiveRadiation = false; hb.animator.SetBool("isVisible", false); if (hb.healthBarImage.fillAmount == 0f) { m.StartGraphics(); } } } public void OnCollisionEnter2D(Collision2D collision) { if (collision.transform.tag == "Player") { hb.animator.SetBool("isVisible", false); PlayerPrefs.SetString("ai", "on"); gameObject.SetActive(false); } } }
Falle (ein blauer Ball, der bei Berührung tötet, was auf das härteste Spiel der Welt hinweist)
Skript DeathlessScript using UnityEngine; public class DeathlessScript : MonoBehaviour { private HealthBar hb; private void Awake() { hb = Camera.main.GetComponent<Management>().healthBar; } public void OnTriggerEnter2D(Collider2D collision) { if (collision.tag == "Player") { hb.Damage(10f, tag, Vector2.zero); } } }
Ich habe nicht alle diese Arten von Schäden im Damage-Skript registriert, aber sie funktionierten im Allgemeinen gut mit Krücken. Danach kamen zusätzliche Mechaniker hinzu.
Zusätzliche MechanikSie wurden abwechslungsreich gemacht. Es gab einige von ihnen, so dass alle von Interesse waren und nicht genug, um mit den meisten Spielmechaniken zu interagieren.
Die erste solche Mechanik waren die Tore. Das allererste und funktionalste von allen. Auf jeden Fall nützlich an allen Orten, an denen funktionale Barrieren benötigt wurden. Es hat auch zusätzliche Funktionen: isActive zum Bestimmen des Startzustands und isState, um die Position nach der Aktivierung zu korrigieren (Namen sind verwechselt, aber als ich bemerkte, dass es zu spät war, um zu korrigieren).

Skript-Tor using UnityEngine; using System.Collections; public class Gate : MonoBehaviour { [Header("StartSet")] public Vector2 gateScale = new Vector2(1, 4); public float speed = 0.1f; public bool isReverse = false; public bool isEnd = true; public Vector2 animSetGateScale = new Vector2(); public Vector2 target = new Vector2(); [Header("SpriteEditor")] public Sprite mainSprite; [Header("Assets")] public GameObject door1; public GameObject door2; private IEnumerator fixUpdate; private void Start() { SpriteRenderer ds1 = door1.GetComponent<SpriteRenderer>(); SpriteRenderer ds2 = door2.GetComponent<SpriteRenderer>(); ds1.sprite = mainSprite; ds2.sprite = mainSprite; if (isReverse == false) { animSetGateScale = target = gateScale; } fixUpdate = FixUpdate(); SetGate(animSetGateScale); } private IEnumerator FixUpdate() { yield return new WaitForSeconds(0.03f); if (animSetGateScale != target) { float s = Time.fixedDeltaTime / 0.03f; animSetGateScale = Vector2.MoveTowards(animSetGateScale, target, speed * s); SetGate(animSetGateScale); StartCoroutine(FixUpdate()); } } private void SetGate(Vector2 scale) { SpriteRenderer ds1 = door1.GetComponent<SpriteRenderer>(); SpriteRenderer ds2 = door2.GetComponent<SpriteRenderer>(); Vector2 size = new Vector2(mainSprite.texture.width, mainSprite.texture.height); float k = size.x / size.y; ds1.size = new Vector2(gateScale.x, scale.y / 2f); ds2.size = new Vector2(gateScale.x, scale.y / 2f); BoxCollider2D d1 = door1.GetComponent<BoxCollider2D>(); BoxCollider2D d2 = door2.GetComponent<BoxCollider2D>(); d1.size = new Vector2(gateScale.x, scale.y / 2f); d2.size = new Vector2(gateScale.x, scale.y / 2f); door1.transform.localScale = new Vector3(1f, 1f, 1f); door2.transform.localScale = new Vector3(1f, 1f, 1f); door1.transform.localPosition = new Vector3(0f, (gateScale.y / 2f) - (scale.y / 4f), 0f); door2.transform.localPosition = new Vector3(0f, -(gateScale.y / 2f) + (scale.y / 4f), 0f); } public void OnTriggerEnter2D(Collider2D collision) { if (collision.CompareTag("Player")) { if (isReverse == false) { target = Vector2.zero; } else { target = gateScale; } StopCoroutine(fixUpdate); fixUpdate = FixUpdate(); StartCoroutine(fixUpdate); } } private void OnTriggerExit2D(Collider2D collision) { if (collision.CompareTag("Player") && isEnd == true) { if (isReverse == false) { target = gateScale; } else { target = Vector2.zero; } StopCoroutine(fixUpdate); fixUpdate = FixUpdate(); StartCoroutine(fixUpdate); } } }
Eine ähnliche Funktionalität besaßen physische Objekte. Nein, dies sind keine Objekte aus der Zerstörung, sondern nur physische Objekte (obwohl sie auch zerstört werden könnten, aber diese Mechanik nicht verwendeten). Es gibt nicht so viele von ihnen in Rätseln, aber sie lassen sich gut mit anderen Mechaniken kombinieren. Zum Beispiel mit einem Tor: Wenn ein Objekt einen Torauslöser berührt, öffnet sich das Tor.
Seit ich gelernt habe, „Kraft zu besitzen“, haben bis zu drei Mechaniker sie kontrolliert. Dies waren Trigger mit demselben Code für die Interaktion mit Objekten, aber jeder führte Aufgaben auf seine eigene Weise aus. Das erste war ein Kraftfeld (es verlangsamte das Objekt und multiplizierte die Kraft mit einem bestimmten Faktor). Die zweite zusätzliche Stärke in Richtung des Punktes und des Punktes hatte "Schwerkraft". Das dritte wurde aus Versehen erstellt: Als das Rätsel um die Schwerelosigkeit nicht funktionierte, wurde es von diesem Skript gespeichert. Darin ändert das Objekt die Richtung der Kraft, ohne sie selbst, ihre Intensität zu ändern.

Wie funktioniert es?Zunächst wird nach dem Satz von Pythagoras die Hypotenuse berechnet, die der Koeffizient des Vektors ist und zur Wiederherstellung der Stärke nützlich ist. Der Winkel wird dann mit der Atan2-Funktion berechnet. Danach wird der Ecke offsetAngle hinzugefügt und ein neuer Vektor basierend auf Sinus und Cosinus erstellt, der mit einem Koeffizienten multipliziert wird und eine geänderte Richtung ohne geänderte Kraft erhält.
public Vector2 RotateVector(Vector2 a, float offsetAngle) { float power = Mathf.Sqrt(ax * ax + ay * ay); float angle = Mathf.Atan2(ay, ax) * Mathf.Rad2Deg - 90f + offsetAngle; return Quaternion.Euler(0, 0, angle) * Vector2.up * power; }
Daraufhin versiegte meine ganze Fantasie von Extras. Ja, es gab Ideen wie eine Bombe an einem Seil, einer Seilbahn usw. Aber dann kam die normale Idee: Sie müssen das Spiel erneut rendern. Trotzdem bin ich ehrlich zu mir selbst: Die überwiegende Mehrheit der Menschen spielt Handyspiele, und kaum einer von ihnen wird mein Spiel spielen, wenn das Spiel unerträglich kompliziert ist. Ich habe beschlossen, mit Rätseln zu beginnen, die den Spieler mit einem Treffer getötet haben, aber ich wollte den Schaden wegen der Zerstörbarkeit nicht ändern. Und dann kam die Idee einer normalen zusätzlichen Mechanik: Booster oder Modifikatoren.
Nach dem Konzept gaben sie vorübergehende Verbesserungen, die mit einigen Grundwerten verbunden sind. Es gab 5 Booster: Behandlung, Unsterblichkeit, Zeitdilatation (Slow Mo), Änderung der Schwerkraft und Änderung der Masse des Spielers.
Aber es schien eine Art Standard zu sein: Triggerbälle, die über das Level verstreut sind, um das Passieren zu erleichtern. Und so habe ich diese Booster zum Laser hinzugefügt. Die Mechanik wurde ein wenig geändert und es hat funktioniert.
Jetzt hat der Laser 5 Interaktionsmodi mit dem Spieler: Schaden und Heilung, Unsterblichkeit, Zeitdilatation (Zeitlupe), Änderung der Schwerkraft und Änderung der Masse des Spielers. Das ist das gleiche, aber mit einem Unterschied: Der Laser wirkt ständig auf den Player und wenn Sie den Laser verlassen, verschwindet der Effekt sofort (oder nach einer Weile). Ja, Booster haben fast das gleiche, aber Laser sind nicht Standard (und damit das ganze Spiel).
Das physische Thema des Spiels ermöglichte es, ein Trampolin zu erstellen, das normalerweise verwendet wird, um den Spieler mit der anschließenden Zerstörung der Wand zu zerstreuen (obwohl dies ein einfacher BoxCollider2D mit PhysicsMaterial ist, bei dem der Sprungparameter für verschiedene Sprungkräfte verdreht wurde).
Und die Sandigkeit des Spiels ermöglichte es Ihnen, Ihre eigenen Skripte für die Animation zu erstellen. Grundsätzlich haben sie das Objekt von Punkt zu Punkt bewegt oder das Objekt gedreht. Zuvor hatten sie viel mehr Funktionen: die Fähigkeit, ein Objekt zu animieren (nach Punkten), die Skalierung (nach Punkten) zu ändern, genauere Beschriftungen für den Anfang und das Ende einer Animation eines Objekts usw. Aber aufgrund der Tatsache, dass es sich um Atavismen handelte, die insgesamt die Produktivität in Anspruch nahmen, musste ich sie im Namen der Optimierung herausschneiden. Das Animationsskript wird überall dort verwendet, wo Sie eine einfache Animation zeigen müssen, denn wie gesagt: "Dem Original fehlten sehr viele Animationen!" Es gibt nur zwei Skripte:
BasicAnimation und PointsAnimation.
BasicAnimation Script using UnityEngine; using System.Collections; public class BasicAnimation : GlobalFunctions { public AnimationType animationType = AnimationType.Infinity; public float speedSpeed = 0.05f; public float rotation = 0f; private bool make = true; private bool animMake = false; private bool isMoved = false; private Transform tr; private float rotationActive = 0f; public void SetPos(bool pos, float m) { rotationActive = rotation * (pos ? 1 : m); } private void Start() { tr = transform; animMake = false; switch (animationType) { case AnimationType.Infinity: make = true; isMoved = true; rotationActive = rotation; break; case AnimationType.Start: make = false; isMoved = false; break; case AnimationType.End: make = true; isMoved = true; rotationActive = rotation; break; case AnimationType.All: make = false; isMoved = false; break; } } public void TimerAnim(float timer, bool anim) { StartAnim(anim); StartCoroutine(TimerTimerAnim(timer, anim)); } private IEnumerator TimerTimerAnim(float timer, bool anim) { yield return new WaitForSeconds(timer); EndAnim(anim); } public void StartAnim(bool anim) { make = true; if (anim == true) { animMake = true; isMoved = true; } else { rotationActive = rotation; } } public void EndAnim(bool anim) { if (anim == true) { animMake = true; isMoved = false; } else { make = false; rotationActive = 0f; } } private void FixedUpdate() { if (animMake == true) { if (isMoved == true) { if (rotationActive != rotation) { rotationActive = Mathf.MoveTowards(rotationActive, rotation, speedSpeed); } else { animMake = false; isMoved = false; } } else { if (rotationActive != 0f) { rotationActive = Mathf.MoveTowards(rotationActive, 0f, speedSpeed); } else { animMake = false; isMoved = true; } } } } private void Update() { if (make == true) { float rot = tr.localEulerAngles.z; float s = Time.fixedDeltaTime / 0.03f * (Time.deltaTime / 0.03f); tr.localEulerAngles = new Vector3(0f, 0f, rot + rotationActive * s); } } }
PointsAnimation Script using UnityEngine; using System.Collections; public class PointsAnimation : GlobalFunctions { public AnimationType animationType = AnimationType.Infinity; public float speedSpeedPosition = 0.001f; public float speedPosition = 0.1f; public Vector3[] pointsPosition = new Vector3[0]; public int counterPosition = 0; private float speedPositionActive = 0f; private int pointsPositionLength = 0; private bool make = true; private bool animMake = false; private bool isMoved = false; private Transform tr; public void SetPos(bool pos, float m) { speedPositionActive = speedPosition * (pos ? 1 : m); } private void Awake() { pointsPositionLength = pointsPosition.Length; tr = transform; switch (animationType) { case AnimationType.Infinity: make = true; isMoved = true; speedPositionActive = speedPosition; break; case AnimationType.Start: make = false; isMoved = false; break; case AnimationType.End: make = true; isMoved = true; speedPositionActive = speedPosition; break; case AnimationType.All: make = false; isMoved = false; break; } } public void TimerAnim(float timer, bool anim) { StartAnim(anim); StartCoroutine(TimerTimerAnim(timer, anim)); } private IEnumerator TimerTimerAnim(float timer, bool anim) { yield return new WaitForSeconds(timer); EndAnim(anim); } public void StartAnim(bool anim) { make = true; if (anim == true) { animMake = true; isMoved = true; } else { speedPositionActive = speedPosition; } } public void EndAnim(bool anim) { if (anim == true) { animMake = true; isMoved = false; } else { make = false; speedPositionActive = 0f; } } private void FixedUpdate() { if (animMake == true) { if (isMoved == true) { if (speedPositionActive != speedPosition) { Vector2 ends = new Vector2(-speedPosition, speedPosition); speedPositionActive = Mathf.MoveTowards(speedPositionActive, speedPosition, speedSpeedPosition); } else { animMake = false; isMoved = false; } } else { if (speedPositionActive != 0f) { Vector2 ends = new Vector2(-speedPosition, speedPosition); speedPositionActive = Mathf.MoveTowards(speedPositionActive, 0f, speedSpeedPosition); } else { animMake = false; isMoved = true; } } } } private void Update() { if (make) { if (tr.localPosition == pointsPosition[counterPosition]) { counterPosition++; if (counterPosition == pointsPositionLength) { counterPosition = 0; } } else { float s = Time.fixedDeltaTime / 0.03f * (Time.deltaTime / 0.03f); tr.localPosition = Vector3.MoveTowards(tr.localPosition, pointsPosition[counterPosition], speedPositionActive * s); } } } }
BenutzeroberflächeIm Vergleich zum Original ist dies ein echtes Meisterwerk.
Zum Vergleich hier das Original:
Hier ist die Fortsetzung:
Hier ist das Original:
Hier ist die Fortsetzung:
Hier orig ... ich denke es ist klar. Minimalismus in der Fortsetzung, an die ich mich erinnert habe, und anstelle der unangemessen gefärbten Pause-Taste und des offen störenden Timers gibt es jetzt eine lokale, irgendwie wahrnehmbare Pause-Taste in der unteren linken Ecke. Die Fortsetzung gewinnt immer noch das Menü. Im Gegensatz zum Original gibt es überall Animationen und der Hintergrund sind 11 Shader, die ich versehentlich in das Shader-Diagramm geschrieben habe. Die Funktionalität wird auch immer besser, es gibt eine Grafikeinstellung, separate Sound- und Musikeinstellungen, eine Konsole, mit der Sie das Speichern ändern können - davon gibt es im Originalmenü nichts.
Es ist so gut geworden, weil ich mich für andere Spiele entschieden habe. Hier und da habe ich im Allgemeinen das Beste von überall genommen (eher gestohlen). Und hier ist, was ich besonders genommen habe:
- Menü abspielen
Aus Altos Abenteuer entnommen, wurden nur Erfahrungen zu Spott, Witzen, ironischen Kommentaren usw. - Pause
Auch von Alto, aber nicht so funktional, aber es passt bequemer und spielt bequemer. - Einstellungen
Teilweise aus Vektor 2 übernommen, nämlich die Form des Menüs und die Lautstärkeregler.
Er nahm im Allgemeinen ein wenig, tat aber sonst alles alleine.
KonsoleMachen Sie zunächst einen Vorbehalt darüber, wie die Erhaltung funktioniert. Es gibt zwei Variablen, die für die globale und lokale Erhaltung verantwortlich sind: Dies sind die Zahlen Fortschritt und Aufzugsspar. Die Fortschrittsvariable ist für das Speichern zwischen Szenen verantwortlich, und die Aufzugs-Speichervariable ist für das Speichern innerhalb der Szene verantwortlich. Wenn Sie die Taste "Start" oder "Neustart" drücken, überträgt das Spiel den Fortschritt auf die Szene und erzeugt den Spieler beim Speichern unter der Aufzugs-Speichernummer.
Über die Konsole können Sie beliebige Variablen ändern oder erstellen. Ein so einfaches und leistungsstarkes Tool war für mich sehr nützlich, um das Spiel zu testen und Fehler darin zu identifizieren. Die Konsole selbst ist ein handgeschriebener Befehl, der andere Konsolen nachahmt.
Skript DebugConsole using UnityEngine; using UnityEngine.UI; using UnityEngine.SceneManagement; using System.Collections; public class DebugConsole : MonoBehaviour { public Animator animatorBlackScreen; public Language l; public InputField inputField; public Text textDebug; private bool access = false; public void AnalyzeText() { string txt = inputField.text.ToLower(); string[] output = new string[0]; string txtLoc = ""; for (int i = 0; i < txt.Length; i++) { if (txt[i] == ' ') { if (txtLoc != "") { output = Add(output, txtLoc); txtLoc = ""; } } else { txtLoc = txtLoc + txt[i]; } } if (txtLoc != "") { output = Add(output, txtLoc); txtLoc = ""; } Analyze(output); } public void Analyze(string[] commands) { switch (commands[0]) { case "playerprefs": if (access == true) { if (commands.Length < 2) { Log(l.ConsoleLanguage(1));//1 } else { switch (commands[1]) { case "f": case "float": float f = 0f; if (float.TryParse(commands[3], out f)) { PlayerPrefs.SetFloat(commands[2], float.Parse(commands[3])); Log(l.ConsoleLanguage(2, commands[2]));//2 } else { Log(l.ConsoleLanguage(3));//3 } break; case "i": case "int": int i = 0; if (int.TryParse(commands[3], out i)) { PlayerPrefs.SetInt(commands[2], int.Parse(commands[3])); Log(l.ConsoleLanguage(4, commands[2]));//4 } else { Log(l.ConsoleLanguage(5));//5 } break; case "s": case "string": PlayerPrefs.SetString(commands[2], commands[3]); Log(l.ConsoleLanguage(6, commands[2]));//6 break; case "clear": PlayerPrefs.DeleteAll(); SceneManager.LoadScene(0); break; default: Log(l.ConsoleLanguage(7, commands[1]));//7 break; } } } else { Log(l.ConsoleLanguage(8));//8 } break; case "next": if (access == true) { if (commands.Length > 1) { switch (commands[1]) { case "level": int p = PlayerPrefs.GetInt("progress"); PlayerPrefs.SetInt("progress", p + 1); Log("ok level"); break; case "save": int s = PlayerPrefs.GetInt("elevatorsave"); PlayerPrefs.SetInt("elevatorsave", s + 1); Log("ok save"); break; case "start": PlayerPrefs.SetInt("elevatorsave", 0); Log("ok start"); break; case "end": PlayerPrefs.SetInt("elevatorsave", 1); Log("ok end"); break; } } } else { Log(l.ConsoleLanguage(8));//8 } break; case "echo": if (commands.Length == 1) { Log(l.ConsoleLanguage(9));//9 } else { switch (commands[1]) { case "vertogpro"://echo vertogpro access = true; Log(l.ConsoleLanguage(10));//10 break; default: Log(l.ConsoleLanguage(11));//11 break; } } break; case "restart": if (access == true) { SceneManager.LoadScene(0); } else { Log(l.ConsoleLanguage(12));//12 } break; case "authors": Log(l.ConsoleLanguage(13));//13 break; case "discharge": animatorBlackScreen.SetBool("isActive", true); PlayerPrefs.SetString("start", "key"); PlayerPrefs.SetString("language", "nothing"); PlayerPrefs.SetString("graphicsquality", "medium"); PlayerPrefs.SetFloat("sound", 0.5f); PlayerPrefs.SetFloat("music", 0.5f); PlayerPrefs.SetFloat("rotatenextlevel", 0f); PlayerPrefs.SetInt("elevatorsave", 0); PlayerPrefs.SetInt("progress", 1); PlayerPrefs.SetInt("deaths", 0); PlayerPrefs.SetInt("discharge", PlayerPrefs.GetInt("discharge") + 1); PlayerPrefs.SetInt("lastmenueffect", -1); PlayerPrefs.SetString("isshotmode", "false"); PlayerPrefs.SetString("boss1", "life"); PlayerPrefs.SetString("boss2", "life"); PlayerPrefs.SetString("ai", "off"); PlayerPrefs.SetString("boss3", "life"); PlayerPrefs.SetString("end", "none"); StartCoroutine(StartGame()); break; case "clear": Clear(); break; case "info": if (access == false) { Log(l.ConsoleLanguage(14));//14 } else { Log(l.ConsoleLanguage(15));//15 } break; default: Log(l.ConsoleLanguage(16, commands[0]));//16 break; } } public void Log(object message) { textDebug.text = message.ToString(); } public void Clear() { inputField.text = ""; textDebug.text = ""; } public string[] Add(string[] old, string addComponent) { string[] n = new string[old.Length + 1]; if (old.Length != 0) { for (int i = 0; i < old.Length; i++) { n[i] = old[i]; } } n[old.Length] = addComponent; return n; } public IEnumerator StartGame() { yield return new WaitForSeconds(1f); SceneManager.LoadSceneAsync(0); } }
Und speziell für Sie werde ich eine Liste der flüssigen Teams darin hinterlassen:- Entladung - Setzt den Spielfortschritt zurück (und alle anderen Informationen auch)
- echo vertogpro - ein Team für den Zugriff auf Entwicklungsteams
- playerprefs [Typ angegeben (Zeichenfolge, int, float)] [Variablenname] [Daten] - ändert oder erstellt eine beliebige Variable. Beispiel: playerprefs int progress 14
- next - ein Subtyp für eine vereinfachte Ebenennavigation mit eigenen Befehlen:
- start - speichert am Anfang des Levels (nächster Start)
- Ende - speichert am Ende des Levels (nächstes Ende)
- Speichern - Teleportiert zum nächsten Speichern (nächster Speicher)
- Level - teleportiert sich zum nächsten Level (nächstes Level)
GrafikFür das Jahr habe ich nicht gelernt, wie man zeichnet, also habe ich fast das Gleiche getan wie im Original: Ich habe ungefähr 30 Texturpakete für Maycraft heruntergeladen, jeweils das Beste ausgewählt und so stellte sich heraus, dass die Hauptgrafiken. Die Grafiken unterschieden sich nicht wesentlich vom Original und machten mich wütend, machten mich so wütend, dass ich immer noch verschiedene animierte Effekte (Explosionen, Feuer usw.) fand und verschiedene Texturpakete aus dem Asset Store aufpumpte. Selbst für ein Handyspiel sind die Grafiken ziemlich schlecht, obwohl noch Fortschritte zu verzeichnen sind. Hier ist das Original:Und hier ist die Fortsetzung:SpeichernWenn das Prinzip des Speicherns einfach ist, ist ihre Implementierung nicht sehr. Das Speichersystem besteht aus 3 Skripten:- ElevatorBase ist die Grundlage, auf der Startup-Teams auftreten. Darin wird durch die Elevatorsave-Variable das aktive Speichern aus dem Speicherarray ausgewählt.
Skript ElevatorBase using UnityEngine; using System.Collections; public class ElevatorBase : MonoBehaviour { public GameObject[] savers = new GameObject[0]; public float inputStartBlock = 1f; private GameUI gameUI; public void Awake() { int l = savers.Length; if (l != 0) { for (int i = 0; i < l; i++) { if (savers[i] != null) { if (savers[i].GetComponent<Saving>()) { Saving saving = savers[i].GetComponent<Saving>(); saving.isFirst = false; saving.idElevatorBase = i; } else if (savers[i].GetComponent<Elevator>()) { savers[i].GetComponent<Elevator>().isFirst = false; } } } int es = PlayerPrefs.GetInt("elevatorsave"); if (savers[es] != null) { if (savers[es].GetComponent<Saving>()) { savers[es].GetComponent<Saving>().isFirst = true; } else if (savers[es].GetComponent<Elevator>()) { savers[es].GetComponent<Elevator>().isFirst = true; } } else { gameUI = GameObject.FindWithTag("Canvas").GetComponent<GameUI>(); StartCoroutine(BlockEnabled()); GameObject.Find("TipsInput").GetComponent<TipsGamePlayInput>().active = true; } } else { gameUI = GameObject.FindWithTag("Canvas").GetComponent<GameUI>(); gameUI.ChangeisBlocked(); } } public IEnumerator BlockEnabled() { yield return new WaitForSeconds(inputStartBlock); GameObject block = gameUI.block.gameObject; block.SetActive(false); } }
- Saving — , , , elevatorsave id.
Saving using System.Collections; using UnityEngine; public class Saving : MonoBehaviour { public Saving[] savings; public Vector2 startPos; public float startRot; public bool isActive = true; public bool isFirst = true; public int idElevatorBase = 0; public TipsGamePlayInput tgpi; private GameObject player; private GameObject cam; private Transform trp; private GameUI gameui; private Management m; private Saving self; private void Start() { self = GetComponent<Saving>(); cam = GameObject.FindWithTag("MainCamera"); m = cam.GetComponent<Management>(); gameui = GameObject.FindWithTag("Canvas").GetComponent<GameUI>(); player = m.player; trp = player.GetComponent<Transform>(); if (isFirst) { trp.position = startPos; m.Set(startRot); OfferSaves(); } isActive = !isFirst; tgpi.SetActive(!isFirst); StartCoroutine(BlockFalse()); } public IEnumerator BlockFalse() { yield return new WaitForSeconds(1f); gameui.block.gameObject.SetActive(false); } private void OnTriggerEnter2D(Collider2D collision) { if (collision.CompareTag("Player") && isActive == true) { isActive = false; PlayerPrefs.SetInt("elevatorsave", idElevatorBase); OfferSaves(); } } public void OfferSaves() { if (savings.Length != 0) { for (int i = 0; i < savings.Length; i++) { savings[i].isActive = false; savings[i].tgpi.SetActive(false); } } } }
- Elevator — , . : ( ).
Elevator using System.Collections; using UnityEngine; public class Elevator : GlobalFunctions { public Vector2 endPos; public Vector2 startPos; public int nextScene = 1; public int nextElevatorSave = 0; public float speed = 0.1f; public bool isFirst = true; public bool isActive = true; public bool isReverse = false; public bool isMake = false; private GameObject player; private Rigidbody2D rb; private Transform tr; private Transform trp; private GameUI gameui; private AudioBase audioBase; private Transform cam; private void Start() { audioBase = GameObject.FindWithTag("MainCamera").GetComponent<AudioBase>(); gameui = GameObject.FindWithTag("Canvas").GetComponent<GameUI>(); player = gameui.m.player; rb = player.GetComponent<Rigidbody2D>(); trp = player.GetComponent<Transform>(); tr = GetComponent<Transform>(); cam = gameui.m.transform; startPos = tr.position; if (isFirst) { trp.position = startPos; rb.velocity = new Vector2(); rb.gravityScale = 0f; gameui.m.Set(); } else { tr.position = endPos; isMake = true; } isActive = isFirst; isReverse = false; } private void OnTriggerEnter2D(Collider2D collision) { if (collision.CompareTag("Player") && isMake == true) { isReverse = true; isActive = true; rb.velocity = new Vector2(); rb.gravityScale = 0f; gameui.block.gameObject.SetActive(true); PlayerPrefs.SetInt("elevatorsave", nextElevatorSave); gameui.animatorBlackScreenGame.SetBool("isActive", true); audioBase.LowerSound(0.05f, 16, 0, TypePlaying.Music); StartCoroutine(NumSaveRotate()); StartCoroutine(gameui.StartGame(1.5f, nextScene)); } } private IEnumerator NumSaveRotate() { yield return new WaitForSeconds(1.5f); PlayerPrefs.SetFloat("rotatenextlevel", Stable(cam.localEulerAngles.z, -180f, 180f)); } private void FixedUpdate() { if (isActive == true) { float s = Time.fixedDeltaTime / 0.03f; if (isReverse == false) { rb.velocity = new Vector2(); tr.position = Vector2.MoveTowards(tr.position, endPos, speed * s); trp.position = tr.position; if ((Vector2)tr.position == endPos) { isMake = true; isActive = false; rb.gravityScale = 1f; gameui.block.gameObject.SetActive(false); } } else if (isReverse == true) { tr.position = Vector2.MoveTowards(tr.position, startPos, speed * s); trp.position = tr.position; if (tr.position == (Vector3)startPos) { isActive = false; rb.gravityScale = 1f; } } } } }
SpieldesignEs war ein echtes Durcheinander. Es war das Spieldesign, das den Entwicklungszyklus von 4 auf 6 Monate verlängerte. Insgesamt hat das Spiel 34 Level: 30 reguläre, 3 Bosse und 1 Finale (Level). Jedes normale habe ich 2-3 Tage gemacht, jeder Boss 2 Wochen und das letzte Level hat eine Woche gemacht. Um alles auszugleichen, habe ich sie so gebaut: 10 Level => 1 Boss => 10 Level => 2 Boss => 10 Level => 3 Boss => Endlevel.Die lokalen Ebenen sind mein Stolz. Sie sind ungewöhnlich, abwechslungsreich und sogar ein wenig interessant. Die Ebenen sind in einer bestimmten Form gestaltet, um ein Gefühl für die offene Welt zu schaffen. Dafür habe ich sogar eine Karte gezeichnet:Die Karte ist nicht die beste Zeichnung und Information, aber sie gab wichtige Informationen für die notwendigen Formen von Ebenen. Ursprünglich war geplant, alle Ebenen auf der Karte zu erstellen, aber ich habe nicht die Ebenen ausgeführt, die abgedunkelt waren. Dies ist übrigens eine Karte mit einer Größe von 1000 x 1000 Pixel, und aus dieser Karte ging der Maßstab hervor: 1 Block = 1 Pixel = Spielergröße.Zwischen den Levels geht der Spieler durch den Aufzug. Es kann auf jede Ebene liefern, und daher ist es möglich, zwischen den Ebenen zu reisen, wodurch ein Spieler mit einem noch größeren Gefühl für die Offenheit der Welt geschaffen wird. Außerdem sind an einigen Stellen Auslöser verborgen, um geheime Aufzüge zu aktivieren, die 10 bis 15 Level weiterführen können.Für gewöhnliche Niveaus gab es einen Konstruktionsalgorithmus:- Ein Hintergrund, der eine Form und einen Maßstab wie auf einer Karte haben würde
- Außenwände (dreifache Dicke aufgrund spezieller Physik)
- Wände sind intern
- Levels selbst
- Aufzüge, Speicher und Audio-Trigger
Bei Chefs ist es komplizierter, weil jeder Chef gleichzeitig unterschiedliche und ähnliche Verhaltensmuster präsentiert. Alle Bosse haben 100 Gesundheit und jedes Level hat etwas zu zerstören. Es ist besser, über jeden einzeln zu sprechen:1 Chef verhält sich sehr einfach: Er bewegt sich zufällig im Raum, wartet 5 Sekunden und wiederholt. Um ehrlich zu sein, ist dies ein schlechtes Beispiel für einen Chef: einfach, verzögert und nicht einprägsam. Und er kann nur getötet werden, wenn man um ihn schlägt. Aber es gibt eine Verteidigung in Form von 4 Sägen: 3 von ihnen bewegen sich geschickt zufällig im Raum und eine schützt den Boss, wenn er sich bewegt. Nach dem Tod explodiert es.Skript BossManagement1 using UnityEngine; using System.Collections; public class BossManagement1 : GlobalFunctions { public float hp = 100f; public float speed = 0.2f; public bool startActivated = false; public bool activated = false; public bool activatedSaw = false; public bool activatedAngle = false; public bool activatedCoroutine = true; private bool active; private float maxhp; public Vector2 target; public Vector2 targetSaw1; public Vector2 targetSaw2; public Vector2 minBorder; public Vector2 maxBorder; public DeadBoss1 deadBoss; public GameObject backGround; public GameObject healthBar; public Transform tr; public Transform sawMain; public Transform saw1; public Transform saw2; public Arrow arrow; public AudioSet setStart; public AudioSet setEnd; public Transform player; public Power playerPower; private Transform bg, hb; private float targethp = 0f; private Vector2 startMove = new Vector2(-20f, 0f); public void Awake() { maxhp = hp; bg = backGround.transform; hb = healthBar.transform; } public void Start() { if (PlayerPrefs.GetString("boss1") == "death") { Dead(false); } } public void FixedUpdate() { if (startActivated && !activatedCoroutine) { if ((Vector2)tr.position != startMove) { tr.position = Vector2.MoveTowards(tr.position, startMove, speed); saw1.position = Vector2.MoveTowards(saw1.position, startMove, speed); saw2.position = Vector2.MoveTowards(saw2.position, startMove, speed); } else { activatedCoroutine = true; startActivated = false; StartCoroutine(ActivatedOn()); } } if (activated) { if ((Vector2)tr.position != target) { tr.position = Vector2.MoveTowards(tr.position, target, speed); } else { activated = false; sawMain.localScale = new Vector2(0f, 0f); StartCoroutine(TargetRotate()); } } if (activatedSaw) { if ((Vector2)saw1.position != targetSaw1) { saw1.position = Vector2.MoveTowards(saw1.position, targetSaw1, speed); } else { float x = Random.Range(minBorder.x, maxBorder.x); float y = Random.Range(minBorder.y, maxBorder.y); targetSaw1 = new Vector2(x, y); } if ((Vector2)saw2.position != targetSaw2) { saw2.position = Vector2.MoveTowards(saw2.position, targetSaw2, speed); } else { float x = Random.Range(minBorder.x, maxBorder.x); float y = Random.Range(minBorder.y, maxBorder.y); targetSaw2 = new Vector2(x, y); } } if (activatedAngle) { Vector2 dir = player.position - tr.position; float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg; tr.localEulerAngles = new Vector3(0f, 0f, Mathf.LerpAngle(tr.localEulerAngles.z, angle, 0.1f)); } } public IEnumerator TargetRotate() { yield return new WaitForSeconds(3f + 3f * hp / maxhp); sawMain.localScale = new Vector2(6f, 6f); float x = Random.Range(minBorder.x, maxBorder.x); float y = Random.Range(minBorder.y, maxBorder.y); target = new Vector2(x, y); activated = true; } public IEnumerator ActivatedOn() { yield return new WaitForSeconds(3f); sawMain.localScale = new Vector2(6f, 6f); target = new Vector2(Random.Range(minBorder.x, maxBorder.x), Random.Range(minBorder.y, maxBorder.y)); targetSaw1 = new Vector2(Random.Range(minBorder.x, maxBorder.x), Random.Range(minBorder.y, maxBorder.y)); targetSaw2 = new Vector2(Random.Range(minBorder.x, maxBorder.x), Random.Range(minBorder.y, maxBorder.y)); activatedSaw = true; activated = true; arrow.isActive = true; } public IEnumerator ActivatedCoroutineOff() { yield return new WaitForSeconds(1f); activatedCoroutine = false; activatedAngle = true; } public void Update() { if (active == true) { if (hp != targethp) { float s = Time.fixedDeltaTime / 0.03f * (Time.deltaTime / 0.03f); hp = MoveToward(hp, targethp, speed * s, new Vector2(-0f, maxhp)); } else { active = false; if (targethp == 0f) { Dead(true); } } } UpdateHP(); } public void UpdateHP() { float h = hp / maxhp; bg.localScale = new Vector3(5f, 0.9f, 1f); hb.localScale = new Vector3(4.8f * h, 0.7f, 1f); hb.localPosition = new Vector3(-2.4f + 4.8f * h / 2f, 0f, 0f); } private bool oneTimeMusic = true; public void Damage(float damage) { if (oneTimeMusic == true) { oneTimeMusic = false; deadBoss.StartBoss(); deadBoss.Boom(); setStart.SetMusic(); startActivated = true; StartCoroutine(ActivatedCoroutineOff()); } if (hp != 0f) { targethp = Stable2(hp - damage, 0f, maxhp); speed = speed + damage * 0.02f; active = true; } } public void Dead(bool boom) { active = false; activated = false; activatedSaw = false; startActivated = false; activatedAngle = false; activatedCoroutine = false; backGround.SetActive(false); healthBar.SetActive(false); sawMain.gameObject.SetActive(false); saw1.gameObject.SetActive(false); saw2.gameObject.SetActive(false); setEnd.SetMusic(); arrow.obj.SetActive(false); PlayerPrefs.SetString("boss1", "death"); deadBoss.Dead(tr.position, boom); } public void OnCollisionEnter2D(Collision2D collision) { if (collision.transform.CompareTag("Player")) { Damage(playerPower.power); } } }
2 Chef ist schon besser in der Qualität, aber immer noch alles andere als ideal. Sein Muster ist komplizierter: Er bestimmt den Standort des Spielers und nach dem Bereich, in dem er sich befindet. Nachdem der Boss einen zufälligen Punkt im Bereich ausgewählt und dorthin gezogen hat. Seine Verteidigung ist bereits bedeutungsvoller: Die Gesundheit des Chefs hat Stufen und verschiedene Waffen für jede Stufe:- 2 Sägen in der Ferne
- 2 Sägen in einiger Entfernung, wenn durch eine Säge geschützt
- 2 laserbegrenzt, während der Bewegung durch eine Säge geschützt
- 2 Laser, wenn durch eine Säge geschützt
- 2 Laser, bei Bewegung geschützt durch eine Säge und 2 Sägen in einiger Entfernung
Im grafischen Teil ist der zweite Boss besser als der erste: Inaktivitätszeit in Form der Wiederherstellung von Ausdauer und Aktivitäten von Drittanbietern in Form der Deaktivierung von Bosslasern, wenn Trigger in der Mitte des Raums aktiviert werden.Skript BossManagement2 using System.Collections; using UnityEngine; public class BossManagement2 : GlobalFunctions { public float hp = 100f; public float speed = 0.5f; public float speedRotate = 0.5f; public int stage = 1; public bool isAlive = true; public bool isActivated = false; public bool isMove = false; public bool isWorkingLaser = true; private float timeStamina = 0f; private float timeRetarget = 0f; public Vector2 region = Vector2.zero; public Vector3 target = Vector3.zero; public GameObject player; public Transform saw; public Transform laser1; public Transform laser2; public Laser laserL1; public Laser laserL2; public Transform laserOffset1; public Transform laserOffset2; public Explosion explosion; public GameObject explosionAsset; public CircleCollider2D trigStart; public BoxCollider2D laserDetected1; public BoxCollider2D laserDetected2; public GameObject saw1; public GameObject saw2; public Transform health; public Transform stamina; public SpriteRenderer srStamina; private Transform pl; private Transform tr; public Transform state; public Laser state1; public Laser state2; public Laser state3; public Laser state4; private Coroutine coroutineStamina; public SpriteRenderer bossBase; public SpriteRenderer laserD1; public SpriteRenderer laserD2; public Gate gateStart; public Gate gateEnd; public GameObject blockWin; public GameObject physicsIn; public GameObject stateLasers; public GameObject expStart; public AudioSet setStart; public AudioClip setEnd; public AudioBase audioBase; public void Awake() { bool isDeath = PlayerPrefs.GetString("boss2") == "death"; blockWin.SetActive(false); if (isDeath) { isAlive = false; gateStart.isReverse = true; gateEnd.isReverse = true; physicsIn.SetActive(false); stateLasers.SetActive(false); expStart.SetActive(false); gameObject.SetActive(false); } else { tr = transform; pl = player.transform; timeStamina = 5.4f / speedRotate / 100f; timeRetarget = 5.4f / speedRotate; saw.localScale = Vector3.zero; stamina.localScale = Vector3.zero; srStamina.color = new Color(0f, 0.5f, 1f, 0f); saw1.SetActive(false); saw2.SetActive(false); LaserDisable(); LaserBlockEnable(); } } public void Update() { if (isAlive) { if (isActivated == true) { switch (stage) { case 1: if (isMove == true) { if (tr.position == target) { isMove = false; RotatePlayer(); saw1.SetActive(true); saw2.SetActive(true); stamina.localScale = Vector3.zero; srStamina.color = new Color(0f, 0.5f, 1f, 1f); if (coroutineStamina != null) { StopCoroutine(coroutineStamina); } coroutineStamina = StartCoroutine(StaminaAnim(timeStamina, 100)); StartCoroutine(Retarget1()); } else { tr.position = Vector2.MoveTowards(tr.position, target, speed); } } break; case 2: if (isMove == true) { if (tr.position == target) { isMove = false; RotatePlayer(); saw.localScale = Vector3.zero; saw1.SetActive(true); saw2.SetActive(true); stamina.localScale = Vector3.zero; srStamina.color = new Color(0f, 0.5f, 1f, 1f); if (coroutineStamina != null) { StopCoroutine(coroutineStamina); } coroutineStamina = StartCoroutine(StaminaAnim(timeStamina, 100)); StartCoroutine(Retarget2()); } else { tr.position = Vector2.MoveTowards(tr.position, target, speed); } } break; case 3: if (isMove == true) { if (tr.position == target) { isMove = false; RotatePlayer(); saw.localScale = Vector3.zero; LaserEnable(); stamina.localScale = Vector3.zero; srStamina.color = new Color(0f, 0.5f, 1f, 1f); if (coroutineStamina != null) { StopCoroutine(coroutineStamina); } coroutineStamina = StartCoroutine(StaminaAnim(timeStamina, 100)); StartCoroutine(Retarget3()); } else { tr.position = Vector2.MoveTowards(tr.position, target, speed); } } break; case 4: if (isMove == true) { if (tr.position == target) { isMove = false; RotatePlayer(); saw.localScale = Vector3.zero; LaserEnable(); stamina.localScale = Vector3.zero; srStamina.color = new Color(0f, 0.5f, 1f, 1f); if (coroutineStamina != null) { StopCoroutine(coroutineStamina); } coroutineStamina = StartCoroutine(StaminaAnim(timeStamina, 100)); StartCoroutine(Retarget4()); } else { tr.position = Vector2.MoveTowards(tr.position, target, speed); } } break; case 5: if (isMove == true) { if (tr.position == target) { isMove = false; RotatePlayer(); saw.localScale = Vector3.zero; LaserEnable(); saw1.SetActive(false); saw2.SetActive(false); stamina.localScale = Vector3.zero; srStamina.color = new Color(0f, 0.5f, 1f, 1f); if (coroutineStamina != null) { StopCoroutine(coroutineStamina); } coroutineStamina = StartCoroutine(StaminaAnim(timeStamina, 100)); StartCoroutine(Retarget5()); } else { tr.position = Vector2.MoveTowards(tr.position, target, speed); } } break; } } else { if (trigStart.enabled == false) { isActivated = true; float musicValue = PlayerPrefs.GetFloat("music"); audioBase.UpSound(0.01f, 5, 0, TypePlaying.Music); explosion.health = 0f; explosion.StartCoroutineTimerOffsetExplosion(); RegionDetected(); LaserDisable(); target = Target(); } } } } public void FixedUpdate() { if (!isMove && isActivated) { laserOffset1.localEulerAngles = new Vector3(0f, 0f, laserOffset1.localEulerAngles.z + speedRotate); laserOffset2.localEulerAngles = new Vector3(0f, 0f, laserOffset2.localEulerAngles.z + speedRotate); if (isWorkingLaser) { state.localEulerAngles = new Vector3(0f, 0f, state.localEulerAngles.z + speedRotate); } } } public void RotatePlayer() { Vector2 p = pl.position; float angle = Mathf.Atan2(py, px) * Mathf.Rad2Deg; laserOffset1.localEulerAngles = new Vector3(0f, 0f, angle); laserOffset2.localEulerAngles = new Vector3(0f, 0f, angle - 180f); } private Vector3[] posLasers = new Vector3[] { Vector3.zero, Vector3.zero}; public void TriggerLaserDefect(int id) { switch (id) { case 1: state1.active = false; state1.lr1.SetPositions(posLasers); break; case 2: state2.active = false; state2.lr1.SetPositions(posLasers); break; case 3: state3.active = false; state3.lr1.SetPositions(posLasers); break; case 4: state4.active = false; state4.lr1.SetPositions(posLasers); break; } if (!state1.active && !state2.active && !state3.active && !state4.active) { isWorkingLaser = false; state1.active = false; state2.active = false; state3.active = false; state4.active = false; laserL1.active = false; laserL2.active = false; laser1.localPosition = Vector2.zero; laser2.localPosition = Vector2.zero; } } public void OnCollisionEnter2D(Collision2D collision) { if (collision.transform.tag == "Player") { hp = hp - pl.GetComponent<Power>().power; health.localScale = new Vector2(hp / 50f, hp / 50f); stage = 5 - (int)(hp / 25f); if (stage == 4) { LaserBlockDisable(); } if (hp <= 0f && isAlive == true) { audioBase.LowerSound(0.1f, 50, 0, TypePlaying.Music); audioBase.SetSound(setEnd, 0, 0.8f, TypePlaying.Music, true, 1f); GameObject deadInside = Instantiate(explosionAsset, pl.position, Quaternion.identity); deadInside.GetComponent<Rigidbody2D>().isKinematic = true; deadInside.transform.localScale = new Vector2(2f, 2f); Explosion exp = deadInside.GetComponent<Explosion>(); exp.radius = 2f; exp.health = 0f; exp.timeOffsetExplosion = 3f; exp.StartCoroutineTimerOffsetExplosion(); gateStart.OnTriggerEnter2D(player.GetComponent<Collider2D>()); gateEnd.OnTriggerEnter2D(player.GetComponent<Collider2D>()); PlayerPrefs.SetString("boss2", "death"); blockWin.SetActive(false); gameObject.SetActive(false); } } } public void OnTriggerEnter2D(Collider2D collision) { if (collision.tag == "Player") { blockWin.SetActive(true); trigStart.enabled = false; } } public void LaserEnable() { if (isWorkingLaser) { laserL1.active = true; laserL2.active = true; state1.active = false; state2.active = false; state3.active = false; state4.active = false; } laser1.localPosition = new Vector2(0f, -1f); laser2.localPosition = new Vector2(0f, -1f); return; } public void LaserDisable() { if (isWorkingLaser) { state1.active = true; state2.active = true; state3.active = true; state4.active = true; laserL1.active = false; laserL2.active = false; } laser1.localPosition = Vector2.zero; laser2.localPosition = Vector2.zero; return; } public void LaserBlockEnable() { laserDetected1.enabled = true; laserDetected2.enabled = true; } public void LaserBlockDisable() { laserDetected1.enabled = false; laserDetected2.enabled = false; } public void RegionDetected() { Vector2 result = Vector2.zero; Vector2 pos = pl.position; if (pos.x > -45f & pos.x <= -30f) { result.x = 1; } else if (pos.x > -30f & pos.x < -5f) { result.x = 2; } else if (pos.x >= -5f & pos.x <= 5f) { result.x = 3; } else if (pos.x > 5f & pos.x <= 30f) { result.x = 4; } else if (pos.x >= 30f & pos.x < 45f) { result.x = 5; } if (pos.y > -45f & pos.y <= -30f) { result.y = 1; } else if (pos.y > -30f & pos.y < -5f) { result.y = 2; } else if (pos.y >= -5f & pos.y <= 5f) { result.y = 3; } else if (pos.y > 5f & pos.y <= 30f) { result.y = 4; } else if (pos.y >= 30f & pos.y < 45f) { result.y = 5; } region = result; return; } private readonly Vector2[] aroundCloser = new Vector2[] { new Vector2(2, 2), new Vector2(2, 3), new Vector2(2, 4), new Vector2(3, 2), new Vector2(3, 4), new Vector2(4, 2), new Vector2(4, 3), new Vector2(4, 4) }; public Vector2 Target() { Vector2 result = Vector2.zero; if (region == new Vector2(3, 3)) { region = aroundCloser[Random.Range(0, 8)]; } switch (region.x) { case 1: result.x = Random.Range(-45f, -32f); break; case 2: result.x = Random.Range(-29f, -5f); break; case 3: result.x = Random.Range(-5f, 5f); break; case 4: result.x = Random.Range(5f, 29f); break; case 5: result.x = Random.Range(32f, 45f); break; } switch (region.y) { case 1: result.y = Random.Range(-45f, -32f); break; case 2: result.y = Random.Range(-29f, -5f); break; case 3: result.y = Random.Range(-5f, 5f); break; case 4: result.y = Random.Range(5f, 29f); break; case 5: result.y = Random.Range(32f, 45f); break; } isMove = true; return result; } public IEnumerator StaminaAnim(float time, int count) { yield return new WaitForSeconds(time); float sc = hp * (100f - count) / 5000f; stamina.localScale = new Vector2(sc, sc); if (count > 1) { count = count - 1; coroutineStamina = StartCoroutine(StaminaAnim(time, count)); } } public IEnumerator Retarget1() { yield return new WaitForSeconds(timeRetarget); srStamina.color = new Color(0f, 0.5f, 1f, 0f); RotatePlayer(); saw1.SetActive(false); saw2.SetActive(false); RegionDetected(); target = Target(); } public IEnumerator Retarget2() { yield return new WaitForSeconds(timeRetarget); srStamina.color = new Color(0f, 0.5f, 1f, 0f); RotatePlayer(); saw.localScale = new Vector2(2f, 2f); saw1.SetActive(false); saw2.SetActive(false); RegionDetected(); target = Target(); } public IEnumerator Retarget3() { yield return new WaitForSeconds(timeRetarget); srStamina.color = new Color(0f, 0.5f, 1f, 0f); RotatePlayer(); saw.localScale = new Vector2(2f, 2f); LaserDisable(); RegionDetected(); target = Target(); } public IEnumerator Retarget4() { yield return new WaitForSeconds(timeRetarget); srStamina.color = new Color(0f, 0.5f, 1f, 0f); RotatePlayer(); saw.localScale = new Vector2(2f, 2f); LaserDisable(); RegionDetected(); target = Target(); } public IEnumerator Retarget5() { yield return new WaitForSeconds(timeRetarget); srStamina.color = new Color(0f, 0.5f, 1f, 0f); RotatePlayer(); saw.localScale = new Vector2(2f, 2f); saw1.SetActive(true); saw2.SetActive(true); LaserDisable(); RegionDetected(); target = Target(); } }
3 Chef ist die beste Qualität unter den Chefs! Er benutzt Raycasts, um sich zu bewegen. Zuerst dreht es sich zufällig in einen beliebigen Winkel, dann wählt es unter 12 in verschiedene Richtungen gestarteten Raycasts den längsten aus und fliegt zum Punkt des Raycasts. Es gibt Objekte auf dem Level, von denen einige auch zerstört werden. Und wie reagieren Boss-Raycasts auf Objekte? Auslöser wurden statischen Objekten hinzugefügt, die doppelt so groß sind wie die Objekte selbst, sodass der Raycast einen Punkt hatte, an dem der Boss nicht in der Luft fliegen würde, nicht in der Wand wäre, sondern an die Wand genietet würde. Der Boss hat eine besondere Verteidigung: Zu Beginn des Levels mit dem Boss (jeder Boss ist ein separates großes Level ohne Rätsel von Drittanbietern) gibt es Auslöser, die so eingestellt sind, dass nur einer aktiviert wird.Der Boss hat 5 Fallenrohlinge und jeder Auslöser lässt nur 3-4 Fallen aktiv. Außerdem hatte er ein verbessertes Gebietssystem, das aus vordefinierten Bereichen für jeden Bereich (in dem sich der Spieler befinden kann) und für jede Falle bestand. Und während des Fluges tötet der Boss immer den Spieler.Fallenliste:- Der Laser in der Mitte, der nach jedem Flug des Chefs den Spieler ansieht.
- 2 Laser, die die Lerp-Funktion verwenden, um sich in bestimmte Bereiche zu bewegen (abhängig vom Standort des Spielers) und vor der Bewegung an den Spieler gesendet werden (sie sollten sich immer vor dem Spieler befinden, aber es ist ein Fehler aufgetreten).
- Eine Säge, die immer in den gleichen Bereich wie der Spieler geht.
- 2 Sägen, die immer vom Bereich, in dem sich der Spieler befindet, auf den linken und rechten Bereich gerichtet sind.
- 4 Fallenkugeln, die sich symmetrisch zur Mitte bewegen
Skript BossManagement3 using System.Collections; using UnityEngine; using UnityEngine.SceneManagement; public class BossManagement3 : MonoBehaviour { public float health = 100f; public Vector4[] boxs = new Vector4[0]; public int[] saw1Fields = new int[0]; public int[] saw2Fields = new int[0]; public int[] saw3Fields = new int[0]; public int[] laser1Fields = new int[0]; public int[] laser2Fields = new int[0]; public Transform trBoss; public SpriteRenderer srBoss; public BossTracing3 bt; public Transform saw1; public Transform saw2; public Transform saw3; public Transform laser; public Transform laser1; public Transform laser2; public Transform trap1; public Transform trap2; public Transform trap3; public Transform trap4; public LineRenderer lr1; public LineRenderer lr2; public TrailRenderer trail; public GameObject exp; public GameObject terminal1; public GameObject terminal2; public GameObject LaserTarget; public GameObject LaserMover; public GameObject TrapsMover; public GameObject SawMover; public GameObject SawsAroundMover; public Explosion explosion; public SpriteRenderer sr; public CircleCollider2D cc; public Animator animatorEnd; public bool isMove = false; public bool isMoveSaw1 = false; public bool isMoveSaw2 = false; public bool isMoveSaw3 = false; public bool isMoveLaser1 = false; public bool isMoveLaser2 = false; public bool isMoveTraps = false; public int loadScene = 35; public int fieldPlayer = 0; private bool isActive = true; private float maxHealth; private Vector2 target = Vector2.zero; private Vector2 saw1target = Vector2.zero; private Vector2 saw2target = Vector2.zero; private Vector2 saw3target = Vector2.zero; private Vector2 laser1target = Vector2.zero; private Vector2 laser2target = Vector2.zero; private Vector2 traptarget1 = Vector2.zero; private Vector2 traptarget2 = Vector2.zero; private Vector2 traptarget3 = Vector2.zero; private Vector2 traptarget4 = Vector2.zero; private Vector2 border = new Vector2(47f, 44.5f); private Vector2 borderSaw = new Vector2(46f, 43.5f); private Management m; public GameObject p { get; private set; } private HealthBar hb; private Transform tr; private Power ppl; private int lengthBoxs = 0; private bool isLife = true; public void Awake() { isActive = !(PlayerPrefs.GetString("boss1") == "life" && PlayerPrefs.GetString("boss2") == "life"); terminal1.SetActive(!isActive); terminal2.SetActive(isActive); trail.enabled = PlayerPrefs.GetString("graphicsquality") != "low"; m = GameObject.FindWithTag("MainCamera").GetComponent<Management>(); lengthBoxs = boxs.Length; maxHealth = health; hb = m.healthBar; p = m.player; tr = p.transform; ppl = m.ppl; float c = health / maxHealth; srBoss.color = new Color(0f, 0f, c); } public void Start() { if (isActive == false) { return; } StartCoroutine(Mover()); fieldPlayer = bt.BoxPos(tr.position); if (fieldPlayer >= 0) { Vector4 r = boxs[saw1Fields[fieldPlayer]]; saw1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[saw2Fields[fieldPlayer]]; saw2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[saw3Fields[fieldPlayer]]; saw3target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[laser1Fields[fieldPlayer]]; laser1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[laser2Fields[fieldPlayer]]; laser2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } else { Vector4 r = boxs[Random.Range(0, lengthBoxs)]; saw1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[Random.Range(0, lengthBoxs)]; saw2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[Random.Range(0, lengthBoxs)]; saw3target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[Random.Range(0, lengthBoxs)]; laser1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[Random.Range(0, lengthBoxs)]; laser2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } TrapMover(); StartCoroutine(Laser1AIM()); StartCoroutine(Laser2AIM()); isMoveSaw1 = true; isMoveSaw2 = true; isMoveSaw3 = true; isMoveLaser1 = true; isMoveLaser2 = true; return; } public void SawMover1() { fieldPlayer = bt.BoxPos(tr.position); if (fieldPlayer >= 0) { Vector4 r = boxs[saw1Fields[fieldPlayer]]; saw1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } else { Vector4 r = boxs[Random.Range(0, lengthBoxs)]; saw1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } isMoveSaw1 = true; } public void SawMover2() { fieldPlayer = bt.BoxPos(tr.position); if (fieldPlayer >= 0) { Vector4 r = boxs[saw2Fields[fieldPlayer]]; saw2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } else { Vector4 r = boxs[Random.Range(0, lengthBoxs)]; saw2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } isMoveSaw2 = true; } public void SawMover3() { fieldPlayer = bt.BoxPos(tr.position); if (fieldPlayer >= 0) { Vector4 r = boxs[saw3Fields[fieldPlayer]]; saw3target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } else { Vector4 r = boxs[Random.Range(0, lengthBoxs)]; saw3target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } isMoveSaw3 = true; } public void LaserMover1() { fieldPlayer = bt.BoxPos(tr.position); if (fieldPlayer >= 0) { Vector4 r = boxs[laser1Fields[fieldPlayer]]; laser1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } else { Vector4 r = boxs[Random.Range(0, lengthBoxs)]; laser1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } StartCoroutine(Laser1AIM()); isMoveLaser1 = true; } public void LaserMover2() { fieldPlayer = bt.BoxPos(tr.position); if (fieldPlayer >= 0) { Vector4 r = boxs[laser2Fields[fieldPlayer]]; laser2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } else { Vector4 r = boxs[Random.Range(0, lengthBoxs)]; laser2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); } StartCoroutine(Laser2AIM()); isMoveLaser2 = true; } public void TrapMover() { traptarget1 = new Vector2(Random.Range(-border.x, border.x), Random.Range(-border.y, border.y)); traptarget2 = new Vector2(-traptarget1.x, -traptarget1.y); traptarget3 = new Vector2(-traptarget1.x, traptarget1.y); traptarget4 = new Vector2(traptarget1.x, -traptarget1.y); isMoveTraps = true; } public IEnumerator Laser1AIM() { yield return new WaitForSeconds(0.5f); Vector2 diff = tr.position; float rot_z = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg + 90f; laser1.rotation = Quaternion.Euler(0f, 0f, rot_z); } public IEnumerator Laser2AIM() { yield return new WaitForSeconds(0.5f); Vector2 diff = tr.position; float rot_z = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg + 90f; laser2.rotation = Quaternion.Euler(0f, 0f, rot_z); } public IEnumerator Mover() { yield return new WaitForSeconds(7.5f); if (isLife) { Vector2 diff = tr.position; float rot_z = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg + 90f; laser.rotation = Quaternion.Euler(0f, 0f, rot_z); target = bt.GetPosRaycast(); isMove = true; } } public void Update() { if (isActive == false) { return; } float s = Time.fixedDeltaTime / (0.03f / Time.timeScale); if (isMove) { trBoss.position = Vector2.MoveTowards(trBoss.position, target, s * 0.5f); if (trBoss.position == (Vector3)target) { isMove = false; if (isLife) { StartCoroutine(Mover()); } } } if (isMoveSaw1) { saw1.position = Vector2.MoveTowards(saw1.position, saw1target, s * 0.1f); if (saw1.position == (Vector3)saw1target) { isMoveSaw1 = false; if (isLife) { SawMover1(); } } } if (isMoveSaw2) { saw2.position = Vector2.MoveTowards(saw2.position, saw2target, s * 0.1f); if (saw2.position == (Vector3)saw2target) { isMoveSaw2 = false; if (isLife) { SawMover2(); } } } if (isMoveSaw3) { saw3.position = Vector2.MoveTowards(saw3.position, saw3target, s * 0.1f); if (saw3.position == (Vector3)saw3target) { isMoveSaw3 = false; if (isLife) { SawMover3(); } } } if (isMoveLaser1) { laser1.position = Vector2.Lerp(laser1.position, laser1target, s * 0.1f); if (laser1.position == (Vector3)laser1target) { isMoveLaser1 = false; if (isLife) { LaserMover1(); } } } if (isMoveLaser2) { laser2.position = Vector2.Lerp(laser2.position, laser2target, s * 0.1f); if (laser2.position == (Vector3)laser2target) { isMoveLaser2 = false; if (isLife) { LaserMover2(); } } } if (isMoveTraps) { trap1.position = Vector2.MoveTowards(trap1.position, traptarget1, s * 0.1f); trap2.position = Vector2.MoveTowards(trap2.position, traptarget2, s * 0.1f); trap3.position = Vector2.MoveTowards(trap3.position, traptarget3, s * 0.1f); trap4.position = Vector2.MoveTowards(trap4.position, traptarget4, s * 0.1f); lr1.SetPosition(0, trap1.position); lr1.SetPosition(1, trap2.position); lr2.SetPosition(0, trap3.position); lr2.SetPosition(1, trap4.position); if (trap1.position == (Vector3)traptarget1) { isMoveTraps = false; if (isLife) { TrapMover(); } } } } public void OnCollisionEnter2D(Collision2D collision) { if (collision.gameObject == p) { if (isActive == false) { isActive = true; Start(); } if (isMove == true) { hb.StraightDamage(10f, "Boss3"); } else { health = health - ppl.power; float c = health / maxHealth; srBoss.color = new Color(0f, 0f, c); trail.startColor = srBoss.color; if (health <= 0f) { isLife = false; isMove = false; saw1target = trBoss.position; saw2target = trBoss.position; saw3target = trBoss.position; isMoveSaw1 = true; isMoveSaw2 = true; isMoveSaw3 = true; sr.enabled = false; cc.enabled = false; exp.SetActive(true); explosion.health = 0f; explosion.StartCoroutineTimerOffsetExplosion(); Vector2 diff = trBoss.position; float rot_z = Mathf.Atan2(diff.y, diff.x) * Mathf.Rad2Deg + 90f; laser.rotation = Quaternion.Euler(0f, 0f, rot_z); int fieldBoss = bt.BoxPos(trBoss.position); Vector4 r = boxs[laser1Fields[fieldBoss]]; laser1target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); r = boxs[laser2Fields[fieldBoss]]; laser2target = new Vector2(Random.Range(rz, rx), Random.Range(rw, ry)); StartCoroutine(Ended()); } } } } public void EndedCoroutine() { if (!isActive) { //Debug.Log("End"); isActive = true; StartCoroutine(Ended()); } } public IEnumerator Ended() { yield return new WaitForSeconds(6.5f); if (hb.healthBarImage.fillAmount != 0f) { animatorEnd.SetBool("isActive", true); StartCoroutine(EndedFunction()); } } public IEnumerator EndedFunction() { yield return new WaitForSeconds(1.5f); if (hb.healthBarImage.fillAmount != 0f) { PlayerPrefs.SetInt("progress", 35); SceneManager.LoadSceneAsync(loadScene); } } public void ControlDamagers(bool lt, bool lm, bool tm, bool sm, bool sam) { LaserTarget.SetActive(lt); LaserMover.SetActive(lm); TrapsMover.SetActive(tm); SawMover.SetActive(sm); SawsAroundMover.SetActive(sam); } }
Audio und MusikIch kann auch keine Musik schreiben, aber ich habe genug Musikgeschmack, um die richtige Musik zu finden. In meinem Plan war es für jedes Level notwendig, einen Track auszuwählen. Und zum größten Teil habe ich den Plan erfüllt: Ich habe 25 Tracks aufgenommen. Ich habe alle Titel im Asset Store durchsucht. Ich habe Sounds für den Rest auf freesound.org oder ähnlichen Seiten aufgenommen.Der Ton aus dem technischen Teil wurde nach einem einfachen Prinzip erzeugt: Auf der Kamera befanden sich 5 deaktivierte AudioSource- und ein AudioBase-Skript zur Steuerung des Tons. In diesem Skript gab es die Hauptfunktion von SetSound mit den Parametern Lautstärke, Loop, Typ (Musik oder Sound) und der Audiodatei selbst. Nach dem Signal begann der Ton zu spielen und (falls nicht geloopt) der IEnumerator mit einer Zeit eingeschaltet wurde, die der Länge des Tracks entspricht, und nachdem er abgelaufen war, schaltete er die Komponente aus.Skript AudioBase using UnityEngine; using System.Collections; public class AudioBase : GlobalFunctions { public AudioSource[] layerSounds = new AudioSource[0]; public GameObject music; private float musicValue, soundValue; private int lengthLayerSounds = 0; private bool soundActive = true; private Coroutine offsetActive; private int lowerSoundCoroutineCounter = 100; private int upSoundCoroutineCounter = 0; public void Awake() { soundActive = PlayerPrefs.GetString("graphicsquality") != "low"; musicValue = PlayerPrefs.GetFloat("music"); soundValue = PlayerPrefs.GetFloat("sound"); lengthLayerSounds = layerSounds.Length; for (int i = 0; i < lengthLayerSounds; i++) { layerSounds[i].enabled = false; } } public void LowerSound(float timer, int upd, int id, TypePlaying typePlaying) { lowerSoundCoroutineCounter = upd; if (typePlaying == TypePlaying.Music) { StartCoroutine(LowerSoundCoroutine(timer, upd, id, musicValue)); } else { StartCoroutine(LowerSoundCoroutine(timer, upd, id, soundValue)); } } public void UpSound(float timer, int upd, int id, TypePlaying typePlaying) { upSoundCoroutineCounter = 0; if (typePlaying == TypePlaying.Music) { StartCoroutine(UpSoundCoroutine(timer, upd, id, musicValue)); } else { StartCoroutine(UpSoundCoroutine(timer, upd, id, soundValue)); } } public IEnumerator LowerSoundCoroutine(float timer, int upd, int id, float volumeSen) { yield return new WaitForSeconds(timer); layerSounds[id].volume = Stable2((layerSounds[id].volume / volumeSen - timer) * volumeSen, 0f, 1f); if (lowerSoundCoroutineCounter > 1) { StartCoroutine(LowerSoundCoroutine(timer, upd, id, volumeSen)); lowerSoundCoroutineCounter -= 1; } } public IEnumerator UpSoundCoroutine(float timer, int upd, int id, float volumeSen) { yield return new WaitForSeconds(timer); layerSounds[id].volume = Stable2((layerSounds[id].volume / volumeSen + timer) * volumeSen, 0f, 1f); if (upSoundCoroutineCounter < upd) { StartCoroutine(UpSoundCoroutine(timer, upd, id, volumeSen)); upSoundCoroutineCounter += 1; } } public void UpdateSound() { if (soundActive) { float time = Time.timeScale; for (int i = 0; i < lengthLayerSounds; i++) { AudioSource audioSource = layerSounds[i]; if (audioSource.enabled == true) { audioSource.pitch = time; } } } } public void SetSound(AudioClip audioClip, int layerSound, float volume, TypePlaying typePlaying, bool loop, float time) { StartCoroutine(SetSoundTime(audioClip, layerSound, volume, typePlaying, loop, time)); } public IEnumerator SetSoundTime(AudioClip audioClip, int layerSound, float volume, TypePlaying typePlaying, bool loop, float time) { yield return new WaitForSeconds(time); SetSound(audioClip, layerSound, volume, typePlaying, loop); } public void SetSound(AudioClip audioClip, int layerSound, float volume, TypePlaying typePlaying, bool loop) { if (volume == 0f) { return; } if (soundActive) { AudioSource audioSource = layerSounds[layerSound]; audioSource.enabled = true; audioSource.clip = audioClip; audioSource.loop = loop; if (typePlaying == TypePlaying.Sound) { audioSource.volume = soundValue * volume; } else { audioSource.volume = musicValue * volume; } audioSource.Play(); if (offsetActive != null) { StopCoroutine(offsetActive); offsetActive = null; } if (!loop) { offsetActive = StartCoroutine(Offet(layerSound, audioClip.length, audioSource)); } } } public IEnumerator Offet(int layerSound, float length, AudioSource audioSource) { yield return new WaitForSeconds(length); if (audioSource.clip == layerSounds[layerSound].clip) { AudioSource audioSource2 = layerSounds[layerSound]; audioSource2.Stop(); audioSource2.enabled = false; } } }
Außerdem verfügt die Tramp-Komponente (Stempeln) über ein eigenes Soundsystem: Wenn ein Spieler das Stempeln des Stempels eingibt, wird die für den Sound verantwortliche Komponente aktiviert. Und wenn nötig, bestimmt das Produkt die Entfernung zum Player und gibt nach Berechnung des Koeffizienten die erforderliche Lautstärke, wodurch ein realistischer Soundeffekt entsteht. Aber es funktioniert nicht so, wie ich es wollte, vielleicht ist es im falschen Code.Das GrundstückJa, dieses Spiel ist die Geschichte. Und er hat zwei Eigenschaften: Er ist fast nonverbal und er hat eine Wahl, die das Ende des Spiels beeinflusst. Es ist besser, über die Variabilität zu berichten (da diese Variabilität tatsächlich die gesamte Handlung darstellt).Das Spiel hat drei Möglichkeiten: bei den ersten beiden Bossen und bei Stufe 32. Die Wahl bei den Bossen liegt auf der Hand: Sie können getötet werden oder nicht, indem sie einen Angriff starten oder die nächste Stufe erreichen. Und auf Stufe 32 etwas komplizierter: Sie können den Auslöser aktivieren, was das Erwachen des lokalen Story-Save-Ankers (eines Charakters namens AI) impliziert. Die Wahl der ersten beiden Bosse beeinflusst, ob es einen Kampf mit 3 Bossen gibt. Wenn Sie mindestens einen der ersten beiden Bosse töten, kommt es zu einem Kampf mit dem dritten Boss. Wenn nicht, dann nein.Es gibt nur 4 Enden: gut, schlecht, neutral und geheim. Sie sind von 2 Möglichkeiten betroffen: KI-Aktivierung und Töten von 3 Bossen. Ich werde die Endungen in der Reihenfolge analysieren:Gutes EndeEs passiert, wenn 3 Bosse nicht getötet wurden und KI aktiviert wurde. Darin findet ein KI-Monolog statt, in dem er auf eine Fortsetzung hinweist und ein brennendes Auge gezeigt wird (aus verschiedenen Spieleffekten des Feuers).Text beendenVielen Dank
Du könntest mich wiederbeleben
und es geschafft haben, den Waldläufer nicht zu wecken.
Anscheinend wirst du seine einzige erfolgreiche Instanz sein.
Du hast eine kleine Pause verdient.
Du hast gewonnen.
Wir sehen uns
Aber warum ist die Handlung fast nonverbal? Ich konnte es wegen der Endungen nicht völlig nonverbal machen. Aber es gibt genug Text im Spiel. Um dem Spieler „für die HNO des Spiels“ zu erklären, wurden im Spiel Terminals mit Notizen angezeigt, und das Skript des Spiels wird ausführlich erklärt.Szenario DasSzenario in diesem Fall ist die Vorgeschichte der Welt, die sich aus den Gesichtern und Charakteren dieses Spiels in Form von Notizen, Protokollen, Berichten, Monologen und Dialogen ergibt: im allgemeinen Text. Und dies ist ein so graphomanisches Delirium eines Programmierers, dass sogar Glukhovsky überrascht wäre (ich habe nichts gegen ihn, ich liebe Metro). Leider hatte ich nicht viel Zeit, um einen vollwertigen NPC zu erstellen. Obwohl ich die Sprites für sie im Spiel gefunden habe:Aufgrund der Umstände wurde das Drehbuch zuletzt geschrieben, und bereits darauf habe ich eine Handlung erstellt, nach der ich das Spiel abgeschlossen habe. Er schrieb jeden Wochentag 4 Wochen lang in einem Kleinbus, als ich zur normalen Arbeit ging. Und selbst in so kurzer Zeit konnte ich viel schreiben.Wenn überhaupt, hat das ursprüngliche Spiel keine Handlung und keine Hinweise darauf. Und jetzt macht es für mich keinen Sinn, die Handlung zu verbergen (schließlich wird niemand das Spiel vollständig bestehen und alle Notizen lesen). Es gibt drei Ziele für diese Graphomanie: Hinzufügen einer angemessenen Variabilität der Aktionen des Spielers, Erklären unerklärlicher Spieldinge und zumindest ein wenig größeres Interesse des Spielers an seinem Spiel.Ich habe das Drehbuch auf sehr einfache Weise geschrieben: Zuerst habe ich es in einer Geschichte für 40-50 Sätze in 2-3 Wochen geschrieben. Dann wählte ich für jede Note entsprechend dem Satz und fügte der Note auf der Grundlage eines Satzes 2-3 Sätze hinzu, änderte sie in Monologe (oder andere Formen der Erzählung) und erhielt fertige, ausgewogene Noten. Infolgedessen wurden aus einem solchen Empfang in allen Notizen insgesamt 160 Sätze mit Informationen akkumuliert.Und Sie müssen verstehen: In meinem Spiel gibt es genug unlogische Dinge, und um jedes im Format einer Geschichte wahrheitsgemäß zu rechtfertigen, wird viel Text benötigt. Deshalb habe ich versucht, kein Wasser zu gießen, und jeder Satz hat entweder versucht, sich mit Bedeutung zu füllen oder das Handlungsloch zu schließen oder die Charaktere der Geschichte zu malen und zu enthüllen. Trotzdem bleibt das Niveau des Schreibens zweifelhaft.Worüber spricht das Drehbuch? Wenn es sehr einfach ist, dann ist dies die Portal-Handlung, nur mit einer offenen Geschichte der Welt und leicht veränderten Charakteren (launischer). Dieses Szenario hat übrigens ein Merkmal: Das Geschlecht lebloser Objekte ist trotz der Logik, des gesunden Menschenverstandes oder der Regeln der russischen Sprache (und auch anderer Sprachen) durchschnittlich geworden. Wenn sich plötzlich jemand (na ja, plötzlich) dafür interessierte, werde ich das vollständige Skript und alle Spielnotizen hier belassen:Das Skript:
, , (3 )
(1 )
, RLIS (2 )
:
[1] . [3] : , , , , .. . [3] , , , ([2] , , ). (4)
[4] , . [5] , . [6] . [6] , . [7] . (5)
[8] : . [9] (- ) . [10] ( ). (3)
[11] . [12] , , , . [13] . [13] , . (3)
[15] ([14] — , , ). [15] ( ) ([16] ). (4)
[17] . [18] «». [18] . [19]- . (4)
[20] «». [21] , . [22] , . (3)
[23] . [24] . [25] « ». (3)
[26] , . [27] , - , . [28] « ». [29] . (4)
[30] - ( ?) ( , ). [31] . [32] , ([33] , ), - , . [34] . [35] , . [36] ( ): 10 (10 = 1 ) . [X]- ( ) , ( 2 1 ?). [37] 2 . (9)
Sammelalbum():
1) {} «» , . , . - , .
2) {} RLIS (reasonable likeness in simulation) — . . RLIS ( ) — .
3) {} RLIS 100 : , , , , .. , , , . , .
4) {} , . , , , , . magnum opus .
5) {ARSotLotC} , . , . , .
6) {} -!!! - , . , , , . , , , . 2 : .
7) {} , backup , . : , . , , .
8) {} . , . , , , ( ).
9) {} , , -. ? . , . , . …
10) {} - , . . , …
11) {ARSotLotC} . , «» , , . , … .
12) {} , «» . . «», . . .
13) {} , . , . , . .
14) {} , , ( ). — , . : , .
15) {} — . , . . , , .
16) {} ? , . .
17) {} . . , «». . , , .
18) {} «». , ? , , .
19) {} «» , . , , , , . . .
20) {} '' ''. , , '' '', , .
21) {} '' : , ''. , . .
22) {ARSotLotC} : , . , .
23) {} , . ' '. , .
24) {} , , . , . , .
25) {} . , - . , , ' '.
26) {} . , . . ' ' !
27) {} ' ' , . . : , , .
28) {ARSotLotC} - < > . . . , .
29) {ARSotLotC} . ? , (- , ) ARSotLotC (Automatic Recording System of the Logs of the Complex).
30) {ARSotLotC} «» , . , , . - , backup . , , .
31) {ARSotLotC} : . , . . backup.
32) {ARSotLotC} . . , . .
33) {ARSotLotC} ( backup'). .
34) {ARSotLotC} , . , , 10 . , . Ps: , , .
35.1) {} . . ' ' . , , ''. , - , . ' '.
35.2)
CodebasisDa meine Spezialität - ein Programmierer, und der Code war meine Hauptsorge. Im Vergleich zur ursprünglichen Codebasis hat sich die Codebasis für die Fortsetzung um das 2- bis 3-fache erhöht (obwohl das Original 900 Zeilen Codemethoden enthält, da ich Angst hatte, Bundles wie Schleifen und Arrays oder GetChild () und Schleifen zu verwenden )Mit der Quantität stieg auch die Gesamtqualität des Codes, aber ich konnte Fehler nicht vermeiden. Infolgedessen gibt es viele Fehler im Code selbst. Und trotz meines objektiv mageren Wissens sehe ich meine Fehler perfekt. Also werden wir meinen wichtigsten Fehler analysieren. Nehmen Sie zum Beispiel einen einfachen Code: public class VelocityRotate : MonoBehaviour { public float rotate = 0f; public bool oneTime = true; private bool active = true; public void OnTriggerEnter2D(Collider2D collision) { if (active == true) { if (oneTime == true) { active = false; } Rigidbody2D rb = collision.GetComponent<Rigidbody2D>(); Vector2 vel = rb.velocity; rb.velocity = RotateVector(vel, rotate); } } public Vector2 RotateVector(Vector2 a, float offsetAngle) { float power = Mathf.Sqrt(ax * ax + ay * ay); float angle = Mathf.Atan2(ay, ax) * Mathf.Rad2Deg - 90f + offsetAngle; return Quaternion.Euler(0, 0, angle) * Vector2.up * power; } }
Haben Sie schnell verstanden, wofür dieses Skript verantwortlich ist? Und wenn du es so machst: public class VelocityRotate : MonoBehaviour { // public float rotate = 0f;// public bool oneTime = true;// private bool active = true;// public void OnTriggerEnter2D(Collider2D collision) { if (active == true) { if (oneTime == true)// { active = false; } // Rigidbody2D rb = collision.GetComponent<Rigidbody2D>(); Vector2 vel = rb.velocity; rb.velocity = RotateVector(vel, rotate); } } public Vector2 RotateVector(Vector2 a, float offsetAngle)// { float power = Mathf.Sqrt(ax * ax + ay * ay);// float angle = Mathf.Atan2(ay, ax) * Mathf.Rad2Deg - 90f + offsetAngle; // offset' return Quaternion.Euler(0, 0, angle) * Vector2.up * power; // } }
Das Fehlen von Kommentaren ist mein allererster und wirklich größter Fehler bei der Entwicklung des Spiels! In seiner gesamten Codebasis gibt es keinen einzigen Kommentar, der erklärt, wofür dieser oder jener Zweig des Codes verantwortlich ist. Und vielleicht ist das für ein kleines Indie-Spiel nicht nötig. Erstens kann ich dieses Spiel definitiv nicht als klein bezeichnen, und zweitens muss ich als zukünftiger Entwickler definitiv in einem Team arbeiten, und das Fehlen einer so nützlichen Angewohnheit wie Kommentieren wird mir jemals einen Streich spielen. Ich habe diesen Fehler gerade erkannt: Er hat mich in all meinen Projekten im Zusammenhang mit der Programmierung verfolgt, und diesmal habe ich dies berücksichtigt und werde das nächste Mal Kommentare abgeben.Bugs und FehlerEs gab viele Fehler. Sehr! Für solch eine massive Arbeit habe ich einen ganzen Monat Korrekturen vorgesehen (August). Es macht keinen Sinn, die Beispiele zu analysieren. Ich habe nur eine Notiz mit all meinen dokumentierten Fehlern eingefügt (obwohl ich die meisten nicht dokumentiert und korrigiert habe):GB2 Checkliste:
// —
\ —
//1) , ,
//2) :
//3)
//4) TipsGamePlay
//5) ( )
//6) 0:
//7) 1: ()
//8)
//9) 2: 2
//10)
//11) 4:
//12) layer Player
//13) 7: ()
//14) 8: ( 1)
//15) 8:
\16) ( )
\17) 8: zero
//18)
//19) 1:
//20) ,
//21)
//22) timescale=0
//23) 6:
//24) 0:
//25)
//26) 7:
//27) 7:
//28) AspectRatio
\29)
//30)
//31) <EXfgpy)b> //32) 7: -
//33) ,
//34) 9:
//35) 9:
//36) 'loop'
//37) 10:
//38) 11: ()
//39) 11:
//40) 11:
//41) 11:
//42) 11:
//43) 11:
//44) ( )
/45) 12:
\46) Raycast
\47) ( static, dynamic, kinematic)
//48) (next level, next start, next end)
\49) 1: elevatorsave = 0
\50) offset angle,
//51) 2:
//52)
//53) 7:
//54) next save
//55) Dynamic Graph
//56) 11: ( )
57) 11:
//58) 9: ()
//59) 11: ( )
//60) 12: ( 2 . active , . .
61) :
//62) : -
//63) :
64)
//65) (. )
//66)
//67) HealthBar
68) 0:
//69) localposition position
70) 14: bool isPresentation
//71) 17: 2 4
72) ()
\73)
//74)
//75) layer,
//76)
//77) 2: 1
\78) ( )
//79)
//80) 3: ,
//81)
//82) 6: ,
//83) 6: 1
//84) 6:
//85) 7: 40. .
//86)
//87) 9:
//88) 32:
//89) offsetAngle elevator
//90) 11:
//91) ( )
//92)
//93)
//94)
//95) 13:
//96) 15:
/97) 3 isshotmode
//98) 17:
//99) 18: ,
//100) 19: ( )
/101) 20:
\102) Tramp
//103) 20:
\104)
//105) 11: ui
//106) text arial
\107)
//108)
//109) 3:
//110) 3:
//111) 3: ,
//112) ,
//113) ()
//114) 4:
//115) ( )
//116) ()
//117) pointsAnimation basicAnimation
//118) 7:
//119) 9:
//120) AudioBase
//121) pointsAnimation
//122) , ( )
//123) 13: HealthBar
//124) 13: ,
//125) 14: kinematic (. )
//126) 14:
//127) 14: ,
//128) velocityField ( , )
//129) 16: velocityField
//130) 22:
//131) 22:
\132) 25:
//133) 26:
//134) 27:
\135) ( )
//136)
//137) :
//138) ( )
//139)
//140) 8:
//141) ( 1.5-2, -oneshot'
\142) lerp
//143) , , ( , )
//144) 22:
//145) 11:
//146) 11:
//147) 11:
//148)
//149) «Home» «Menu»
//150)
//151)
//152)
\153) ( healthEnd)
//154) :
//155) 33: ,
//156) 15: ( 0.1)
//157) 15: velocityfield healthbar
//158)
//159) basicAnimation (27)
//160) (18, 27)
//161)
\162) 19: -
//163) ( trigger collision)
//164) 20: 50 250
//165) shotmode
//166) 27:
//167) 28:
//168) 17:
//169) tag boss3
\170) ( , )
//171) 35
//172) : , 600 «I'll come back»
//173) 33:
//174)
//175) HealthBar
//176) ( damage-
//177) 27:
0) (0)
1) (2)
2) (2)
3) (1)
4) (1)
5) (1)
6) (1)
7) (1)
8) (2)
9) (1)
10) (0)
11) (1)
(13)
12) (0)
13) (2)
14) (2)
15) (0)
16) (0)
17) (1)
18) (1)
19) (3)
20) (0)
21) (3)
22) (1)
(13)
23) (1)
24) (1)
25) (0)
26) (0)
27) (0)
28) (3)
29) (1)
30) (2)
31) (0)
32) (0)
33) (1)
34) (1)
(10)
Und was beim Zerlegen Sinn macht, sind die Mängel. Und nicht kleine, die auf Fehler zurückzuführen sind, sondern große, die die gröbsten Fehler in der Spielleistung darstellen. Ich möchte auch darauf hinweisen, dass ich mit Fehlern keine Fehler meine. Das Spiel hat viele Nachteile, das ist verständlich, aber ich möchte die Dinge herausfinden, die ich reparieren oder verhindern könnte, dass sie erstellt werden.Was sind meine Hauptmängel?- . , . 2 3-4 . , , : 10 . , . .
- , . , , , , .
- . , « » 60% . , .
LokalisierungAufgrund des vollwertigen Szenarios hat sich das Volumen des lokalisierten Textes ungefähr um das 30-fache erhöht. Die Übersetzungstechnik hat sich jedoch nicht geändert: Während ich über Google Translate übersetze, fahre ich fort. Erst habe ich direkt aus dem Russischen übersetzt, jetzt übersetze ich ins Englische, korrigiere Fehler und schon daraus in andere Sprachen. Außerdem verringerte sich die Anzahl der Sprachen: Wenn das ursprüngliche Spiel 18 Sprachen hatte und seine Seite in ALLE von Google unterstützten Sprachen übersetzt wurde, wurde die Fortsetzung in nur 10 Sprachen übertragen: Was ist im Spiel, was ist auf der Seite (und dies ist die einzige Fortsetzung) dem Original unterlegen).Für normale Notenterminals habe ich ein ziemlich großes Schema für die Arbeit mit Text erstellt. Kurz gesagt, anstelle von einfachen Zeichenfolgen gab es eine spezielle Klasse für die Arbeit mit verschiedenen Sprachen:Skript StringLanguageMinimize [System.Serializable] public class StringLanguageMinimize { public string english = ""; public string spanish = ""; public string italian = ""; public string german = ""; public string russian = ""; public string french = ""; public string portuguese = ""; public string korean = ""; public string chinese = ""; public string japan = ""; public string GetString() { string ret = ""; switch (PlayerPrefs.GetString("language")) { case "english": ret = english; break; case "spanish": ret = spanish; break; case "italian": ret = italian; break; case "german": ret = german; break; case "russian": ret = russian; break; case "french": ret = french; break; case "portuguese": ret = portuguese; break; case "korean": ret = korean; break; case "chinese": ret = chinese; break; case "japan": ret = japan; break; } return ret; } }
Und genau die gleiche Klasse für Terminals:Skript-Terminal [System.Serializable] public class StringLanguage { [TextArea] public string english = ""; [TextArea] public string spanish = ""; [TextArea] public string italian = ""; [TextArea] public string german = ""; [TextArea] public string russian = ""; [TextArea] public string french = ""; [TextArea] public string portuguese = ""; [TextArea] public string korean = ""; [TextArea] public string chinese = ""; [TextArea] public string japan = ""; public string GetString() { string ret = ""; switch (PlayerPrefs.GetString("language")) { case "english": ret = english; break; case "spanish": ret = spanish; break; case "italian": ret = italian; break; case "german": ret = german; break; case "russian": ret = russian; break; case "french": ret = french; break; case "portuguese": ret = portuguese; break; case "korean": ret = korean; break; case "chinese": ret = chinese; break; case "japan": ret = japan; break; } return ret; } }
Als nächstes kam der Terminal-Triggercode:Eingabe von Skripttipps using UnityEngine; public class TipsInput : MonoBehaviour { public int idTips = 0; public bool isPress2Read = true; public bool oneTime = true; private bool active = true; public GameObject[] copys; private Data data; private Press2Read p2r; private TipsInput ti; private void Awake() { data = GameObject.FindWithTag("MainCamera").GetComponent<Data>(); p2r = GameObject.FindWithTag("Press2Read").GetComponent<Press2Read>(); ti = GetComponent<TipsInput>(); } public void OnCollisionEnter2D(Collision2D collision) { if (collision.transform.CompareTag("Player")) { if (isPress2Read == false && active == true) { Disable(); data.SetDialoge(idTips); if (copys.Length != 0) { for (int i = 0; i < copys.Length; i++) { copys[i].GetComponent<TipsInput>().Disable(); } } } else if (isPress2Read == true) { p2r.Active(ti); } } } public void OnCollisionExit2D(Collision2D collision) { if (isPress2Read == true) { p2r.DeActive(); } } public void Disable() { if (oneTime == true) { active = false; } return; } }
Wichtige Klassendaten:Daten using UnityEngine; using UnityEngine.UI; using System.Collections; public class Data : GlobalFunctions { public Dialoge[] dialoges; public DeadPhrases[] deadPhrases; public GamePlay[] gameplay; [Space] public Tips tips; public AudioBase audioBase; public TipsGamePlay gamePlayTips; public Image slowmobonus; public Text fpsText; public float scaleTips = 1f; public float scaleGameUI = 1f; public float scaleSlowMo = 1f; private float speed = 0f; private float target = 1f; private float timeDuration = 1f; private int updFPS = 0; public void Awake() { scaleTips = scaleGameUI = scaleSlowMo = 1f; slowmobonus.color = new Color(0f, 0f, 0f, 0f); } public void Start() { StartCoroutine(SecFPSUpdate()); } public void SetDialoge(int id) { if (dialoges.Length != 0) { tips.SetActiveTrue(dialoges[id].dialogeStrings, dialoges[id].name); } } public void FalseP2R() { tips.SetFalse(); } public string GetDeadPhrase(string typeDead) { int idType = -1; for (int i = 0; i < deadPhrases.Length; i++) { if (deadPhrases[i].typeDead == typeDead) { idType = i; break; } } if (idType == -1) { return typeDead; } int rand = Random.Range(0, deadPhrases[idType].deadPhrases.Length); return deadPhrases[idType].deadPhrases[rand].GetString(); } public string GetDeadPhrase2() { string ret = ""; switch (PlayerPrefs.GetString("language")) { case "english": ret = "Tap to continue"; break; case "spanish": ret = "Pulse para continuar"; break; case "italian": ret = "Tocca per continuare"; break; case "german": ret = "Tippen Sie, um fortzufahren"; break; case "russian": ret = " "; break; case "french": ret = "Appuyez sur pour continuer"; break; case "portuguese": ret = "Clique para continuar"; break; case "korean": ret = "계속하려면 탭하세요"; break; case "chinese": ret = "点按即可继续"; break; case "japan": ret = "タップして続行します"; break; } return ret; } public void PauseGameUI(float time) { scaleGameUI = time; Update(); audioBase.UpdateSound(); } public void SetGamePlayTips(int id) { if (id == -1) { gamePlayTips.SetActiveTrueSaved(); } else { gamePlayTips.SetActiveTrue(gameplay[id]); } } public void SlowMo(float timeDuration2, float setSlowMo, float speed2) { speed = speed2; target = setSlowMo; timeDuration = timeDuration2; Update(); audioBase.UpdateSound(); } public void SlowMo(float timeDuration2) { scaleSlowMo = 0.1f; float sb = (1f - scaleSlowMo) * 0.3921569f; slowmobonus.color = new Color(0f, 0f, 0f, sb); Update(); audioBase.UpdateSound(); } public IEnumerator EndAnim(float timeDuration) { yield return new WaitForSeconds(timeDuration); End(); } public void End() { scaleSlowMo = 1f; float sb = (1f - scaleSlowMo) * 0.3921569f; slowmobonus.color = new Color(0f, 0f, 0f, sb); Update(); audioBase.UpdateSound(); } public void End2(float timeDuration2) { if (timeDuration2 == 0) { End(); return; } StartCoroutine(EndAnim(timeDuration2)); } private void Update() { Time.timeScale = scaleTips * scaleSlowMo * scaleGameUI; Time.fixedDeltaTime = 0.03f * scaleSlowMo * scaleTips; updFPS = updFPS + 1; return; } private IEnumerator SecFPSUpdate() { yield return new WaitForSeconds(1f); fpsText.text = "FPS: " + updFPS; updFPS = 0; StartCoroutine(SecFPSUpdate()); } }
Und die Hauptklasse Tips, die für den Betrieb des Terminals verantwortlich ist:Skript-Tipps using System.Collections; using UnityEngine.UI; using UnityEngine; public class Tips : GlobalFunctions { public Data data; public Press2Read p2r; public GameUI gameUI; public GameObject obj; public AudioClip setClip; public Text nameText; public Text txt; private int textID = 0; private int textsID = 0; private AudioBase audioBase; private DialogeString textActive; private DialogeString[] textsActive; private bool isMass = false; [TextArea] public string end = ""; [TextArea] public string endPast = ""; public void Start() { audioBase = GameObject.FindWithTag("MainCamera").GetComponent<AudioBase>(); data.scaleTips = 1f; obj.SetActive(false); txt.text = ""; } public void SetActiveTrue(DialogeString text, StringLanguageMinimize name) { data.scaleTips = 0.1f; audioBase.layerSounds[0].volume /= 10f; obj.SetActive(true); nameText.text = name.GetString(); gameUI.pauseButton.SetActive(false); textActive = text; isMass = false; StartCoroutine(TimerFalse()); } public void SetActiveTrue(DialogeString[] texts, StringLanguageMinimize name) { data.scaleTips = 0.1f; audioBase.layerSounds[0].volume /= 10f; obj.SetActive(true); nameText.text = name.GetString(); gameUI.pauseButton.SetActive(false); textsActive = texts; isMass = true; StartCoroutine(TimersFalse()); } public IEnumerator TimerFalse(float time = 0.02f) { yield return new WaitForSecondsRealtime(time); string ds = textActive.dialogeString.GetString(); if (textID < ds.Length && ds != end) { audioBase.SetSound(setClip, 1, 0.5f, TypePlaying.Sound, false); end = end + ds.Substring(textID, 1); txt.text = endPast + end; textID = textID + 1; if (textID + 1 != ds.Length && ds != end) { if (ds.Substring(textID + 1, 1) == ",") { StartCoroutine(TimersFalse(0.1f)); } else if (ds.Substring(textID + 1, 1) == ".") { StartCoroutine(TimersFalse(0.15f)); } else if (ds.Substring(textID + 1, 1) == "?") { StartCoroutine(TimersFalse(0.15f)); } else if (ds.Substring(textID + 1, 1) == ".") { StartCoroutine(TimersFalse(0.15f)); } else { StartCoroutine(TimersFalse()); } } else { StartCoroutine(TimersFalse()); } } else { endPast = txt.text; if (textActive.isSkip) { if (textActive.skipOffset == 0f) { SetActiveFalse(); } else { IsSkip(textActive.skipOffset); } } } } public IEnumerator TimersFalse(float time = 0.02f) { yield return new WaitForSecondsRealtime(time); string ds = textsActive[textsID].dialogeString.GetString(); if (textID < ds.Length && ds != end) { audioBase.SetSound(setClip, 1, 0.5f, TypePlaying.Sound, false); end = end + ds.Substring(textID, 1); txt.text = endPast + end; textID = textID + 1; string ds1 = textsActive[textsID].dialogeString.GetString(); if (textID + 1 != ds1.Length && ds1 != end) { if (ds1.Substring(textID + 1, 1) == ",") { StartCoroutine(TimersFalse(0.1f)); } else if (ds1.Substring(textID + 1, 1) == ".") { StartCoroutine(TimersFalse(0.15f)); } else if (ds1.Substring(textID + 1, 1) == "?") { StartCoroutine(TimersFalse(0.15f)); } else if (ds1.Substring(textID + 1, 1) == "!") { StartCoroutine(TimersFalse(0.15f)); } else { StartCoroutine(TimersFalse()); } } else { StartCoroutine(TimersFalse()); } } else { endPast = txt.text; if (textsActive[textsID].isSkip) { if (textsActive[textsID].skipOffset == 0f) { SetActiveFalse(); } else { IsSkip(textsActive[textsID].skipOffset); } } } } public IEnumerator IsSkip(float time) { yield return new WaitForSecondsRealtime(time); SetActiveFalse(); } public void SetFalse() { obj.SetActive(false); gameUI.pauseButton.SetActive(true); end = ""; endPast = ""; txt.text = ""; textID = textsID = 0; data.scaleTips = 1f; audioBase.layerSounds[0].volume *= 10f; } public void SetActiveFalse() { if (isMass == false) { if (textActive.dialogeString.GetString() != end) { end = textActive.dialogeString.GetString(); if (textActive.isSkip) { SetActiveFalse(); } } else { obj.SetActive(false); gameUI.pauseButton.SetActive(true); end = ""; data.scaleTips = 1f; audioBase.layerSounds[0].volume *= 10f; } } else { if (textsActive[textsID].dialogeString.GetString() != end) { if (textsActive[textsID].isStep == true) { txt.text = end = textsActive[textsID].dialogeString.GetString(); if (textsActive[textsID].isSkip) { SetActiveFalse(); } } else { end = textsActive[textsID].dialogeString.GetString(); txt.text = endPast + end; } } else { if (textsID != textsActive.Length - 1) { textsID = textsID + 1; textID = 0; end = ""; if (textsActive[textsID].isStep == true) { endPast = ""; } StartCoroutine(TimersFalse()); } else { obj.SetActive(false); gameUI.pauseButton.SetActive(true); p2r.UnTap(); end = ""; endPast = ""; txt.text = ""; textID = textsID = 0; data.scaleTips = 1f; audioBase.layerSounds[0].volume *= 10f; } } } } }
Ich entschied, dass es deprimierend wäre, wenn der Text nur angezeigt würde, und machte daher mit Hilfe von IEnumerator eine Emulation des Schreibens des Textes (genau der gleiche Effekt am Ende).ErscheinungsdatumUrsprünglich war mein Plan, das Spiel am 1. September zu veröffentlichen. Und so tat ich es: Im letzten Moment stellte sich heraus, dass ich am Ende 4 Fehler hatte (und es wurde auch nicht übersetzt), es schnell behoben und das Spiel am Abend angelegt. Leider verzögerte sich die Prüfung um 7 Tage, da ich mich entschied, das Angebot manuell mit etwas zu prüfen. Höchstwahrscheinlich befindet sich die Angelegenheit in dem Konto, das "definiert" wurde und bereits manuell durch Moderation überprüft wurde.PR war für mich viel schwieriger als die Vorbereitung auf die Veröffentlichung, da es kein Geld und keine Verbindungen gab, aber ich wollte das Spiel vertreiben. Deshalb habe ich einfache Methoden angewendet: Ich habe alles an Freunde in VK geworfen, Beiträge auf Reddit erstellt, es in das Angebot für Websites mit Handyspielen aufgenommen, versucht, die Autoren von Musik zu kontaktieren usw. Und das ergab ein kleines Ergebnis:ZusammenfassungÜberraschenderweise verbrachte ich an dem Tag, an dem ich diesen Artikel veröffentlichte, 3 Jahre in der IT! Und trotz meines Alters von 16 Jahren habe ich mir an diesem Tag, als ich 13 Jahre alt war, das Ziel gesetzt: Programmieren zu lernen und ein Traumspiel zu kreieren. Und von diesem Moment an wurde mein Traum bis zu einem gewissen Grad wahr.Was ist mit dem Spiel? Ich bin zufrieden mit ihr Nein, wirklich, ich habe von nichts so viele nützliche Informationen und Erfahrungen erhalten wie von diesem Projekt. Nun, die Qualität des Spiels könnte deutlich höher sein, aber auch das, was mir schon gut tut. Außerdem ist dieses Spiel für mich etwas Persönliches und es wäre respektlos, dieses Spiel zuerst zu monetarisieren. Daher gibt es darin keine Werbung, Spende und es gibt keine kostenpflichtige VersionDanach möchte ich weiterhin im Spielentwickler sein. Aber die Lebensumstände sind so, dass es nicht mehr möglich ist. Und um normal Programmierer zu werden, brauche ich Entwicklung, persönliches Wachstum über mich. Ich weiß nicht, was ich jetzt lernen soll und wohin ich gehen soll, aber eines weiß ich ganz genau: Dies ist höchstwahrscheinlich mein letztes Spiel auf der Unity Engine.Danke für wenigstens etwas Aufmerksamkeit. Wenn sich meine Geschichte als chaotisch herausstellte, stellen Sie Fragen, ich werde klarstellen, dass ich kann.PS: Jemand mochte den letzten Trailer:Und hier ist der Trailer zu diesem Spiel: