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.
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:
- Yeni bir script oluşturun, adını yazın "SC_PlatformTile" ve içerisine aşağıdaki kodu yapıştırı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);
}
}
}
- Yeni bir script oluşturun, adını yazın "SC_GroundGenerator" ve içerisine aşağıdaki kodu yapıştırın:
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.
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:
Bu Horizon Bending Shader'ı inceleyin.