Endless Runner-Tutorial für Unity

In Videospielen gibt es immer ein Ende, egal wie groß die Welt ist. Einige Spiele versuchen jedoch, die unendliche Welt nachzuahmen. Solche Spiele fallen in die Kategorie Endless Runner.

Endless Runner ist eine Art Spiel, bei dem sich der Spieler ständig vorwärts bewegt, dabei Punkte sammelt und Hindernissen ausweicht. Das Hauptziel besteht darin, das Ende des Levels zu erreichen, ohne in die Hindernisse zu fallen oder mit ihnen zusammenzustoßen. Oftmals wiederholt sich das Level jedoch unendlich und erhöht den Schwierigkeitsgrad schrittweise, bis der Spieler mit dem Hindernis kollidiert.

Subway Surfers-Gameplay

Wenn man bedenkt, dass selbst moderne Computer/Spielgeräte über eine begrenzte Rechenleistung verfügen, ist es unmöglich, eine wirklich unendliche Welt zu schaffen.

Wie erzeugen manche Spiele die Illusion einer unendlichen Welt? Die Antwort liegt in der Wiederverwendung der Bausteine ​​(auch Objekt-Pooling genannt). Mit anderen Worten: Sobald der Block hinter oder außerhalb der Kameraansicht liegt, wird er nach vorne verschoben.

Um ein Endlosläufer-Spiel in Unity zu erstellen, müssen wir eine Plattform mit Hindernissen und einem Spieler-Controller erstellen.

Schritt 1: Erstellen Sie die Plattform

Wir beginnen mit der Erstellung einer gekachelten Plattform, die später im Prefab gespeichert wird:

  • Erstellen Sie ein neues GameObject und nennen Sie es "TilePrefab"
  • Neuen Würfel erstellen (GameObject -> 3D-Objekt -> Würfel)
  • Bewegen Sie den Würfel innerhalb des "TilePrefab"-Objekts, ändern Sie seine Position in (0, 0, 0) und skalieren Sie ihn auf (8, 0,4, 20).

  • Optional können Sie Schienen an den Seiten hinzufügen, indem Sie zusätzliche Würfel erstellen, wie folgt:

Für die Hindernisse werde ich 3 Hindernisvarianten haben, aber Sie können so viele machen, wie Sie brauchen:

  • Erstellen Sie 3 GameObjects innerhalb des "TilePrefab"-Objekts und nennen Sie sie "Obstacle1", "Obstacle2" und "Obstacle3"
  • Erstellen Sie für das erste Hindernis einen neuen Würfel und verschieben Sie ihn innerhalb des "Obstacle1"-Objekts
  • Skalieren Sie den neuen Würfel auf ungefähr die gleiche Breite wie die Plattform und verkleinern Sie seine Höhe (der Spieler muss springen, um diesem Hindernis auszuweichen).
  • Erstellen Sie ein neues Material, nennen Sie es "RedMaterial", ändern Sie seine Farbe in Rot und weisen Sie es dann dem Würfel zu (dies dient nur dazu, das Hindernis von der Hauptplattform zu unterscheiden).

  • Erstellen Sie für "Obstacle2" ein paar Würfel und platzieren Sie sie in einer dreieckigen Form, so dass unten ein freier Raum bleibt (der Spieler muss sich ducken, um diesem Hindernis auszuweichen).

  • Und schließlich wird "Obstacle3" ein Duplikat von "Obstacle1" und "Obstacle2" sein, zusammen kombiniert

  • Wählen Sie nun alle Objekte innerhalb von Hindernissen aus und ändern Sie deren Tag in "Finish". Dies wird später benötigt, um die Kollision zwischen Spieler und Hindernis zu erkennen.

Um eine unendliche Plattform zu generieren, benötigen wir einige Skripte, die das Objekt-Pooling und die Hindernisaktivierung verwalten:

  • Erstellen Sie ein neues Skript, nennen Sie es "SC_PlatformTile" und fügen Sie den folgenden Code ein:

SC_PlatformTile.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SC_PlatformTile : MonoBehaviour
{
    public Transform startPoint;
    public Transform endPoint;
    public GameObject[] obstacles; //Objects that contains different obstacle types which will be randomly activated

    public void ActivateRandomObstacle()
    {
        DeactivateAllObstacles();

        System.Random random = new System.Random();
        int randomNumber = random.Next(0, obstacles.Length);
        obstacles[randomNumber].SetActive(true);
    }

    public void DeactivateAllObstacles()
    {
        for (int i = 0; i < obstacles.Length; i++)
        {
            obstacles[i].SetActive(false);
        }
    }
}
  • Erstellen Sie ein neues Skript, nennen Sie es "SC_GroundGenerator" und fügen Sie den folgenden Code ein:

SC_GroundGenerator.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class SC_GroundGenerator : MonoBehaviour
{
    public Camera mainCamera;
    public Transform startPoint; //Point from where ground tiles will start
    public SC_PlatformTile tilePrefab;
    public float movingSpeed = 12;
    public int tilesToPreSpawn = 15; //How many tiles should be pre-spawned
    public int tilesWithoutObstacles = 3; //How many tiles at the beginning should not have obstacles, good for warm-up

    List<SC_PlatformTile> spawnedTiles = new List<SC_PlatformTile>();
    int nextTileToActivate = -1;
    [HideInInspector]
    public bool gameOver = false;
    static bool gameStarted = false;
    float score = 0;

    public static SC_GroundGenerator instance;

    // Start is called before the first frame update
    void Start()
    {
        instance = this;

        Vector3 spawnPosition = startPoint.position;
        int tilesWithNoObstaclesTmp = tilesWithoutObstacles;
        for (int i = 0; i < tilesToPreSpawn; i++)
        {
            spawnPosition -= tilePrefab.startPoint.localPosition;
            SC_PlatformTile spawnedTile = Instantiate(tilePrefab, spawnPosition, Quaternion.identity) as SC_PlatformTile;
            if(tilesWithNoObstaclesTmp > 0)
            {
                spawnedTile.DeactivateAllObstacles();
                tilesWithNoObstaclesTmp--;
            }
            else
            {
                spawnedTile.ActivateRandomObstacle();
            }
            
            spawnPosition = spawnedTile.endPoint.position;
            spawnedTile.transform.SetParent(transform);
            spawnedTiles.Add(spawnedTile);
        }
    }

    // Update is called once per frame
    void Update()
    {
        // Move the object upward in world space x unit/second.
        //Increase speed the higher score we get
        if (!gameOver && gameStarted)
        {
            transform.Translate(-spawnedTiles[0].transform.forward * Time.deltaTime * (movingSpeed + (score/500)), Space.World);
            score += Time.deltaTime * movingSpeed;
        }

        if (mainCamera.WorldToViewportPoint(spawnedTiles[0].endPoint.position).z < 0)
        {
            //Move the tile to the front if it's behind the Camera
            SC_PlatformTile tileTmp = spawnedTiles[0];
            spawnedTiles.RemoveAt(0);
            tileTmp.transform.position = spawnedTiles[spawnedTiles.Count - 1].endPoint.position - tileTmp.startPoint.localPosition;
            tileTmp.ActivateRandomObstacle();
            spawnedTiles.Add(tileTmp);
        }

        if (gameOver || !gameStarted)
        {
            if (Input.GetKeyDown(KeyCode.Space))
            {
                if (gameOver)
                {
                    //Restart current scene
                    Scene scene = SceneManager.GetActiveScene();
                    SceneManager.LoadScene(scene.name);
                }
                else
                {
                    //Start the game
                    gameStarted = true;
                }
            }
        }
    }

    void OnGUI()
    {
        if (gameOver)
        {
            GUI.color = Color.red;
            GUI.Label(new Rect(Screen.width / 2 - 100, Screen.height / 2 - 100, 200, 200), "Game Over\nYour score is: " + ((int)score) + "\nPress 'Space' to restart");
        }
        else
        {
            if (!gameStarted)
            {
                GUI.color = Color.red;
                GUI.Label(new Rect(Screen.width / 2 - 100, Screen.height / 2 - 100, 200, 200), "Press 'Space' to start");
            }
        }


        GUI.color = Color.green;
        GUI.Label(new Rect(5, 5, 200, 25), "Score: " + ((int)score));
    }
}
  • Hängen Sie das SC_PlatformTile-Skript an das "TilePrefab"-Objekt an
  • Weisen Sie die Objekte "Obstacle1", "Obstacle2" und "Obstacle3" dem Obstacles-Array zu

Für den Startpunkt und den Endpunkt müssen wir zwei GameObjects erstellen, die jeweils am Anfang und am Ende der Plattform platziert werden sollten:

  • Weisen Sie Startpunkt- und Endpunktvariablen in SC_PlatformTile zu

  • Speichern Sie das "TilePrefab"-Objekt in Prefab und entfernen Sie es aus der Szene
  • Erstellen Sie ein neues GameObject und nennen Sie es "_GroundGenerator"
  • Hängen Sie das SC_GroundGenerator-Skript an das "_GroundGenerator"-Objekt an
  • Ändern Sie die Position der Hauptkamera auf (10, 1, -9) und ihre Drehung auf (0, -55, 0).
  • Erstelle ein neues GameObject, nenne es "StartPoint" und ändere seine Position in (0, -2, -15)
  • Wählen Sie das Objekt "_GroundGenerator" aus und weisen Sie in SC_GroundGenerator die Variablen „Hauptkamera“, „Startpunkt“ und „Kachel-Prefab“ zu

Drücken Sie nun „Play“ und beobachten Sie, wie sich die Plattform bewegt. Sobald die Plattformkachel die Kameraansicht verlässt, wird sie ans Ende zurückbewegt, wobei ein zufälliges Hindernis aktiviert wird, wodurch die Illusion eines unendlichen Levels entsteht (Springe zu 0:11).

Die Kamera muss ähnlich wie das Video platziert werden, sodass die Plattformen in Richtung der Kamera und dahinter verlaufen, andernfalls wiederholen sich die Plattformen nicht.

Sharp Coder Videoplayer

Schritt 2: Erstellen Sie den Player

Die Spielerinstanz ist eine einfache Kugel mit einem Controller und der Fähigkeit zu springen und sich zu ducken.

  • Erstellen Sie eine neue Kugel (GameObject -> 3D-Objekt -> Kugel) und entfernen Sie die Sphere Collider-Komponente
  • Weisen Sie ihm das zuvor erstellte "RedMaterial" zu
  • Erstellen Sie ein neues GameObject und nennen Sie es "Player"
  • Bewegen Sie die Kugel innerhalb des "Player"-Objekts und ändern Sie ihre Position in (0, 0, 0).
  • Erstellen Sie ein neues Skript, nennen Sie es "SC_IRPlayer" und fügen Sie den folgenden Code ein:

SC_IRPlayer.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Rigidbody))]

public class SC_IRPlayer : MonoBehaviour
{
    public float gravity = 20.0f;
    public float jumpHeight = 2.5f;

    Rigidbody r;
    bool grounded = false;
    Vector3 defaultScale;
    bool crouch = false;

    // Start is called before the first frame update
    void Start()
    {
        r = GetComponent<Rigidbody>();
        r.constraints = RigidbodyConstraints.FreezePositionX | RigidbodyConstraints.FreezePositionZ;
        r.freezeRotation = true;
        r.useGravity = false;
        defaultScale = transform.localScale;
    }

    void Update()
    {
        // Jump
        if (Input.GetKeyDown(KeyCode.W) && grounded)
        {
            r.velocity = new Vector3(r.velocity.x, CalculateJumpVerticalSpeed(), r.velocity.z);
        }

        //Crouch
        crouch = Input.GetKey(KeyCode.S);
        if (crouch)
        {
            transform.localScale = Vector3.Lerp(transform.localScale, new Vector3(defaultScale.x, defaultScale.y * 0.4f, defaultScale.z), Time.deltaTime * 7);
        }
        else
        {
            transform.localScale = Vector3.Lerp(transform.localScale, defaultScale, Time.deltaTime * 7);
        }
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        // We apply gravity manually for more tuning control
        r.AddForce(new Vector3(0, -gravity * r.mass, 0));

        grounded = false;
    }

    void OnCollisionStay()
    {
        grounded = true;
    }

    float CalculateJumpVerticalSpeed()
    {
        // From the jump height and gravity we deduce the upwards speed 
        // for the character to reach at the apex.
        return Mathf.Sqrt(2 * jumpHeight * gravity);
    }

    void OnCollisionEnter(Collision collision)
    {
        if(collision.gameObject.tag == "Finish")
        {
            //print("GameOver!");
            SC_GroundGenerator.instance.gameOver = true;
        }
    }
}
  • Hängen Sie das SC_IRPlayer-Skript an das "Player"-Objekt an (Sie werden feststellen, dass es eine weitere Komponente namens Rigidbody hinzugefügt hat).
  • Fügen Sie die BoxCollider-Komponente zum "Player"-Objekt hinzu

  • Platzieren Sie das "Player"-Objekt etwas über dem "StartPoint"-Objekt, direkt vor der Kamera

Drücken Sie Play und verwenden Sie die W-Taste zum Springen und die S-Taste zum Hocken. Das Ziel besteht darin, rote Hindernisse zu vermeiden:

Sharp Coder Videoplayer

Überprüfen Sie diesen Horizon Bending Shader.

Quelle
📁EndlessRunner.unitypackage26.68 KB
Empfohlene Artikel
Erstellen eines 2D-Brick-Breaker-Spiels in Unity
Erstellen eines Schiebepuzzlespiels in Unity
So erstellen Sie ein von Flappy Bird inspiriertes Spiel in Unity
Minispiel in Unity | WÜRFELvermeiden
Tutorial für das Match-3-Puzzlespiel in Unity
Farm-Zombies | Erstellung eines 2D-Plattformspiels in Unity
So erstellen Sie ein Schlangenspiel in Unity