Unity için Endless Runner Eğitimi

Video oyunlarında dünya ne kadar büyük olursa olsun her zaman bir sonu vardır. Ancak bazı oyunlar sonsuz dünyayı taklit etmeye çalışır, bu tür oyunlar Endless Runner adı verilen kategoriye girer.

Endless Runner, oyuncunun puan toplarken ve engellerden kaçınırken sürekli olarak ileriye doğru hareket ettiği bir oyun türüdür. Ana amaç, engellere düşmeden veya çarpmadan seviyenin sonuna ulaşmaktır, ancak çoğu zaman seviye kendisini sonsuza kadar tekrar eder ve oyuncu engele çarpana kadar zorluk seviyesi kademeli olarak artar.

Subway Surfers Oynanışı

Modern bilgisayarların/oyun cihazlarının bile sınırlı işlem gücüne sahip olduğu düşünüldüğünde, gerçekten sonsuz bir dünya yaratmak imkansızdır.

Peki bazı oyunlar sonsuz bir dünya yanılsaması nasıl yaratır? Cevap, yapı taşlarını yeniden kullanarak (diğer adıyla nesne birleştirme), yani blok Kamera görünümünün arkasına veya dışına geçtiği anda öne taşınır.

'da sonsuz koşu oyunu yapmak için Unityengelli bir platform ve bir oyuncu kontrolcüsü yapmamız gerekecek.

Adım 1: Platformu Oluşturun

Daha sonra Prefab'da saklanacak olan döşenmiş bir platform oluşturarak başlıyoruz:

  • Yeni bir GameObject oluşturun ve onu çağırın "TilePrefab"
  • Yeni Küp Oluştur (GameObject -> 3D Object -> Cube)
  • Küpü "TilePrefab" nesnenin içine taşıyın, konumunu (0, 0, 0) olarak değiştirin ve (8, 0.4, 20) olarak ölçekleyin

  • İsteğe bağlı olarak aşağıdaki gibi ek Küpler oluşturarak kenarlara Raylar ekleyebilirsiniz:

Engeller için 3 farklı engel çeşidim olacak ama siz ihtiyacınız kadar yapabilirsiniz:

  • Nesnenin içine 3 GameObjects oluşturun "TilePrefab" ve bunlara isim verin "Obstacle1"ve "Obstacle2" "Obstacle3"
  • İlk engel için yeni bir Küp oluşturun ve onu "Obstacle1" nesnenin içine taşıyın
  • Yeni Küpü platformla aynı genişliğe getirin ve yüksekliğini azaltın (oyuncunun bu engeli aşmak için zıplaması gerekecektir)
  • Yeni bir Malzeme yaratın, adını "RedMaterial" ve rengini Kırmızı olarak değiştirin, ardından onu Küp'e atayın (bu sadece engelin ana platformdan ayırt edilmesini sağlar)

  • Birkaç küp oluşturun "Obstacle2" ve bunları üçgen bir şekle yerleştirin, altta bir boşluk bırakın (oyuncunun bu engeli aşmak için çömelmesi gerekecektir)

  • Ve son olarak, ve'nin "Obstacle3" bir kopyası olacak, birleştirilmiş "Obstacle1" "Obstacle2"

  • Şimdi Engeller içindeki tüm Nesneleri seçin ve etiketlerini olarak değiştirin "Finish", bu daha sonra Oyuncu ve Engel arasındaki çarpışmayı tespit etmek için gerekli olacak.

Sonsuz bir platform oluşturmak için Nesne Havuzu ve Engel aktivasyonunu işleyecek birkaç betiğe ihtiyacımız olacak:

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_ZeminOluşturucusu.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 betiğini "TilePrefab" nesneye ekleyin
  • "Obstacle1", "Obstacle2" ve nesnesini "Obstacle3" Engeller dizisine atayın

Başlangıç ​​Noktası ve Bitiş Noktası için sırasıyla platformun başlangıcına ve sonuna yerleştirilmesi gereken 2 adet GameObject oluşturmamız gerekiyor:

  • SC_PlatformTile'da Başlangıç ​​Noktası ve Bitiş Noktası değişkenlerini atayın

  • Nesneyi Prefab'a kaydedin "TilePrefab" ve Sahne'den kaldırın
  • Yeni bir GameObject oluşturun ve onu çağırın "_GroundGenerator"
  • SC_GroundGenerator betiğini nesneye "_GroundGenerator" ekleyin
  • Ana Kamera konumunu (10, 1, -9) olarak değiştirin ve dönüşünü (0, -55, 0) olarak değiştirin
  • Yeni bir GameObject oluşturun, onu çağırın "StartPoint" ve konumunu (0, -2, -15) olarak değiştirin
  • Nesneyi seçin "_GroundGenerator" ve SC_GroundGenerator'da Ana Kamera, Başlangıç ​​Noktası ve Döşeme Önceden Hazırlanmış değişkenlerini atayın

Şimdi Oynat'a basın ve platformun nasıl hareket ettiğini izleyin. Platform karosu kamera görünümünden çıkar çıkmaz, rastgele bir engelin etkinleştirilmesiyle sona geri taşınır ve sonsuz bir seviye yanılsaması yaratır (0:11'e atla).

Kamera videoya benzer şekilde yerleştirilmeli, böylece platformlar Kameraya doğru ve arkasına doğru gitmeli, aksi takdirde platformlar tekrar etmeyecektir.

Sharp Coder Video oynatıcı

Adım 2: Oynatıcıyı Oluşturun

Oyuncu Örneği, zıplama ve çömelme yeteneğine sahip bir kontrolcü kullanan basit bir Küre olacak.

  • Yeni bir Küre oluşturun (GameObject -> 3D Object -> Sphere) ve onun Sphere Collider bileşenini kaldırın
  • Daha önce oluşturulanı "RedMaterial" ona ata
  • Yeni bir GameObject oluşturun ve onu çağırın "Player"
  • Küreyi "Player" nesnenin içine taşıyın ve konumunu (0, 0, 0) olarak değiştirin
  • Yeni bir script oluşturun, adını yazın "SC_IRPlayer" ve içerisine aşağıdaki kodu yapıştırın:

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 betiğini nesneye ekleyin "Player" (Rigidbody adında başka bir bileşen eklediğini fark edeceksiniz)
  • BoxCollider bileşenini nesneye "Player" ekleyin

  • "Player" Nesneyi nesnenin biraz üstüne "StartPoint", Kameranın hemen önüne yerleştirin

Play'e basın ve zıplamak için W tuşunu ve çömelmek için S tuşunu kullanın. Amaç kırmızı Engellerden kaçınmaktır:

Sharp Coder Video oynatıcı

Bu Horizon Bending Shader'ı inceleyin.