Eindeloze Runner-tutorial voor Unity

Hoe groot de wereld ook is, in videogames komt er altijd een einde. Maar sommige games proberen de oneindige wereld na te bootsen; dergelijke games vallen onder de categorie Endless Runner.

Endless Runner is een soort spel waarbij de speler voortdurend vooruit beweegt terwijl hij punten verzamelt en obstakels ontwijkt. Het hoofddoel is om het einde van het level te bereiken zonder in de obstakels te vallen of ermee in botsing te komen, maar vaak herhaalt het level zichzelf oneindig, waardoor de moeilijkheidsgraad geleidelijk toeneemt, totdat de speler tegen het obstakel botst.

Subway Surfers-gameplay

Gezien het feit dat zelfs moderne computers/spelapparaten een beperkte verwerkingskracht hebben, is het onmogelijk om een ​​werkelijk oneindige wereld te maken.

Dus hoe creëren sommige games de illusie van een oneindige wereld? Het antwoord is door de bouwstenen te hergebruiken (ook wel objectpooling genoemd), met andere woorden: zodra het blok achter of buiten het camerabeeld komt, wordt het naar voren verplaatst.

Om in Unity een eindeloze runner-game te maken, moeten we een platform met obstakels en een spelercontroller maken.

Stap 1: Creëer het platform

We beginnen met het maken van een betegeld platform dat later zal worden opgeslagen in de Prefab:

  • Maak een nieuw GameObject en roep het aan "TilePrefab"
  • Nieuwe kubus maken (GameObject -> 3D-object -> Kubus)
  • Verplaats de kubus binnen het "TilePrefab"-object, verander de positie in (0, 0, 0) en schaal naar (8, 0,4, 20)

  • Optioneel kun je rails aan de zijkanten toevoegen door extra kubussen te maken, zoals deze:

Voor de obstakels heb ik 3 obstakelvariaties, maar je kunt er zoveel maken als nodig is:

  • Maak 3 GameObjects in het "TilePrefab"-object en noem ze "Obstacle1", "Obstacle2" en "Obstacle3"
  • Maak voor het eerste obstakel een nieuwe kubus en verplaats deze binnen het "Obstacle1"-object
  • Schaal de nieuwe kubus tot ongeveer dezelfde breedte als het platform en verlaag de hoogte ervan (de speler moet springen om dit obstakel te ontwijken)
  • Maak een nieuw materiaal, noem het "RedMaterial" en verander de kleur in Rood, wijs het vervolgens toe aan de kubus (dit is alleen maar om het obstakel te onderscheiden van het hoofdplatform)

  • Maak voor de "Obstacle2" een paar kubussen en plaats ze in een driehoekige vorm, waarbij er onderaan een open ruimte overblijft (de speler moet hurken om dit obstakel te vermijden)

  • En ten slotte wordt "Obstacle3" een duplicaat van "Obstacle1" en "Obstacle2", gecombineerd

  • Selecteer nu alle objecten binnen obstakels en verander hun tag in "Finish". Dit is later nodig om de botsing tussen speler en obstakel te detecteren.

Om een ​​oneindig platform te genereren hebben we een aantal scripts nodig die Object Pooling en Obstacle activering afhandelen:

  • Maak een nieuw script, noem het "SC_PlatformTile" en plak de onderstaande code erin:

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);
        }
    }
}
  • Maak een nieuw script, noem het "SC_GroundGenerator" en plak de onderstaande code erin:

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));
    }
}
  • Voeg het SC_PlatformTile-script toe aan het "TilePrefab"-object
  • Wijs "Obstacle1", "Obstacle2" en "Obstacle3"-object toe aan de Obstacles-array

Voor het startpunt en eindpunt moeten we 2 GameObjects maken die respectievelijk aan het begin en het einde van het platform moeten worden geplaatst:

  • Wijs startpunt- en eindpuntvariabelen toe in SC_PlatformTile

  • Sla het "TilePrefab"-object op in Prefab en verwijder het uit de scène
  • Maak een nieuw GameObject en roep het aan "_GroundGenerator"
  • Koppel het SC_GroundGenerator-script aan het "_GroundGenerator"-object
  • Wijzig de positie van de hoofdcamera in (10, 1, -9) en wijzig de rotatie ervan in (0, -55, 0)
  • Maak een nieuw GameObject, noem het "StartPoint" en verander de positie in (0, -2, -15)
  • Selecteer het object "_GroundGenerator" en wijs in SC_GroundGenerator de variabelen Main Camera, Start Point en Tile Prefab toe

Druk nu op Play en kijk hoe het platform beweegt. Zodra de platformtegel uit het camerabeeld verdwijnt, wordt deze terug naar het einde verplaatst en wordt een willekeurig obstakel geactiveerd, waardoor de illusie ontstaat van een oneindig niveau (Ga naar 0:11).

De camera moet op dezelfde manier worden geplaatst als de video, zodat de platforms naar de camera en erachter gaan, anders herhalen de platforms zich niet.

Sharp Coder Video speler

Stap 2: Maak de speler

De spelerinstantie zal een eenvoudige bol zijn met een controller die kan springen en hurken.

  • Maak een nieuwe Sphere (GameObject -> 3D Object -> Sphere) en verwijder de Sphere Collider-component
  • Wijs er eerder aangemaakte "RedMaterial" aan toe
  • Maak een nieuw GameObject en roep het aan "Player"
  • Verplaats de bol binnen het "Player"-object en verander de positie in (0, 0, 0)
  • Maak een nieuw script, noem het "SC_IRPlayer" en plak de onderstaande code erin:

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;
        }
    }
}
  • Voeg het SC_IRPlayer-script toe aan het "Player"-object (je zult merken dat er een andere component is toegevoegd met de naam Rigidbody)
  • Voeg de BoxCollider-component toe aan het "Player"-object

  • Plaats het "Player"-object iets boven het "StartPoint"-object, recht voor de camera

Druk op Speel en gebruik de W-toets om te springen en de S-toets om te bukken. Het doel is om rode obstakels te vermijden:

Sharp Coder Video speler

Kijk eens naar Horizon Bending Shader.

Voorgestelde artikelen
Een 2D Brick Breaker-spel maken in Unity
Een glijdend puzzelspel maken in eenheid
Hoe je in Unity een op Flappy Bird geïnspireerd spel maakt
Minigame in eenheid | CUBEvermijd
Tutorial voor Match-3-puzzelspel in Unity
Boerderijzombies | Making of 2D-platformgame in Unity
Hoe je een slangenspel in eenheid maakt