Hướng dẫn chạy vô tận cho Unity

Trong trò chơi điện tử, thế giới dù có rộng lớn đến đâu thì nó vẫn luôn có điểm kết thúc. Nhưng một số trò chơi cố gắng mô phỏng thế giới vô tận, những trò chơi như vậy thuộc danh mục có tên Người chạy vô tận.

Endless Runner là một loại trò chơi trong đó người chơi liên tục di chuyển về phía trước đồng thời thu thập điểm và tránh chướng ngại vật. Mục tiêu chính là đi đến cuối cấp độ mà không rơi vào hoặc va chạm với chướng ngại vật, nhưng đôi khi, cấp độ lặp lại vô tận, tăng dần độ khó cho đến khi người chơi va chạm với chướng ngại vật.

Trò chơi Subway Surfers

Xét rằng ngay cả máy tính/thiết bị chơi game hiện đại cũng có khả năng xử lý hạn chế nên không thể tạo ra một thế giới thực sự vô tận.

Vậy làm thế nào một số trò chơi có thể tạo ra ảo ảnh về một thế giới vô tận? Câu trả lời là bằng cách sử dụng lại các khối xây dựng (hay còn gọi là gộp đối tượng), nói cách khác, ngay khi khối ở phía sau hoặc bên ngoài chế độ xem Camera, khối đó sẽ được di chuyển lên phía trước.

Để tạo trò chơi chạy vô tận trong Unity, chúng ta sẽ cần tạo một nền tảng có chướng ngại vật và bộ điều khiển người chơi.

Bước 1: Tạo nền tảng

Chúng tôi bắt đầu bằng cách tạo một nền tảng lát gạch mà sau này sẽ được lưu trữ trong Prefab:

  • Tạo một GameObject mới và gọi nó "TilePrefab"
  • Tạo khối lập phương mới (GameObject -> Đối tượng 3D -> Cube)
  • Di chuyển Khối bên trong đối tượng "TilePrefab", thay đổi vị trí của nó thành (0, 0, 0) và chia tỷ lệ thành (8, 0,4, 20)

  • Tùy chọn, bạn có thể thêm Rails vào các cạnh bằng cách tạo các Khối bổ sung, như thế này:

Về chướng ngại vật, tôi sẽ có 3 biến thể chướng ngại vật, nhưng bạn có thể tạo bao nhiêu tùy ý:

  • Tạo 3 GameObject bên trong đối tượng "TilePrefab" và đặt tên chúng là "Obstacle1", "Obstacle2" và "Obstacle3"
  • Đối với chướng ngại vật đầu tiên, hãy tạo Khối mới và di chuyển nó vào trong đối tượng "Obstacle1"
  • Chia tỷ lệ Khối lập phương mới có chiều rộng bằng với nền tảng và giảm chiều cao của nó xuống (người chơi sẽ cần phải nhảy để tránh chướng ngại vật này)
  • Tạo Vật liệu mới, đặt tên là "RedMaterial" và đổi màu thành Đỏ, sau đó gán nó cho Khối lập phương (điều này chỉ để phân biệt chướng ngại vật với nền tảng chính)

  • Đối với "Obstacle2", hãy tạo một vài hình khối và đặt chúng thành hình tam giác, chừa một khoảng trống ở phía dưới (người chơi sẽ cần cúi xuống để tránh chướng ngại vật này)

  • Và cuối cùng, "Obstacle3" sẽ là bản sao của "Obstacle1" và "Obstacle2", được kết hợp với nhau

  • Bây giờ, hãy chọn tất cả các Đối tượng bên trong Chướng ngại vật và thay đổi thẻ của chúng thành "Finish", điều này sẽ cần thiết sau này để phát hiện va chạm giữa Người chơi và Chướng ngại vật.

Để tạo ra một nền tảng vô hạn, chúng ta sẽ cần một vài tập lệnh sẽ xử lý việc kích hoạt Object Pooling và Obstacle:

  • Tạo một tập lệnh mới, gọi nó là "SC_PlatformTile" và dán mã bên dưới vào bên trong nó:

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);
        }
    }
}
  • Tạo một tập lệnh mới, gọi nó là "SC_GroundGenerator" và dán đoạn mã bên dưới vào bên trong nó:

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));
    }
}
  • Đính kèm tập lệnh SC_PlatformTile vào đối tượng "TilePrefab"
  • Gán đối tượng "Obstacle1", "Obstacle2" và "Obstacle3" vào mảng Chướng ngại vật

Đối với Điểm bắt đầu và Điểm kết thúc, chúng ta cần tạo 2 GameObject tương ứng sẽ được đặt ở đầu và cuối của nền tảng:

  • Gán biến Điểm bắt đầu và Điểm kết thúc trong SC_PlatformTile

  • Lưu đối tượng "TilePrefab" vào Prefab và xóa nó khỏi Scene
  • Tạo một GameObject mới và gọi nó "_GroundGenerator"
  • Đính kèm tập lệnh SC_GroundGenerator vào đối tượng "_GroundGenerator"
  • Thay đổi vị trí Camera chính thành (10, 1, -9) và thay đổi góc xoay của nó thành (0, -55, 0)
  • Tạo một GameObject mới, gọi nó là "StartPoint" và thay đổi vị trí của nó thành (0, -2, -15)
  • Chọn đối tượng "_GroundGenerator" và trong SC_GroundGenerator gán các biến Main Camera, Start Point và Tile Prefab

Bây giờ hãy nhấn Play và quan sát cách nền tảng di chuyển. Ngay khi ô nền tảng thoát ra khỏi chế độ xem camera, nó sẽ được di chuyển trở lại cuối cùng với một chướng ngại vật ngẫu nhiên được kích hoạt, tạo ra ảo ảnh về cấp độ vô hạn (Chuyển đến 0:11).

Camera phải được đặt tương tự như video để các nền tảng hướng về phía Camera và phía sau nó, nếu không các nền tảng sẽ không lặp lại.

Sharp Coder Trình phát video

Bước 2: Tạo trình phát

Phiên bản người chơi sẽ là một quả cầu đơn giản sử dụng bộ điều khiển có khả năng nhảy và cúi người.

  • Tạo một Sphere mới (GameObject -> 3D Object -> Sphere) và xóa thành phần Sphere Collider của nó
  • Gán "RedMaterial" đã tạo trước đó cho nó
  • Tạo một GameObject mới và gọi nó "Player"
  • Di chuyển quả cầu bên trong đối tượng "Player" và thay đổi vị trí của nó thành (0, 0, 0)
  • Tạo một tập lệnh mới, đặt tên là "SC_IRPlayer" và dán đoạn mã bên dưới vào trong tập lệnh đó:

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;
        }
    }
}
  • Đính kèm tập lệnh SC_IRPlayer vào đối tượng "Player" (bạn sẽ nhận thấy rằng nó đã thêm một thành phần khác có tên là Rigidbody)
  • Thêm thành phần BoxCollider vào đối tượng "Player"

  • Đặt đối tượng "Player" phía trên đối tượng "StartPoint" một chút, ngay phía trước Camera

Nhấn Play và sử dụng phím W để nhảy và phím S để cúi xuống. Mục tiêu là để tránh chướng ngại vật màu đỏ:

Sharp Coder Trình phát video

Kiểm tra cái này Horizon Bending Shader.

Nguồn
📁EndlessRunner.unitypackage26.68 KB
Bài viết được đề xuất
Tạo trò chơi Brick Breaker 2D trong Unity
Tạo trò chơi giải đố trượt trong Unity
Cách tạo một trò chơi lấy cảm hứng từ Flappy Bird trong Unity
Hướng dẫn trò chơi giải đố ghép 3 trong Unity
Trò chơi nhỏ trong Unity | khối lập phương Flappy
Cách tạo trò chơi rắn trong Unity
Trò chơi nhỏ trong Unity | CUBTránh