Tutorial de corredor sin fin para Unity

En los videojuegos, por muy grande que sea el mundo, siempre tiene un final. Pero algunos juegos intentan emular el mundo infinito, tales juegos entran en la categoría llamada Endless Runner.

Endless Runner es un tipo de juego en el que el jugador avanza constantemente mientras acumula puntos y evita obstáculos. El objetivo principal es llegar al final del nivel sin caer ni chocar con los obstáculos, pero muchas veces el nivel se repite infinitamente, aumentando gradualmente la dificultad, hasta que el jugador choca con el obstáculo.

Jugabilidad de Subway Surfers

Teniendo en cuenta que incluso las computadoras y dispositivos de juego modernos tienen una potencia de procesamiento limitada, es imposible crear un mundo verdaderamente infinito.

Entonces, ¿cómo crean algunos juegos la ilusión de un mundo infinito? La respuesta es reutilizar los bloques de construcción (también conocido como agrupación de objetos); en otras palabras, tan pronto como el bloque pasa detrás o fuera de la vista de la cámara, se mueve al frente.

Para crear un juego de corredores sin fin en Unity, necesitaremos crear una plataforma con obstáculos y un controlador de jugador.

Paso 1: crear la plataforma

Comenzamos creando una plataforma en mosaico que luego se almacenará en el Prefab:

  • Crea un nuevo GameObject y llámalo "TilePrefab"
  • Crear nuevo cubo (GameObject -> Objeto 3D -> Cubo)
  • Mueva el Cubo dentro del objeto "TilePrefab", cambie su posición a (0, 0, 0) y escale a (8, 0.4, 20)

  • Opcionalmente, puedes agregar rieles a los lados creando cubos adicionales, como este:

Para los obstáculos, tendré 3 variaciones de obstáculos, pero puedes hacer tantas como necesites:

  • Crea 3 GameObjects dentro del objeto "TilePrefab" y llámalos "Obstacle1", "Obstacle2" y "Obstacle3"
  • Para el primer obstáculo, crea un nuevo Cubo y muévelo dentro del objeto "Obstacle1"
  • Escale el nuevo Cubo aproximadamente al mismo ancho que la plataforma y reduzca su altura (el jugador deberá saltar para evitar este obstáculo)
  • Crea un nuevo Material, llámalo "RedMaterial" y cambia su color a Rojo, luego asígnalo al Cubo (esto es solo para que el obstáculo se distinga de la plataforma principal)

  • Para "Obstacle2" crea un par de cubos y colócalos en forma triangular, dejando un espacio abierto en la parte inferior (el jugador deberá agacharse para evitar este obstáculo)

  • Y por último, "Obstacle3" será un duplicado de "Obstacle1" y "Obstacle2", combinados.

  • Ahora seleccione todos los Objetos dentro de los Obstáculos y cambie su etiqueta a "Finish", esto será necesario más adelante para detectar la colisión entre el Jugador y el Obstáculo.

Para generar una plataforma infinita necesitaremos un par de scripts que manejarán la agrupación de objetos y la activación de obstáculos:

  • Crea un nuevo script, llámalo "SC_PlatformTile" y pega el siguiente código dentro de él:

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 nuevo script, llámalo "SC_GroundGenerator" y pega el siguiente código dentro de él:

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));
    }
}
  • Adjunte el script SC_PlatformTile al objeto "TilePrefab"
  • Asignar objetos "Obstacle1", "Obstacle2" y "Obstacle3" a la matriz de obstáculos

Para el punto de inicio y el punto final, necesitamos crear 2 GameObjects que deben colocarse al inicio y al final de la plataforma respectivamente:

  • Asignar variables de punto inicial y punto final en SC_PlatformTile

  • Guarde el objeto "TilePrefab" en Prefab y elimínelo de la escena
  • Crea un nuevo GameObject y llámalo "_GroundGenerator"
  • Adjunte el script SC_GroundGenerator al objeto "_GroundGenerator"
  • Cambie la posición de la cámara principal a (10, 1, -9) y cambie su rotación a (0, -55, 0)
  • Crea un nuevo GameObject, llámalo "StartPoint" y cambia su posición a (0, -2, -15)
  • Seleccione el objeto "_GroundGenerator" y en SC_GroundGenerator asigne las variables Cámara principal, Punto de inicio y Prefab de mosaico

Ahora presiona Play y observa cómo se mueve la plataforma. Tan pronto como el mosaico de la plataforma sale de la vista de la cámara, regresa al final con un obstáculo aleatorio activado, creando la ilusión de un nivel infinito (salte a 0:11).

La Cámara debe colocarse de manera similar al video, de modo que las plataformas vayan hacia la Cámara y detrás de ella, de lo contrario las plataformas no se repetirán.

Sharp Coder Reproductor de video

Paso 2: crea el reproductor

La Instancia del jugador será una Esfera simple que usará un controlador con la capacidad de saltar y agacharse.

  • Cree una nueva Esfera (GameObject -> Objeto 3D -> Esfera) y elimine su componente Sphere Collider
  • Asígnale "RedMaterial" creado previamente
  • Crea un nuevo GameObject y llámalo "Player"
  • Mueva la Esfera dentro del objeto "Player" y cambie su posición a (0, 0, 0)
  • Cree un nuevo script, llámelo "SC_IRPlayer" y pegue el siguiente código dentro de él:

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;
        }
    }
}
  • Adjunte el script SC_IRPlayer al objeto "Player" (notará que agregó otro componente llamado Rigidbody)
  • Agregue el componente BoxCollider al objeto "Player"

  • Coloque el objeto "Player" ligeramente encima del objeto "StartPoint", justo en frente de la cámara.

Presiona Play y usa la tecla W para saltar y la tecla S para agacharte. El objetivo es evitar los obstáculos rojos:

Sharp Coder Reproductor de video

Mira este Sombra de flexión del horizonte.

Fuente
📁EndlessRunner.unitypackage26.68 KB
Artículos sugeridos
Cómo hacer un juego inspirado en Flappy Bird en Unity
Tutorial para el juego de rompecabezas Match-3 en Unity
Minijuego en Unity | Flappy Cube
Cómo hacer un juego de serpientes en Unity
Creando un juego de romper ladrillos 2D en Unity
Creando un juego de rompecabezas deslizante en Unity
Minijuego en Unity | CUBEavoid