Учебное пособие по Endless Runner для Unity

В видеоиграх, каким бы большим ни был мир, у него всегда есть конец. Но некоторые игры пытаются имитировать бесконечный мир, такие игры подпадают под категорию Endless Runner.

Endless Runner — это тип игры, в которой игрок постоянно движется вперед, набирая очки и избегая препятствий. Основная цель - достичь конца уровня, не попадая в препятствия и не сталкиваясь с ними, но часто уровень повторяется бесконечно, постепенно увеличивая сложность, пока игрок не столкнется с препятствием.

Геймплей Subway Surfers

Учитывая, что даже современные компьютеры/игровые устройства имеют ограниченную вычислительную мощность, невозможно создать по-настоящему бесконечный мир.

Так как же некоторые игры создают иллюзию бесконечного мира? Ответ заключается в повторном использовании строительных блоков (также известном как объединение объектов), другими словами, как только блок оказывается позади или за пределами поля зрения камеры, он перемещается вперед.

Чтобы создать бесконечный раннер в Unity, нам нужно будет создать платформу с препятствиями и контроллер игрока.

Шаг 1: Создайте платформу

Начнем с создания плиточной платформы, которая позже будет сохранена в Prefab:

  • Создайте новый GameObject и назовите его "TilePrefab"
  • Создайте новый куб (GameObject -> 3D Object -> Cube)
  • Переместите куб внутри объекта "TilePrefab", измените его положение на (0, 0, 0) и масштабируйте до (8, 0,4, 20).

  • При желании вы можете добавить рельсы по бокам, создав дополнительные кубы, например:

Что касается препятствий, у меня будет 3 варианта препятствий, но вы можете сделать столько, сколько необходимо:

  • Создайте 3 GameObject внутри объекта "TilePrefab" и назовите их "Obstacle1", "Obstacle2" и "Obstacle3"
  • Для первого препятствия создайте новый куб и переместите его внутрь объекта "Obstacle1".
  • Масштабируйте новый куб примерно до той же ширины, что и платформа, и уменьшите его высоту (игроку придется подпрыгнуть, чтобы избежать этого препятствия).
  • Создайте новый Материал, назовите его "RedMaterial" и измените его цвет на Красный, затем назначьте его Кубу (это нужно для того, чтобы препятствие отличалось от основной платформы).

  • Для "Obstacle2" создайте пару кубиков и поместите их в треугольную форму, оставив одно свободное пространство внизу (игроку придется присесть, чтобы избежать этого препятствия).

  • И, наконец, "Obstacle3" будет дубликатом "Obstacle1" и "Obstacle2", объединенных вместе.

  • Теперь выберите все объекты внутри препятствий и измените их тег на "Finish", это понадобится позже для обнаружения столкновения между игроком и препятствием.

Чтобы создать бесконечную платформу, нам понадобится пара скриптов, которые будут обрабатывать пул объектов и активацию препятствий:

  • Создайте новый скрипт, назовите его "SC_PlatformTile" и вставьте в него приведенный ниже код:

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);
        }
    }
}
  • Создайте новый скрипт, назовите его "SC_GroundGenerator" и вставьте в него приведенный ниже код:

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));
    }
}
  • Прикрепите скрипт SC_PlatformTile к объекту "TilePrefab".
  • Назначьте объекты "Obstacle1", "Obstacle2" и "Obstacle3" массиву препятствий.

Для начальной и конечной точек нам нужно создать 2 GameObject, которые должны быть размещены в начале и конце платформы соответственно:

  • Назначьте переменные Start Point и End Point в SC_PlatformTile.

  • Сохраните объект "TilePrefab" в Prefab и удалите его из сцены.
  • Создайте новый GameObject и назовите его "_GroundGenerator"
  • Прикрепите скрипт SC_GroundGenerator к объекту "_GroundGenerator".
  • Измените положение основной камеры на (10, 1, -9) и измените ее вращение на (0, -55, 0).
  • Создайте новый GameObject, назовите его "StartPoint" и измените его позицию на (0, -2, -15).
  • Выберите объект "_GroundGenerator" и в SC_GroundGenerator назначьте переменные Main Camera, Start Point и Tile Prefab.

Теперь нажмите Play и наблюдайте, как движется платформа. Как только плитка платформы выходит из поля зрения камеры, она перемещается обратно в конец, при этом активируется случайное препятствие, создавая иллюзию бесконечного уровня (перейдите к 0:11).

Камеру необходимо разместить аналогично видео, чтобы платформы шли к камере и за ней, иначе платформы не будут повторяться.

Sharp Coder Видео проигрыватель

Шаг 2: Создайте игрока

Экземпляр игрока будет представлять собой простую сферу с контроллером, способным прыгать и приседать.

  • Создайте новую сферу (GameObject -> 3D Object -> Sphere) и удалите ее компонент Sphere Collider.
  • Назначьте ему ранее созданный "RedMaterial".
  • Создайте новый GameObject и назовите его "Player"
  • Переместите сферу внутри объекта "Player" и измените ее положение на (0, 0, 0).
  • Создайте новый скрипт, назовите его "SC_IRPlayer" и вставьте в него приведенный ниже код:

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;
        }
    }
}
  • Прикрепите скрипт SC_IRPlayer к объекту "Player" (вы заметите, что он добавил еще один компонент под названием Rigidbody)
  • Добавьте компонент BoxCollider в объект "Player".

  • Поместите объект "Player" немного выше объекта "StartPoint", прямо перед камерой.

Нажмите Play и используйте клавишу W, чтобы подпрыгнуть, и клавишу S, чтобы присесть. Цель состоит в том, чтобы избежать красных препятствий:

Sharp Coder Видео проигрыватель

Посмотрите на эту Шейдер изгиба горизонта.

Источник
📁EndlessRunner.unitypackage26.68 KB
Рекомендуемые статьи
Учебное пособие по игре-головоломке «три в ряд» в Unity
Создание 2D-игры Brick Breaker в Unity
Как создать игру в стиле Flappy Bird в Unity
Мини-игра в Unity | CUBEизбегать
Как создать игру-змейку в Unity
Создание скользящей игры-головоломки в Unity
Ферма Зомби | Создание 2D-платформера в Unity