Tutorial Endless Runner per Unity

Nei videogiochi, non importa quanto sia grande il mondo, ha sempre una fine. Ma alcuni giochi cercano di emulare il mondo infinito e rientrano nella categoria chiamata Endless Runner.

Endless Runner è un tipo di gioco in cui il giocatore avanza costantemente raccogliendo punti ed evitando ostacoli. L'obiettivo principale è raggiungere la fine del livello senza cadere o scontrarsi con gli ostacoli, ma spesso il livello si ripete all'infinito, aumentando gradualmente la difficoltà, finché il giocatore non si scontra con l'ostacolo.

Gameplay di Subway Surfers

Considerando che anche i computer/dispositivi di gioco moderni hanno una potenza di elaborazione limitata, è impossibile creare un mondo veramente infinito.

Allora come fanno alcuni giochi a creare l'illusione di un mondo infinito? La risposta è riutilizzando i blocchi costitutivi (ovvero il pooling di oggetti), in altre parole, non appena il blocco va dietro o fuori dalla vista della telecamera, viene spostato in primo piano.

Per realizzare un gioco Endless Runner in Unity, dovremo creare una piattaforma con ostacoli e un controller per il giocatore.

Passaggio 1: creare la piattaforma

Iniziamo creando una piattaforma piastrellata che verrà successivamente archiviata nel Prefab:

  • Crea un nuovo GameObject e chiamalo "TilePrefab"
  • Crea un nuovo cubo (GameObject -> Oggetto 3D -> Cubo)
  • Sposta il cubo all'interno dell'oggetto "TilePrefab", cambia la sua posizione in (0, 0, 0) e ridimensionalo in (8, 0.4, 20)

  • Facoltativamente puoi aggiungere binari ai lati creando cubi aggiuntivi, come questo:

Per gli ostacoli, avrò 3 varianti di ostacolo, ma puoi realizzarne quante ne desideri:

  • Crea 3 GameObjects all'interno dell'oggetto "TilePrefab" e chiamali "Obstacle1", "Obstacle2" e "Obstacle3"
  • Per il primo ostacolo, crea un nuovo Cubo e spostalo all'interno dell'oggetto "Obstacle1"
  • Scala il nuovo cubo fino a raggiungere la stessa larghezza della piattaforma e riduci la sua altezza (il giocatore dovrà saltare per evitare questo ostacolo)
  • Crea un nuovo Materiale, chiamalo "RedMaterial" e cambia il suo colore in Rosso, quindi assegnalo al Cubo (questo serve solo per distinguere l'ostacolo dalla piattaforma principale)

  • Per "Obstacle2" crea una coppia di cubi e posizionali a forma triangolare, lasciando uno spazio aperto sul fondo (il giocatore dovrà accovacciarsi per evitare questo ostacolo)

  • E infine, "Obstacle3" sarà un duplicato di "Obstacle1" e "Obstacle2", combinati insieme

  • Ora seleziona tutti gli oggetti all'interno degli ostacoli e modifica il loro tag in "Finish", questo sarà necessario in seguito per rilevare la collisione tra giocatore e ostacolo.

Per generare una piattaforma infinita avremo bisogno di un paio di script che gestiranno il pooling degli oggetti e l'attivazione degli ostacoli:

  • Crea un nuovo script, chiamalo "SC_PlatformTile" e incolla al suo interno il codice seguente:

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);
        }
    }
}
  • Crea un nuovo script, chiamalo "SC_GroundGenerator" e incolla al suo interno il codice seguente:

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));
    }
}
  • Allega lo script SC_PlatformTile all'oggetto "TilePrefab"
  • Assegna gli oggetti "Obstacle1", "Obstacle2" e "Obstacle3" all'array Ostacoli

Per Start Point e End Point, dobbiamo creare 2 GameObjects che dovrebbero essere posizionati rispettivamente all'inizio e alla fine della piattaforma:

  • Assegnare le variabili Punto iniziale e Punto finale in SC_PlatformTile

  • Salva l'oggetto "TilePrefab" in Prefab e rimuovilo dalla scena
  • Crea un nuovo GameObject e chiamalo "_GroundGenerator"
  • Allega lo script SC_GroundGenerator all'oggetto "_GroundGenerator"
  • Cambia la posizione della telecamera principale in (10, 1, -9) e cambia la sua rotazione in (0, -55, 0)
  • Crea un nuovo GameObject, chiamalo "StartPoint" e cambia la sua posizione in (0, -2, -15)
  • Seleziona l'oggetto "_GroundGenerator" e in SC_GroundGenerator assegna le variabili Main Camera, Start Point e Tile Prefab

Ora premi Play e osserva come si muove la piattaforma. Non appena la tessera della piattaforma esce dalla visuale della telecamera, viene spostata indietro all'estremità con l'attivazione di un ostacolo casuale, creando l'illusione di un livello infinito (Vai a 0:11).

La telecamera deve essere posizionata in modo simile al video, in modo che le piattaforme vadano verso la telecamera e dietro di essa, altrimenti le piattaforme non si ripeteranno.

Sharp Coder Lettore video

Passaggio 2: crea il giocatore

L'istanza del giocatore sarà una semplice sfera che utilizza un controller con la capacità di saltare e accovacciarsi.

  • Crea una nuova Sfera (GameObject -> Oggetto 3D -> Sfera) e rimuovi il suo componente Sphere Collider
  • Assegnagli "RedMaterial" creato in precedenza
  • Crea un nuovo GameObject e chiamalo "Player"
  • Sposta la sfera all'interno dell'oggetto "Player" e cambia la sua posizione in (0, 0, 0)
  • Crea un nuovo script, chiamalo "SC_IRPlayer" e incolla al suo interno il codice seguente:

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;
        }
    }
}
  • Collega lo script SC_IRPlayer all'oggetto "Player" (noterai che ha aggiunto un altro componente chiamato Rigidbody)
  • Aggiungi il componente BoxCollider all'oggetto "Player"

  • Posiziona l'oggetto "Player" leggermente sopra l'oggetto "StartPoint", proprio di fronte alla fotocamera

Premi Gioca e usa il tasto W per saltare e il tasto S per abbassarti. L'obiettivo è evitare gli ostacoli rossi:

Sharp Coder Lettore video

Controlla questo Horizon Bending Shader.

Articoli suggeriti
Tutorial per il gioco di puzzle match-3 in Unity
Creazione di un gioco Brick Breaker 2D in Unity
Come realizzare un gioco ispirato a Flappy Bird in Unity
Come realizzare un gioco Snake in Unity
Creare un gioco puzzle a scorrimento in Unity
Mini gioco in Unity | CUBEavoid
Zombie della fattoria | Realizzazione di un gioco platform 2D in Unity