Bu bölümde basit araç kontrolü ve basit kamera kontrolü yazıldı.
Araç kontrolü için:
using UnityEngine;
public class PlayerController : MonoBehaviour // Bu scripti araca ekledik. Aracın hareketini yönetmek için kullanıyoruz.
{
private float speed = 15f;
private float turnSpeed = 50f;
private float horizontalInput;
private float verticalInput;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void Update()
{
//Kullanıcıdan alınan veri
horizontalInput = Input.GetAxis("Horizontal"); // Unitynin yerleşik Input sisteminden sağ ve sol ok tuşlarından gelen veriyi 1 ve -1 olarak alır.
verticalInput = Input.GetAxis("Vertical"); // Unitynin yerleşik Input sisteminden ileri ve ger ok tuşlarından gelen veriyi 1 ve -1 olarak alır.
// Aracı ileri götüren kod:
transform.Translate(Vector3.forward * Time.deltaTime * speed * verticalInput); // Vector3.forward = 0,0,1 | Time.deltaTime = 1/saniyedeki frame sayısı
// Aracı yanlara götüren kod:
// transform.Translate(Vector3.right * Time.deltaTime * horizontalInput * turnSpeed); // bu kodda araç dönmez. yanlara doğru kayma hareketi yapar.
// Aracı dönderen kod:
transform.Rotate(Vector3.up, horizontalInput * Time.deltaTime * turnSpeed);
}
}
Kamera kontrolü için:
using UnityEngine;
public class FollowPlayer : MonoBehaviour // Kameranın nesneyi takip etmesi için kullanılan script. kameraya ekliyoruz.
{
public GameObject player; // takip edilecek nesne
private Vector3 offset = new Vector3(0, 5, -7); // nesneye göre kameranın konumu
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void LateUpdate() // LateUpdate(), Update() metodunun tamamlanmasını beklemesi gereken işlemler için kullanılır. Kamera takibi vs. LateUpdate() yerine Update() kullanılırsa kullanıcının konumu henüz güncellenmediğinden karakterin bir önceki karedeki konumunu kullanabileceği için küçük gecikmeler veya titremeler olabilir.
{
transform.position = player.transform.position + offset;
}
}
Bu komut dosyası player GameObject'ine eklenir ve oyuncunun yatay hareketini ve yemek atmasını kontrol eder.
using UnityEngine;
public class PlayerController : MonoBehaviour
{
[Header("Player Movement")] //Kullanıcı hareketi için gerekli değişkenler
public float horizontalInput;
public float speed = 10.0f;
public float xRange = 10.0f;
[Header("Player Bullet")] // Kullanıcı mermisi için gerekli değişkenler
public GameObject projectilePrefab;
void Start()
{
}
// Update is called once per frame
void Update()
{
//Yatay input alındı
horizontalInput = Input.GetAxis("Horizontal");
//Yatay sınırlara gelindiğinde pozisyonun sabitlenip değişmemesi sağlandı
if(transform.position.x < -xRange)
{
transform.position = new Vector3(-xRange, transform.position.y, transform.position.z);
}
if (transform.position.x > xRange)
{
transform.position = new Vector3(xRange, transform.position.y, transform.position.z);
}
//Yatay hareket
transform.Translate(Vector3.right * horizontalInput * Time.deltaTime * speed);
if (Input.GetKeyDown(KeyCode.Space)) //Space tuşuna basıldığında projectilePrefab oluşturur.
{
Instantiate(projectilePrefab, transform.position, projectilePrefab.transform.rotation);
}
}
}
Bu komut dosyası hayvan GameObject'ine ve atılacak gıda GameObject'ine eklenir ve GameObject'in ileri doğru hareketini kontrol eder.
using UnityEngine;
public class MoveForward : MonoBehaviour
{
public float speed = 40.0f; // başlangıç hız değeri. public olduğundan unity içinde düzenlenir.
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void Update()
{
transform.Translate(Vector3.forward * Time.deltaTime * speed);
}
}
Bu komut dosyası hayvan GameObject'ine ve atılacak gıda GameObject'ine eklenir ve GameObject'in alan sınırını aşınca yok olmasını sağlar.
using UnityEngine;
public class DestroyOutOfBounds : MonoBehaviour
{
public float topBound = 30.0f; // nesne için üst sınır değeri
public float lowerBound = -10.0f; // nesne için alt sınır değeri
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void Update()
{
if (transform.position.z > topBound) // nesnenin z pozisyonu sınır değerinden büyükse
{
Destroy(gameObject); //nesneyi yok et
}
else if (transform.position.z < lowerBound) // nesnenin z pozisyonu sınır değerinden küçükse
{
Destroy(gameObject); //nesneyi yok et
Debug.Log("Game Over"); // konsola gameover yaz
}
}
}
Bu komut dosyası hayvan GameObject'ine ve atılacak gıda GameObject'ine eklenir ve GameObject'lerin çarpışmasını kontrol eder. Çarpışanların yok olmasını sağlar
using UnityEngine;
public class DetectCollisions : MonoBehaviour
{
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Update is called once per frame
void Update()
{
}
private void OnTriggerEnter(Collider other)
{
Destroy(gameObject); // mevcut gameObject'i yok eder
Destroy(other.gameObject); // Çarpışılan diğer gameObject'i yok eder.
}
}
Adından da anlaşıldığı gibi.
using UnityEngine;
public class SpawnManager : MonoBehaviour
{
public GameObject[] animalPrefabs; // animal Prefableri tutan dizi
private float spawnDelay = 2;
private float spawnInterval = 1.5f;
void Start()
{
InvokeRepeating("SpawnRandomAnimal", spawnDelay, spawnInterval); // başlanıçtan sonra bir fonksiyonu uyandırmak için kullanılır. ilk parametre fonksiyon adı. ikincisi başlangıştan itibaren kaç saniyede başlatılacağı. üçüncüsü ise hangi aralıkla tekrar edeceği.
}
void Update()
{
}
void SpawnRandomAnimal() //Random Animal üreten fonksiyon
{
int animalIndex = Random.Range(0, animalPrefabs.Length); // animalPrefabs dizisinin uzunluğu arasında rastgele bir sayı oluştur
Vector3 spawnPos = new Vector3(Random.Range(-15, 15), 0, 20); // x eksenlerinde rastgele bir konum oluştur
Instantiate(animalPrefabs[animalIndex], spawnPos, animalPrefabs[animalIndex].transform.rotation); // rastgele bir hayvanı rastgele bir konumda oluştur
}
}
Atandığı nesneye random renk atayan kod:
using System.Collections;
using System.Collections.Generic;
using JetBrains.Annotations;
using UnityEngine;
public class Cube : MonoBehaviour
{
public MeshRenderer Renderer; // MeshRenderer component tanımlandı
void Start()
{
Material material = Renderer.material; // Renderer component'ine erişildi ve material değişkenine atandı
material.color = new Color(Random.Range(0.1f, 1f), Random.Range(0.1f, 1f), Random.Range(0.1f, 1f), Random.Range(0.5f, 1f)); // material değişkeninin rengi rastgele olarak değiştirildi
}
}
Bu projede engellerin üzerinden atlayan ve durmadan koşan bir karakter yapacağız. Karakter konumunda sabitken arkaplan ve engeller karaktere doğru gelecek. Karakter engelle çarpışana kadar bu döngü sonsuz devam edecek.
using UnityEngine;
public class PlayerController : MonoBehaviour
{
private Rigidbody playerRb; // bir nesneye kuvvet uygulayacaksak rigitbody kompanentine ihtiyacımız var
// Animasyolar:
private Animator playerAnim; // objenin animasyonlarını kontrol eder
// VFX
public ParticleSystem explosionParticle;
public ParticleSystem dirtParticle;
// SFX
public AudioClip jumpSound;
public AudioClip crashSound;
private AudioSource playerAudio;
// Zıplama
public float jumpForce = 10; // objenin zıplama kuvveti
public float gravityModifier; // objenin düşme hızını ayarlar
bool isGrounded = true; // objenin yere değip değmediğini kontrol eder
// Oyun devam durumu
public bool gameOver = false; // oyunun bitip bitmediğini kontrol eder
void Start()
{
// Zıplama
playerRb = GetComponent<Rigidbody>(); // objenin rigidbody bileşenini alır
Physics.gravity *= gravityModifier; // fizik motorunun yerçekimini ayarlar
// Animasyon
playerAnim = GetComponent<Animator>(); // objenin animator bileşenini alır
// SFX
playerAudio = GetComponent<AudioSource>();
}
void Update()
{
// Space ile zıplama:
if(Input.GetKeyDown(KeyCode.Space) && isGrounded && !gameOver) // space tuşuna basıldığında, obje yere değdiğinde ve oyun bitmediyse
{
playerRb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse); // objenye yukarı doğru bir kuvvet uygular (birinci parametre yön, ikinci parametre kuvvet)
isGrounded = false; // objenin yere değmediğini belirtir
playerAnim.SetTrigger("Jump_trig"); // objenin zıplama animasyonunu çalıştırır
dirtParticle.Stop(); // Ayaktan çıkan toz efektini durdur
playerAudio.PlayOneShot(jumpSound, 1.0f); // Zıplama sesini çal
}
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("Ground")) // obje yere değdiğinde
{
isGrounded = true; // objenin yere değdiğini belirtir
dirtParticle.Play(); // ayaktan çıkan toz efektini başlat
}
else if (collision.gameObject.CompareTag("Obstacle")) // obje engelle çarpıştığında (Oyun bittiğinde)
{
gameOver = true; // oyunun bittiğini belirtir
Debug.Log("Game Over!"); // konsola "Game Over!" yazar
playerAnim.SetBool("Death_b", true); // objenin ölüm animasyonunu çalıştırır
playerAnim.SetInteger("DeathType_int", 1); // objenin ölüm animasyonunu çalıştırır
explosionParticle.Play(); // patlama VFX
dirtParticle.Stop(); // Ayaktan çıkan toz efektini durdur
playerAudio.PlayOneShot(crashSound, 1.0f); // oyun sonu SFX
}
}
}
engellerin ve backgroundun sola doğru hareket etmesini sağlayan kod
using UnityEngine;
public class MoveLeft : MonoBehaviour
{
public float speed = 30;
private PlayerController playerControllerScript; // PlayerController scripti
private float leftBound = -15; // objenin hareket edebileceği en sol sınır
void Start()
{
playerControllerScript = GameObject.Find("Player").GetComponent<PlayerController>(); // PlayerController scriptini alır
}
void Update()
{
if(playerControllerScript.gameOver == false) // PlayerController scriptindeki gameOver değişkeni false olduğu sürece
{
transform.Translate(Vector3.left * Time.deltaTime * speed); // hareket ettirir
}
if(transform.position.x < leftBound && gameObject.CompareTag("Obstacle")) // obje sınıra ulaştığında ve engelse
{
Destroy(gameObject); // objeyi yok eder
}
}
}
Yarısı hareket ettiğinde backgroundu başlangıç konumuna tekrar çeken kod.
using UnityEngine;
public class RepeatBackground : MonoBehaviour
{
private Vector3 startPos;
private float repeatWidth;
void Start()
{
startPos = transform.position;
repeatWidth = GetComponent<BoxCollider>().size.x / 2; // BoxCollider'ın yarısını alır. Bunun için nesneye box collider eklendi.
}
void Update()
{
if(transform.position.x < startPos.x - repeatWidth)
{
transform.position = startPos;
}
}
}
Prefab olarak atanan engelleri belirlenen zaman aralıklarında oluşturan kod
using UnityEngine;
public class SpawnManager : MonoBehaviour
{
public GameObject obstaclePrefab;
private Vector3 spawnPos = new Vector3(25, 0, 0); // engellerin oluşturulacağı konum
private float startDelay = 2;
private float repeatRate = 2;
PlayerController playerControllerScript; // PlayerController scripti
void Start()
{
playerControllerScript = GameObject.Find("Player").GetComponent<PlayerController>(); // PlayerController scriptini alır
InvokeRepeating("SpawnObstacle", startDelay, repeatRate); // belirli aralıklarla SpawnObstacle fonksiyonunu çağırır
}
void Update()
{
}
void SpawnObstacle() // engel oluşturur
{
if(playerControllerScript.gameOver == false) // PlayerController scriptindeki gameOver değişkeni false olduğu sürece
{
Instantiate(obstaclePrefab, spawnPos, obstaclePrefab.transform.rotation);
}
}
}
Bu bölümde fizik tabanlı bir oyun kontrolcüsü anlatılmıştır.
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float speed = 10.0f;
// inputları almak için değişkenler
private float verticalInput;
private float horizontalInput;
// sınırlar için değişkenler
private float bottomBound = -2.0f;
private float zxBound = 24.0f;
// zıplama ve yerde olup olmadığını kontrol etmek için değişkenler
private bool isGrounded;
private bool jumpInput;
// Rigidbody ve konum değişkenleri
private Rigidbody playerRb;
private Vector3 playerPos;
void Start()
{
playerRb = GetComponent<Rigidbody>();
playerPos = transform.position; // Oyuncunun başlangıç konumunu alıyoruz. Aşağı düşerse konum sıfırlama için kullanacağız.
}
void Update() // Kullanıcıdan gelen inputları almak için Update fonksiyonunu kullanıyoruz. FixedUpdate Inputları almak için uygun değil.
{
// Inputları alıyoruz.
verticalInput = Input.GetAxis("Vertical");
horizontalInput = Input.GetAxis("Horizontal");
if (Input.GetKeyDown(KeyCode.Space) && isGrounded) // Zıplama inputu
{
jumpInput = true;
}
}
void FixedUpdate() // Fiziksel işlemleri yapmak için FixedUpdate fonksiyonunu kullanıyoruz.
{
MovePLayer(); // Oyuncuyu hareket ettiriyoruz.
Jump(); // Zıplama işlemini yapıyoruz.
ConstainPlayerPosition(); // Oyuncunun sınırlarını belirliyoruz.
}
private void OnCollisionEnter(Collision collision) // Yerde olup olmadığını kontrol etmek için OnCollisionEnter fonksiyonunu kullanıyoruz.
{
if (collision != null)
{
isGrounded = true;
}
}
void MovePLayer()
{
// Fizik temelli hareket ettirme işlemi. Uyguladığımız gücün yönüne göre hareket eder.
playerRb.AddForce(verticalInput * speed * Vector3.forward);
playerRb.AddForce(horizontalInput * speed * Vector3.right);
}
void Jump()
{
if (jumpInput) // Zıplama
{
playerRb.AddForce(Vector3.up * speed, ForceMode.Impulse);
isGrounded = false;
jumpInput = false;
}
}
void ConstainPlayerPosition()
{
// Aşağı düşerse konum sıfırlama
if (transform.position.y < bottomBound)
{
transform.position = playerPos;
}
// Görünmez sınırlar
if (transform.position.x > zxBound)
{
transform.position = new Vector3(zxBound, transform.position.y, transform.position.z);
}
if (transform.position.x < -zxBound)
{
transform.position = new Vector3(-zxBound, transform.position.y, transform.position.z);
}
if (transform.position.z > zxBound)
{
transform.position = new Vector3(transform.position.x, transform.position.y, zxBound);
}
if (transform.position.z < -zxBound)
{
transform.position = new Vector3(transform.position.x, transform.position.y, -zxBound);
}
}
}
Oyunumuzda sınırlı bir alanda küresel bir karakteri yönetiyoruz. Her dalgada o dalga sayısı kadar düşman küre bizi alanın dışına atmaya çalışıyor. Player kontrol mantığı rigidbody üzerinden addforce fonksiyonu ile bakış açısına göre ileri - geri ile yapılıyor. horizontalInput ile de bakış açısı değiştiriliyor.
using System.Collections; // IEnumerator kullanabilmek için gerekli
using UnityEngine;
public class PlayerController : MonoBehaviour // Player gameobjetine atanan script
{
public float speed = 5f;
private Rigidbody playerRb; // player bileşenine güç uygulamak için kullanılır
private GameObject focalPoint; // ana kameranın parenti olarak atanan gameobject. Kamera bakış açısını ayarlamak için kullanılır
private float powerUpStrength = 15f;
public bool hasPowerUp = false; // oyuncunun güçlendirme alıp almadığını kontrol etmek için kullanılır
public GameObject powerupIndicator; // güçlendirme alındığında gösterilecek olan gameobject
void Start()
{
playerRb = GetComponent<Rigidbody>(); // Rigidbody bileşenini alır
focalPoint = GameObject.Find("Focal Point"); // sahnedeki "Focal Point" isimli gameobjecti bulur
}
void Update()
{
float forwardInput = Input.GetAxis("Vertical"); // W ve S tuşları ile ileri geri hareket için kullanılır
playerRb.AddForce(forwardInput * speed * focalPoint.transform.forward); // oyuncunun bakış açısına göre hareket ettirir
powerupIndicator.transform.position = transform.position + new Vector3(0, -0.5f, 0); // güçlendirme göstergesinin oyuncunun altında görünmesini sağlar
}
private void OnTriggerEnter(Collider other) // oyuncunun bir nesneye çarpması durumunda tetiklenir
{
if (other.CompareTag("Powerup")) // "Powerup" tagine sahip bir nesneye çarparsa
{
hasPowerUp = true; // güçlendirme alındı
powerupIndicator.gameObject.SetActive(true); // güçlendirme göstergesini aktif eder
Destroy(other.gameObject); // güçlendirme nesnesini yok eder
StartCoroutine(PowerUpCooldownRoutine()); // güçlendirme süresini başlatır
}
}
IEnumerator PowerUpCooldownRoutine() // güçlendirme süresini başlatan coroutine
{
yield return new WaitForSeconds(7); // 7 saniye bekler
hasPowerUp = false; // güçlendirme süresi doldu
powerupIndicator.gameObject.SetActive(false); // güçlendirme göstergesini kapatır
}
private void OnCollisionEnter(Collision collision) // oyuncunun bir nesneye çarpması durumunda tetiklenir
{
if (collision.gameObject.CompareTag("Enemy") && hasPowerUp) // "Enemy" tagine sahip bir nesneye çarparsa ve güçlendirme almışsa
{
Rigidbody enemyRb = collision.gameObject.GetComponent<Rigidbody>(); // çarpılan nesnenin Rigidbody bileşenini alır
Vector3 awayFromPlayer = collision.transform.position - transform.position; // oyuncunun pozisyonundan çarpılan nesnenin pozisyonunu çıkararak oyuncudan uzak bir vektör oluşturur
enemyRb.AddForce(awayFromPlayer * powerUpStrength, ForceMode.Impulse); // çarpılan nesneye güç uygular
}
}
}
using UnityEngine;
public class RotateCamera : MonoBehaviour // Kamerayı döndürmek için kullanılan ve focal point gameobjectine atanan script
{
public float rotationSpeed = 50f;
void Update()
{
float horizontalInput = Input.GetAxis("Horizontal");
transform.Rotate(Vector3.up, horizontalInput * rotationSpeed * Time.deltaTime);
}
}
using UnityEngine;
public class Enemy : MonoBehaviour // Düşman gameobjectine atanan script
{
public float speed = 3f;
private Rigidbody enemyRb;
private GameObject player;
void Start()
{
enemyRb = GetComponent<Rigidbody>(); // Rigidbody bileşenini alır
player = GameObject.Find("Player"); // sahnedeki "Player" isimli gameobjecti bulur
}
void Update()
{
Vector3 lookDirection = (player.transform.position - transform.position).normalized; // Düşmanın bakış açısını oyuncunun pozisyonuna göre ayarlar
enemyRb.AddForce(lookDirection * speed); // Düşmanı oyuncuya doğru hareket ettirir
if (transform.position.y < -10) // Düşman y ekseninde -10'un altına düşerse
{
Destroy(gameObject); // Düşmanı yok eder
}
}
}
using UnityEngine;
public class SpawnManager : MonoBehaviour // SpawnManager gameobjectine atanan script.
{
public GameObject enemyPrefab; // düşman prefabı
public GameObject powerupPrefab; // güçlendirme prefabı
private float spawnRange = 9f; // düşmanların spawn olacağı alanın boyutu
public int enemyCount; // düşman sayısı
public int waveNumber = 1; // dalga numarası
void Start()
{
SpawnEnemyWave(waveNumber); // ilk dalga düşmanları spawn edilir
Instantiate(powerupPrefab, GenerateSpawnPosition(), powerupPrefab.transform.rotation); // ilk güçlendirme spawn edilir
}
void Update()
{
enemyCount = FindObjectsByType<Enemy>(FindObjectsSortMode.None).Length; // sahnedeki düşman sayısını alır
if (enemyCount == 0) // sahnedeki düşman sayısı 0 ise
{
waveNumber++; // dalga numarasını artırır
SpawnEnemyWave(waveNumber); // yeni dalga düşmanları spawn edilir
Instantiate(powerupPrefab, GenerateSpawnPosition(), powerupPrefab.transform.rotation); // yeni güçlendirme spawn edilir
}
}
void SpawnEnemyWave(int enemiesToSpawn) // düşman dalgasını spawn eden fonksiyon
{
for(int i = 0; i < enemiesToSpawn; i++) // düşman sayısı kadar döngü oluşturur
{
Instantiate(enemyPrefab, GenerateSpawnPosition(), enemyPrefab.transform.rotation); // düşman prefabını rastgele bir pozisyonda spawn eder
}
}
private Vector3 GenerateSpawnPosition() // rastgele spawn pozisyonu oluşturan fonksiyon. void fonksiyonu yerine Vector3 döndürür.
{
float spawnPosX = Random.Range(-spawnRange, spawnRange);
float spawnPosZ = Random.Range(-spawnRange, spawnRange);
Vector3 randomPos = new Vector3(spawnPosX, 0, spawnPosZ);
return randomPos; // rastgele pozisyonu döndürür
}
}
Enemy prefabını kopyalandı. Hız değeri değiştirildi. Her iki prefabı da eklemek ve birini random çağırabilmek için SpawnManager aşağıdaki şekilde güncellendi.
using UnityEngine;
public class SpawnManager : MonoBehaviour
{
public GameObject[] enemyPrefabs; // Enemy prefabs array halinde alındı
public GameObject powerupPrefab;
private float spawnRange = 9f;
public int enemyCount;
public int waveNumber = 1;
void Start()
{
SpawnEnemyWave(waveNumber);
Instantiate(powerupPrefab, GenerateSpawnPosition(), powerupPrefab.transform.rotation);
}
void Update()
{
enemyCount = FindObjectsByType<Enemy>(FindObjectsSortMode.None).Length;
if (enemyCount == 0)
{
waveNumber++;
SpawnEnemyWave(waveNumber);
Instantiate(powerupPrefab, GenerateSpawnPosition(), powerupPrefab.transform.rotation);
}
}
void SpawnEnemyWave(int enemiesToSpawn)
{
for(int i = 0; i < enemiesToSpawn; i++)
{
Instantiate(enemyPrefabs[Random.Range(0, enemyPrefabs.Length)], GenerateSpawnPosition(), enemyPrefabs[0].transform.rotation); //Enemy prefabini rastgele seçip spawn ettik
}
}
private Vector3 GenerateSpawnPosition()
{
float spawnPosX = Random.Range(-spawnRange, spawnRange);
float spawnPosZ = Random.Range(-spawnRange, spawnRange);
Vector3 randomPos = new Vector3(spawnPosX, 0, spawnPosZ);
return randomPos;
}
}
Bir tane rocket prefabı oluşturdum ve ona RocketBehaviour scriptini yerleştirdik. PowerUp prefabına PowerUp scriptini oluşturup yerleştirdik.
using UnityEngine;
public enum PowerUpType { None, Pushback, Rockets } // Enum türü tanımladık. Bu türde olan değişken yanlızca bu değerleri alabilir.
public class PowerUp : MonoBehaviour
{
public PowerUpType powerUpType; // Enum türünde bir değişken tanımladık. Inspector ekranından bizim prefabımıza uygun olanı seçeceğiz.
}
using UnityEngine;
public class RocketBehaviour : MonoBehaviour // roket başka bir fonksiyonda üretildi. Burada yapacağı hareketi ve çarpışma durumunu tanımladık.
{
private Transform target;
private float speed = 15f;
private bool homing; // hedefe doğru hareket edip etmeyeceğini kontrol etmek için
private float rocketStrength = 15f;
private float aliveTime = 5f;
public void Fire(Transform newTarget)
{
target = newTarget;
homing = true;
Destroy(gameObject, aliveTime); // aliveTime süresi dolunca roketi yok et
}
void Update()
{
if(homing && target != null)
{
Vector3 moveDirection = (target.position - transform.position).normalized;
transform.position += moveDirection * speed * Time.deltaTime;
transform.LookAt(target); // roketin yönünü hedefe doğru döndür
}
}
private void OnCollisionEnter(Collision col)
{
if(target != null)
{
if(col.gameObject.CompareTag(target.tag))
{
Rigidbody targetRigitBody = col.gameObject.GetComponent<Rigidbody>();
Vector3 away = -col.contacts[0].normal;
targetRigitBody.AddForce(away * rocketStrength, ForceMode.Impulse);
Destroy(gameObject);
}
}
}
}
Birden fazla powerup prefabını alıp random yerleştirmesi için SpawnManager modifiye edildi.
using UnityEngine;
public class SpawnManager : MonoBehaviour
{
public GameObject[] enemyPrefabs;
public GameObject[] powerupPrefabs; // birden fazla powerup eklenebilmesi için array tanımladık
private float spawnRange = 9f;
public int enemyCount;
public int waveNumber = 1;
void Start()
{
int randomPowerup = Random.Range(0, powerupPrefabs.Length); // rastgele powerup seçimi
SpawnEnemyWave(waveNumber);
Instantiate(powerupPrefabs[randomPowerup], GenerateSpawnPosition(), powerupPrefabs[randomPowerup].transform.rotation); //Başlangıçta rasgele bir powerup oluşması sağlandı.
}
void Update()
{
enemyCount = FindObjectsByType<Enemy>(FindObjectsSortMode.None).Length;
if (enemyCount == 0)
{
waveNumber++;
SpawnEnemyWave(waveNumber);
int randomPowerup = Random.Range(0, powerupPrefabs.Length); // rastgele powerup seçimi
Instantiate(powerupPrefabs[randomPowerup], GenerateSpawnPosition(), powerupPrefabs[randomPowerup].transform.rotation); // Rasgele bir powerup oluşması sağlandı.
}
}
void SpawnEnemyWave(int enemiesToSpawn)
{
for(int i = 0; i < enemiesToSpawn; i++)
{
Instantiate(enemyPrefabs[Random.Range(0, enemyPrefabs.Length)], GenerateSpawnPosition(), enemyPrefabs[0].transform.rotation);
}
}
private Vector3 GenerateSpawnPosition()
{
float spawnPosX = Random.Range(-spawnRange, spawnRange);
float spawnPosZ = Random.Range(-spawnRange, spawnRange);
Vector3 randomPos = new Vector3(spawnPosX, 0, spawnPosZ);
return randomPos;
}
}
Powerup tipleri arasındaki farkı anlaması ve "F" tuşuyla roket ateşlemesi için PlayerController modifiye edildi.
using System.Collections;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float speed = 5f;
private Rigidbody playerRb;
private GameObject focalPoint;
private float powerUpStrength = 15f;
public bool hasPowerUp = false;
public GameObject powerupIndicator;
// güdümlü füze için gerekli olan değişkenler
public PowerUpType currentPowerUp = PowerUpType.None; // mevcup powerup türü
public GameObject rocketPrefab; // füze prefabı
private GameObject tmpRocket; // prefabdan oluşan her bir füze için geçici değişken
private Coroutine powerupCountdown; // powerup süresi için coroutine
void Start()
{
playerRb = GetComponent<Rigidbody>();
focalPoint = GameObject.Find("Focal Point");
}
void Update()
{
float forwardInput = Input.GetAxis("Vertical");
playerRb.AddForce(forwardInput * speed * focalPoint.transform.forward);
powerupIndicator.transform.position = transform.position + new Vector3(0, -0.5f, 0);
if(currentPowerUp == PowerUpType.Rockets && Input.GetKeyDown(KeyCode.F)) // powerUp rockets ise ve f tuşuna basılırsa
{
LaunchRockets(); // füze fırlatma fonksiyonu çağrıldı
}
}
private void OnTriggerEnter(Collider other) // rocket powerUpı için güncellendi.
{
if (other.CompareTag("Powerup"))
{
hasPowerUp = true;
currentPowerUp = other.GetComponent<PowerUp>().powerUpType; // powerup türünü al
powerupIndicator.gameObject.SetActive(true);
Destroy(other.gameObject);
if(powerupCountdown != null) // Sayım var ise
{
StopCoroutine(powerupCountdown); // var olan sayımı durdur
}
powerupCountdown = StartCoroutine(PowerUpCooldownRoutine()); // sayımı durdurabilmek için değişkene atadık
}
}
IEnumerator PowerUpCooldownRoutine()
{
yield return new WaitForSeconds(7);
hasPowerUp = false;
currentPowerUp = PowerUpType.None; // powerup türünü sıfırla
powerupIndicator.gameObject.SetActive(false);
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("Enemy") && currentPowerUp == PowerUpType.Pushback) // çarpışmada mevcut powerupı sorgulaması için güncelleme yapıldı.
{
Rigidbody enemyRb = collision.gameObject.GetComponent<Rigidbody>();
Vector3 awayFromPlayer = collision.transform.position - transform.position;
enemyRb.AddForce(awayFromPlayer * powerUpStrength, ForceMode.Impulse);
}
}
void LaunchRockets() // füze fırlatma fonksiyonu
{
foreach (var enemy in FindObjectsByType<Enemy>(FindObjectsSortMode.None)) // düşmanları bul ve her bir düşman için...
{
tmpRocket = Instantiate(rocketPrefab, transform.position + Vector3.up, Quaternion.identity);
tmpRocket.GetComponent<RocketBehaviour>().Fire(enemy.transform); // füzeleri fırlatacak fonksiyona enemy transform iletildi ve fonksiyon çağırıldı.
}
}
}
Smash PowerUp içindeki listeye "Smash" eklendi.
using UnityEngine;
public enum PowerUpType { None, Pushback, Rockets, Smash } // Enum türüne ekleme yapıldı "Smash"
public class PowerUp : MonoBehaviour
{
public PowerUpType powerUpType;
}
Yeni bir PowerUp prefabı oluşturuldu ve inspectorde türü Smash seçilldi. Bu prefab SpawnManager'e eklendi
PlayerController altında smash hareketinin fonksiyonu yazıldı.
using System.Collections;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float speed = 5f;
private Rigidbody playerRb;
private GameObject focalPoint;
private float powerUpStrength = 15f;
public bool hasPowerUp = false;
public GameObject powerupIndicator;
// güdümlü füze için gerekli olan değişkenler
public PowerUpType currentPowerUp = PowerUpType.None; // mevcup powerup türü
public GameObject rocketPrefab;
private GameObject tmpRocket;
private Coroutine powerupCountdown;
// smash powerupı için gerekli olan değişkenler
[Header("Smash Powerup")]
public float hangTime; // zıplama süresi
public float smashSpeed; // zıplama hızı
public float explosionForce; // patlama kuvveti
public float explosionRadius; // patlama yarıçapı
bool smashing = false;
float floorY;
IEnumerator Smash() // hareket içeren ve sıralı fonksiyonlar için void yerine IEnumerator kullanıldı. void ile yapsaydık hepsi bir anda olurdu. IEnumerator ile frmame by frame ilerlendi.
{
var enemies = FindObjectsByType<Enemy>(FindObjectsSortMode.None); // tüm düşmaları bul
floorY = transform.position.y; // zıplama anındaki yüksekliği değişkene kaydet.
float jumpTime = Time.time + hangTime; // zıplama zamanının biteceği zamanı hesapla
while (Time.time < jumpTime) // mevcut zaman zıplama zamanından küçükse (zıplama bitmediyse)
{
playerRb.linearVelocity = new Vector2(playerRb.linearVelocity.x, smashSpeed); // yukarı doğru hareket et
yield return null; // bir sonraki frame'e geç
}
while(transform.position.y > floorY) // nesne başlangıç konumundan yüksekse ve üstteki şart sağlanmıyorsa
{
playerRb.linearVelocity = new Vector2(playerRb.linearVelocity.x, -smashSpeed); // aşağı doğru hareket et
yield return null; // bir sonraki frame'e geç
}
for(int i = 0; i < enemies.Length; i++) // enemies listesini döngüye al
{
if(enemies[i] != null) // enemies[i] null
{
enemies[i].GetComponent<Rigidbody>().AddExplosionForce(explosionForce, transform.position, explosionRadius, 0.0f, ForceMode.Impulse); // düşmanın rigidbody'sine patlama kuvveti uygula
}
smashing = false; // zıplama işlemi bitti
}
}
void Start()
{
playerRb = GetComponent<Rigidbody>();
focalPoint = GameObject.Find("Focal Point");
}
void Update()
{
float forwardInput = Input.GetAxis("Vertical");
playerRb.AddForce(forwardInput * speed * focalPoint.transform.forward);
powerupIndicator.transform.position = transform.position + new Vector3(0, -0.5f, 0);
if(currentPowerUp == PowerUpType.Rockets && Input.GetKeyDown(KeyCode.F))
{
LaunchRockets();
}
if(currentPowerUp == PowerUpType.Smash && Input.GetKeyDown(KeyCode.Space) && !smashing) // smash powerupı aktifse ve F tuşuna basıldıysa ve player zıplamış değilse
{
smashing = true; // zıplamayı true yap
StartCoroutine(Smash()); // zıplama işlemini başlat
}
}
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Powerup"))
{
hasPowerUp = true;
currentPowerUp = other.GetComponent<PowerUp>().powerUpType;
powerupIndicator.gameObject.SetActive(true);
Destroy(other.gameObject);
if(powerupCountdown != null)
{
StopCoroutine(powerupCountdown);
}
powerupCountdown = StartCoroutine(PowerUpCooldownRoutine());
}
}
IEnumerator PowerUpCooldownRoutine()
{
yield return new WaitForSeconds(7);
hasPowerUp = false;
currentPowerUp = PowerUpType.None;
powerupIndicator.gameObject.SetActive(false);
}
private void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.CompareTag("Enemy") && currentPowerUp == PowerUpType.Pushback)
{
Rigidbody enemyRb = collision.gameObject.GetComponent<Rigidbody>();
Vector3 awayFromPlayer = collision.transform.position - transform.position;
enemyRb.AddForce(awayFromPlayer * powerUpStrength, ForceMode.Impulse);
}
}
void LaunchRockets()
{
foreach (var enemy in FindObjectsByType<Enemy>(FindObjectsSortMode.None))
{
tmpRocket = Instantiate(rocketPrefab, transform.position + Vector3.up, Quaternion.identity);
tmpRocket.GetComponent<RocketBehaviour>().Fire(enemy.transform);
}
}
}
Boss'umuzun hangi koşulda spawn edileceği SpawnManager güncellenerek belirtildi.
using UnityEngine;
public class SpawnManager : MonoBehaviour
{
public GameObject[] enemyPrefabs;
public GameObject[] powerupPrefabs; // birden fazla powerup eklenebilmesi için array tanımladık
private float spawnRange = 9f;
public int enemyCount;
public int waveNumber = 1;
// Boss Fight için gereken değişkenler
[Header("Boss Fight")]
public GameObject bossPrefab; // Boss prefab'ı
public GameObject[] miniEnemyPrefabs; // Mini düşman prefab'ı
public int bossRound; // kaç turda 1 boss spawn edileceği
void Start()
{
int randomPowerup = Random.Range(0, powerupPrefabs.Length);
SpawnEnemyWave(waveNumber);
Instantiate(powerupPrefabs[randomPowerup], GenerateSpawnPosition(), powerupPrefabs[randomPowerup].transform.rotation);
}
void Update()
{
enemyCount = FindObjectsByType<Enemy>(FindObjectsSortMode.None).Length;
if (enemyCount == 0)
{
waveNumber++;
if(waveNumber % bossRound == 0) // Boss spawn etme koşulu
{
SpawnBossWave(waveNumber);
}
else
{
SpawnEnemyWave(waveNumber);
}
int randomPowerup = Random.Range(0, powerupPrefabs.Length);
Instantiate(powerupPrefabs[randomPowerup], GenerateSpawnPosition(), powerupPrefabs[randomPowerup].transform.rotation);
}
}
void SpawnEnemyWave(int enemiesToSpawn)
{
for(int i = 0; i < enemiesToSpawn; i++)
{
Instantiate(enemyPrefabs[Random.Range(0, enemyPrefabs.Length)], GenerateSpawnPosition(), enemyPrefabs[0].transform.rotation);
}
}
private Vector3 GenerateSpawnPosition()
{
float spawnPosX = Random.Range(-spawnRange, spawnRange);
float spawnPosZ = Random.Range(-spawnRange, spawnRange);
Vector3 randomPos = new Vector3(spawnPosX, 0, spawnPosZ);
return randomPos;
}
void SpawnBossWave(int currentRound)
{
int miniEnemysToSpawn;
if(bossRound != 0) // her boss savaşında belirli aralıklarla boss levelinin yarısı kadar mini düşman spawn edilecek.
{
miniEnemysToSpawn = currentRound / bossRound;
}
else
{
miniEnemysToSpawn = 1;
}
var boss = Instantiate(bossPrefab, GenerateSpawnPosition(), bossPrefab.transform.rotation); // Boss'u spawn et
boss.GetComponent<Enemy>().miniEnemySpawnCount = miniEnemysToSpawn; // Boss'un mini düşman spawn etme sayısını ayarla
}
public void SpawnMiniEnemy(int amount)
{
for(int i = 0; i < amount; i++)
{
int randomMini = Random.Range(0, miniEnemyPrefabs.Length); // Mini düşman prefab'ını rastgele seç
Instantiate(miniEnemyPrefabs[randomMini], GenerateSpawnPosition(), miniEnemyPrefabs[randomMini].transform.rotation); // Mini düşmanı spawn et
}
}
}
Boss spawn edildiğinde nasıl davranacağı Enemy içinde yazıldı.
using UnityEngine;
public class Enemy : MonoBehaviour
{
public float speed = 3f;
private Rigidbody enemyRb;
private GameObject player;
// Boss Fight için gereken değişkenler
public bool isBoss = false; // Boss olup olmadığını kontrol etmek için
public float spawnInterval;
private float nextSpawn;
public int miniEnemySpawnCount; // Boss'un spawn edeceği mini düşman sayısı. Bu sayı SpawnManager'dan ayarlanacak.
private SpawnManager spawnManager;
void Start()
{
enemyRb = GetComponent<Rigidbody>();
player = GameObject.Find("Player");
if(isBoss)
{
spawnManager = FindAnyObjectByType<SpawnManager>();
}
}
void Update()
{
Vector3 lookDirection = (player.transform.position - transform.position).normalized;
enemyRb.AddForce(lookDirection * speed);
if(isBoss) // spawn edilen boss ise
{
if(Time.time > nextSpawn)
{
nextSpawn = Time.time + spawnInterval; // Spawn aralığını ayarlıyoruz
spawnManager.SpawnMiniEnemy(miniEnemySpawnCount); // Mini düşmanları spawn et. Fonksiyon spawnManager'da tanımlı.
}
}
if (transform.position.y < -10)
{
Destroy(gameObject);
}
}
}
Basit bir giriş sayfasında zorluk seçtikten sonra nesnelerin yukarıya uçtuğu, bizim de ekrana gelen nesneleri tıklayarak yok ettiğimiz bir oyun hazırladık.
using System.Collections; // IEnumerator için
using System.Collections.Generic; // List kullanabilmek için
using TMPro; // TextMeshPro kullanabilmek için
using UnityEngine.SceneManagement; // SceneManager kullanabilmek için
using UnityEngine.UI; // UI kullanabilmek için
using UnityEngine;
public class GameManager : MonoBehaviour // Oyun yöneticisi scripti. Game Manager adında bir empty Game Objecte atandı.
{
[Header("UI References")] // UI referansları için başlık ekliyoruz.
public TextMeshProUGUI scoreText; // TextMeshProUGUI kullanarak scoreText'i tanımlıyoruz.
public TextMeshProUGUI gameOverText; // TextMeshProUGUI kullanarak gameOverText'i tanımlıyoruz.
public Button restartButton; // Restart butonunu tanımlıyoruz.
public GameObject titleScreen; // Başlangıç ekranını tanımlıyoruz. (Title ve zorluk butonları bunun child'idır.)
[Header("Spawn Object References")] // Spawn nesne referansları için başlık ekliyoruz.
public List<GameObject> targets;
public bool isGameActive; // Oyun aktif mi değil mi kontrol etmek için bir boolean değişkeni tanımlıyoruz.
private int score; // Skor değişkenini tanımlıyoruz.
public float spawnRate = 1.0f;
IEnumerator SpawnTarget() // Nesnekeri Spawn eden fonksiyon. Başlatıldıktan sonra belirli aralıklarla tekrarlayan bir işlem olduğundan bu yöntemle yazıldı.
{
while (isGameActive) // while şart true olduğu sürece içerideki döngüyü devam ettirir.
{
yield return new WaitForSeconds(spawnRate); // spawnRate kadar bekle.
int index = Random.Range(0, targets.Count);
Instantiate(targets[index]); // targets listesinden rastgele bir nesne seçip instantiate ediyoruz.
}
}
public void UpdateScore(int scoreToAdd) // Skor güncelleme fonksiyonu.
{
score += scoreToAdd;
scoreText.text = "Score: " + score; // Skor metnini güncelliyoruz.
}
public void GameOver() // Oyun bitti fonksiyonu.
{
gameOverText.gameObject.SetActive(true); // Oyun bitti metnini gösteriyoruz.
restartButton.gameObject.SetActive(true); // Yeniden başlat butonunu gösteriyoruz.
isGameActive = false; // Oyun artık aktif değil.
}
public void RestartGame() // Yeniden başlatma fonksiyonu.
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name); // Mevcut sahneyi yeniden yüklüyoruz.
}
public void StartGame(int difficulty) // Oyun başlatma fonksiyonu. Zorluk seviyesini parametre olarak alır.
{
isGameActive = true; // Oyun aktif.
score = 0; // Skor değişkenini sıfırlıyoruz.
spawnRate /= difficulty; // Zorluk seviyesine göre spawnRate'i ayarlıyoruz.
titleScreen.gameObject.SetActive(false); // Başlangıç ekranını gizliyoruz.
StartCoroutine(SpawnTarget()); // SpawnTarget() fonksiyonunu başlatır.
UpdateScore(0); // Skor metnini güncelliyoruz.
}
}
using UnityEngine;
public class Target : MonoBehaviour // Hedef nesnesi için bir script. Bu script, hedef nesnelerin hareketini ve etkileşimlerini kontrol eder. Hedef olarak kullanılacak prefablara eklendi.
{
private Rigidbody targetRb;
private float minSpeed = 12;
private float maxSpeed = 16;
private float maxTorque = 10;
private float xRange = 4;
private float ySpawnPos = -6;
private GameManager gameManager; // GameManager scriptini tanımlıyoruz.
public int pointValue; // Target'ın puan değerini tanımlıyoruz.
public ParticleSystem explosionParticle;
void Start() // Nesne oluşturulduğunda çalışacak olan fonksiyon.
{
targetRb = GetComponent();
targetRb.AddForce(RandomForce(), ForceMode.Impulse);
targetRb.AddTorque(RandomTorque(), RandomTorque(), RandomTorque(), ForceMode.Impulse);
transform.position = RandomSpawnPos();
gameManager = GameObject.Find("Game Manager").GetComponent(); // GameManager scriptini buluyoruz.
}
private void OnMouseDown() // Obje mouse ile tıklandığında çalışacak olan fonksiyon.
{
if(gameManager.isGameActive) // Eğer oyun aktifse
{
Destroy(gameObject); // Mouse tıklandığında gameObject'i yok eder.
Instantiate(explosionParticle, transform.position, explosionParticle.transform.rotation); // Patlama efektini oluşturur.
gameManager.UpdateScore(pointValue); // GameManager'daki UpdateScore fonksiyonunu çağırır ve 1 ekler.
}
}
private void OnTriggerEnter(Collider other) // Bir nesne triger özellikli collider'a girdiğinde çalışacak olan fonksiyon. Bunun için nesnelerin ekranı terkettiği yerde isTrigger = true bir nesne yer alır
{
Destroy(gameObject); // Eğer bir nesne collider'a girerse onu yok eder.
if (!gameObject.CompareTag("Bad")) // Eğer nesne "Bad" tag'ine sahip değilse
{
gameManager.GameOver(); // GameManager'daki GameOver fonksiyonunu çağırır.
}
}
Vector3 RandomForce() // Rastgele bir kuvvet oluşturur.
{
return Vector3.up * Random.Range(minSpeed, maxSpeed);
}
float RandomTorque() // Rastgele bir tork oluşturur.
{
return Random.Range(-maxTorque, maxTorque);
}
Vector3 RandomSpawnPos() // Rastgele bir konum oluşturur.
{
return new Vector3(Random.Range(-xRange, xRange), ySpawnPos);
}
}
using UnityEngine.UI;
using UnityEngine;
public class DiffucltyButton : MonoBehaviour // Zorluk seviyesini ayarladığımız butonlara atadığımız script
{
private Button button;
private GameManager gameManager; // GameManager scriptini tanımlıyoruz.
public int difficulty; // Zorluk seviyesini tanımlıyoruz.
void Start()
{
button = GetComponent<Button>();
gameManager = GameObject.Find("Game Manager").GetComponent<GameManager>(); // GameManager scriptini buluyoruz.
button.onClick.AddListener(SetDifficulty); // Butona tıklandığında SetDifficulty fonksiyonunu çağırır.
}
void Update()
{
}
void SetDifficulty()
{
gameManager.StartGame(difficulty); // GameManager'daki StartGame fonksiyonunu çağırır.
}
}
Canvas içinde "Live Text" oluşturuldu ve yerleştirildi. "GameManager" scriptinde değişken olarak eklendi. Game Manager içinde inspectorde ataması yapıldı. "GameManager" scriptinde LiveUpdate() fonksiyonu oluşturuldu ve bu fonksiyon "Target" scriptinde kullanıldı
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityEngine;
public class GameManager : MonoBehaviour
{
[Header("UI References")]
public TextMeshProUGUI scoreText;
public TextMeshProUGUI gameOverText;
public TextMeshProUGUI liveText; // TextMeshProUGUI kullanarak liveText'i tanımlıyoruz.
public Button restartButton;
public GameObject titleScreen;
[Header("Spawn Object References")]
public List <GameObject> targets;
public bool isGameActive;
private int score;
private int lives; // can değişkeni
public float spawnRate = 1.0f;
IEnumerator SpawnTarget()
{
while (isGameActive)
{
yield return new WaitForSeconds(spawnRate);
int index = Random.Range(0, targets.Count);
Instantiate(targets[index]);
}
}
public void UpdateScore(int scoreToAdd)
{
score += scoreToAdd;
scoreText.text = "Score: " + score;
}
public void LiveUpdate() // Can güncelleme fonksiyonu
{
lives--; // Her can kaybında 1 azaltıyoruz.
liveText.text = "Lives: " + lives; // Can sayısını güncelliyoruz.
if (lives <= 0) // Eğer can sayısı 0 veya daha az ise
{
GameOver(); // Oyun bitiyor.
lives = 0; // Can sayısını 0 yapıyoruz.
}
}
public void GameOver()
{
gameOverText.gameObject.SetActive(true);
restartButton.gameObject.SetActive(true);
isGameActive = false;
}
public void RestartGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
public void StartGame(int difficulty)
{
lives = 3; // Oyuna başlarken can sayısını 3 yapıyoruz.
isGameActive = true;
score = 0;
spawnRate /= difficulty;
titleScreen.gameObject.SetActive(false);
liveText.text = "Lives: " + lives; // Can sayısını başlatıyoruz.
StartCoroutine(SpawnTarget());
UpdateScore(0);
}
}
using UnityEngine;
public class Target : MonoBehaviour
{
private Rigidbody targetRb;
private float minSpeed = 12;
private float maxSpeed = 16;
private float maxTorque = 10;
private float xRange = 4;
private float ySpawnPos = -6;
private GameManager gameManager;
public int pointValue;
public ParticleSystem explosionParticle;
void Start()
{
targetRb = GetComponent<Rigidbody>();
targetRb.AddForce(RandomForce(), ForceMode.Impulse);
targetRb.AddTorque(RandomTorque(), RandomTorque(), RandomTorque(), ForceMode.Impulse);
transform.position = RandomSpawnPos();
gameManager = GameObject.Find("Game Manager").GetComponent<GameManager>();
}
private void OnMouseDown()
{
if(gameManager.isGameActive)
{
Destroy(gameObject);
Instantiate(explosionParticle, transform.position, explosionParticle.transform.rotation);
gameManager.UpdateScore(pointValue);
}
}
private void OnTriggerEnter(Collider other)
{
Destroy(gameObject);
if (!gameObject.CompareTag("Bad")) // buradaki GameOver() fonksiyonunu sildik ve can azalma fonksiyonunu çağırdık. GameOver() fonksiyonu bir şarta bağlanarak LiveUpdate() içinden çağırılıyor.
{
gameManager.LiveUpdate();
}
}
Vector3 RandomForce()
{
return Vector3.up * Random.Range(minSpeed, maxSpeed);
}
float RandomTorque()
{
return Random.Range(-maxTorque, maxTorque);
}
Vector3 RandomSpawnPos()
{
return new Vector3(Random.Range(-xRange, xRange), ySpawnPos);
}
}
Oyunu duraklatacak bir buton ve duraklatmada gösterilecek bir panel oluşturduk. "GameManager" scriptine aşağıdaki fonksiyonu ekleyip foksiyonu butona atadık.
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityEngine;
public class GameManager : MonoBehaviour
{
[Header("UI References")]
public TextMeshProUGUI scoreText;
public TextMeshProUGUI gameOverText;
public TextMeshProUGUI liveText;
public Button restartButton;
public GameObject titleScreen;
public GameObject pausePanel; // pause paneli için referans
[Header("Spawn Object References")]
public List<GameObject> targets;
public bool isGameActive = false;
private int score;
private int lives;
public float spawnRate = 1.0f;
bool isPaused = false; // oyunun duraklatılıp duraklatılmadığını kontrol etmek için bir değişken
IEnumerator SpawnTarget()
{
while (isGameActive)
{
yield return new WaitForSeconds(spawnRate);
int index = Random.Range(0, targets.Count);
Instantiate(targets[index]);
}
}
public void UpdateScore(int scoreToAdd)
{
score += scoreToAdd;
scoreText.text = "Score: " + score;
}
public void LiveUpdate()
{
lives--;
liveText.text = "Lives: " + lives;
if (lives <= 0)
{
GameOver();
lives = 0;
}
}
public void GameOver()
{
gameOverText.gameObject.SetActive(true);
restartButton.gameObject.SetActive(true);
isGameActive = false;
}
public void RestartGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
}
public void StartGame(int difficulty)
{
lives = 3;
isGameActive = true;
score = 0;
spawnRate /= difficulty;
titleScreen.gameObject.SetActive(false);
liveText.text = "Lives: " + lives;
StartCoroutine(SpawnTarget());
UpdateScore(0);
}
public void PauseGame()
{
if(isGameActive)
{
isPaused = !isPaused; // Oyunun duraklatılıp duraklatılmadığını tersine çeviriyoruz.
if (isPaused)
{
Time.timeScale = 0; // Zamanı duraklatıyoruz.
pausePanel.SetActive(true); // Pause panelini açıyoruz.
}
else
{
Time.timeScale = 1; // Zamanı devam ettiriyoruz.
pausePanel.SetActive(false); // Pause panelini kapatıyoruz.
}
}
}
}
Tıklayıp kaydırdığında bir iz bırakan ve temas ettiği hedefleri yok eden fruit ninja tarzı bir yapı için "ClickAndSwipe" adında bir script oluşturduk ve bunu EmtyGameObject oluşturup içine attık.
using UnityEngine;
[RequireComponent(typeof(TrailRenderer), typeof(BoxCollider))] // Bu bileşenin ekli olduğu GameObject'e TrailRenderer ve BoxCollider bileşenlerinin zorunlu olarak eklenmesini sağlar.
public class ClickAndSwipe : MonoBehaviour
{
private GameManager gameManager; // GameManager scriptine erişim için bir referans
private Camera cam; // Ana kameraya referans
private Vector3 mousePos; // Fare konumunun dünya koordinatındaki karşılığı
private TrailRenderer trail; // İz (trail) çizmek için TrailRenderer bileşeni
private BoxCollider col; // Çarpışmaları algılamak için BoxCollider bileşeni
private bool swiping = false; // Kullanıcının şu anda sürükleme yapıp yapmadığını tutar
void Awake()
{
cam = Camera.main; // Ana kamerayı bulur ve referansa atar
trail = GetComponent<TrailRenderer>(); // Aynı GameObject'teki TrailRenderer bileşenini alır
col = GetComponent<BoxCollider>(); // Aynı GameObject'teki BoxCollider bileşenini alır
trail.enabled = false; // Başlangıçta iz çizilmesin
col.enabled = false; // Başlangıçta çarpışma olmasın
gameManager = GameObject.Find("Game Manager").GetComponent<GameManager>(); // "Game Manager" isimli GameObject'i bulup içindeki GameManager scriptine erişir
}
void UpdateMousePosition() // Fare pozisyonunu dünya koordinatlarına çevirip GameObject'in pozisyonunu günceller
{
mousePos = cam.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10f)); // Fare pozisyonu ekran koordinatlarında. Kamera z= -10f olduğundan bunu 10f derinliğe sahip dünya koordinatına çeviriyoruz.
transform.position = mousePos; // GameObject'in pozisyonu fare konumuna ayarlanıyor
}
void UpdateComponents() // TrailRenderer ve BoxCollider bileşenlerini swiping durumuna göre aktif/pasif yapar
{
trail.enabled = swiping; // Eğer swiping true ise trail çizilir
col.enabled = swiping; // Eğer swiping true ise collider aktif olur
}
void Update()
{
if(gameManager) // Eğer gameManager referansı varsa çalışmaya devam eder
{
if (Input.GetMouseButtonDown(0)) // Sol fare tuşuna basıldıysa
{
swiping = true; // Sürükleme başlatılır
UpdateComponents(); // Bileşenler aktif hale getirilir
}
else if (Input.GetMouseButtonUp(0)) // Sol fare tuşu bırakıldıysa
{
swiping = false; // Sürükleme durdurulur
UpdateComponents(); // Bileşenler pasif hale getirilir
}
if(swiping) // Eğer sürükleme devam ediyorsa pozisyon güncellenir
{
UpdateMousePosition(); // Fare konumuna göre nesne konumu güncellenir
}
}
}
private void OnCollisionEnter(Collision collision) // Başka bir nesneyle çarpışma gerçekleştiğinde çalışır
{
// Eğer çarpılan nesnenin tag'i "Target" ya da "Bad" ise:
if(collision.gameObject.CompareTag("Target") || collision.gameObject.CompareTag("Bad"))
{
// O nesnenin Target scriptine ulaşıp yok etme fonksiyonunu çağırır
collision.gameObject.GetComponent<Target>().DestroyTarget();
}
}
}
Kodu eklediğimiz EmptyGameObject seçiliyken inspectorde BoxCollider ve TrailRenderer içinde ayarlar değiştirilir.
Değişkenler ve event fonksiyonlar için referans chatgpt konuşması
Tekrar tekrak kullanılacak mermi vs nesneler için Object Pooling diye bir teknik kullanılabilir. Bunun için önce prefabı ve prefabdan oluşturulan nesneleri de tutan bir fonksiyon oluşturulur ve sahnede çağırılması için bir gameobjecte atanır.
using System.Collections.Generic;
using UnityEngine;
// Bu sınıf bir nesne havuzu (object pool) yöneticisi olarak kullanılacak
public class ObjectPool : MonoBehaviour
{
// Singleton mantığıyla tek bir örneğe erişmek için static değişken tanımlanır
public static ObjectPool SharedInstance;
// Havuzda tutulacak (hazır bekletilecek) GameObject listesidir
public List<GameObject> pooledObjects;
// Havuzda üretilecek olan prefab (örn: mermi prefabı)
public GameObject objectToPool;
// Havuzda başlangıçta kaç tane nesne üretileceğini belirler
public int amountToPool;
// Awake() fonksiyonu, script aktif olduğunda Start'tan önce çalışır
void Awake()
{
// Bu sınıfın örneğini static olarak saklıyoruz (Singleton gibi)
SharedInstance = this;
}
// Start() fonksiyonu oyun başladığında bir kez çalışır
void Start()
{
// Boş bir liste oluşturuyoruz
pooledObjects = new List<GameObject>();
GameObject tmp;
// Belirtilen sayıda nesneyi havuzda oluşturuyoruz
for (int i = 0; i < amountToPool; i++)
{
// Prefab'den bir nesne oluştur
tmp = Instantiate(objectToPool);
// Şimdilik bu nesneyi sahnede aktif etme (gizli tut)
tmp.SetActive(false);
// Havuz listesine ekle
pooledObjects.Add(tmp);
}
}
public GameObject GetPooledObject()
{
// Havuzda aktif olmayan bir nesne bul
for (int i = 0; i < pooledObjects.Count; i++)
{
// Eğer nesne aktif değilse, onu döndür
if (!pooledObjects[i].activeInHierarchy)
{
return pooledObjects[i];
}
}
// Eğer havuzda aktif olmayan bir nesne yoksa, null döndür
return null;
}
}
Daha sonra ateş etme fonksiyonu olarak Instantiate(...) yerine:
...
{
GameObject bullet = ObjectPool.SharedInstance.GetPooledObject(); //ObjectPool'dan bir nesne alır
bullet.transform.position = transform.position; //Nesnenin pozisyonunu oyuncunun pozisyonuna ayarlar
bullet.transform.rotation = transform.rotation; //Nesnenin rotasını oyuncunun rotasına ayarlar
bullet.SetActive(true); //Nesneyi aktif hale getirir
}
...
Destroy() ile nesne yok etmek yerine de:
...
gameObject.SetActive(false);
...
kodları kullanılır. Herhangi bir şekilde nesne havuzunda oluşturulan ve kullanılan nesnelerden silinen olursa kod hata verir.
Data kalıcılığı için iki ayrı senaryo için iki farklı metot kullandık. İlk metot datanın scene değiştiğinde de varlığını koruması için kullanıldı. "Singleton" olarak adlandırılan bu metotta bir gameobject oluşturduk. Bu nesneyi DontDestroyOnLoad() metotu ile scene değişikliklerinde de korunur hale getirdik. Bu sayede nesnede yer alan data korunmuş oldu. "static" özelliği de değişkenin içeriğinin sınıf üzerinden doğrudan ulaşılabilmesini sağladı.
using UnityEngine;
using System.IO; // File işlemleri için gerekli
public class MainManager : MonoBehaviour
{
public static MainManager Instance; // static niteliği verinin dışarıdan erişilmesini sağlar.
public Color TeamColor;
private void Awake()
{
// Singleton pattern: Bu sınıfın tek bir örneği olmasını sağlar
if (Instance != null)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject); // nesnenin sayfa geçişlerinde silinmesini engeller.
}
}
Verimizin proje kapatıldığında da saklanabilmesi için veri JSON formatına çevirilip bir dosyaya kaydedilir. Bu şekilde saklanabilmesi için verinin bir kalıbının (class) oluşturulması gerelir. Verinin JSONa çevirilebilmesi için class kalıbının [System.Serializable] ön eki ile serializable yapılması gerekir.
...
[System.Serializable] // Bu sınıfın JSON formatında serileştirilmesini sağlar
class SaveData // Veri kaydetmek için kullanılacak sınıf
{
public Color TeamColor;
}
public void SaveColor() // Rengi kaydetmek için kullanılacak fonksiyon
{
SaveData data = new SaveData(); // Yeni bir veri nesnesi oluştur
data.TeamColor = TeamColor; // Rengi ata
string json = JsonUtility.ToJson(data); // Veriyi JSON formatına çevir
File.WriteAllText(Application.persistentDataPath + "/savefile.json", json); // Veriyi dosyaya kaydet
}
public void LoadColor() // Rengi yüklemek için kullanılacak fonksiyon
{
string path = Application.persistentDataPath + "/savefile.json"; // Dosya yolu
if (File.Exists(path)) // Dosya var mı kontrol et
{
string json = File.ReadAllText(path); // Dosyadan veriyi oku
SaveData data = JsonUtility.FromJson<SaveData>(json); // JSON formatından nesneye çevir
TeamColor = data.TeamColor; // Rengi ayarla
}
}
...
Aşağıdaki kod sadece Unity testlerinde şartlı çalışır. Amacı da zaten editörde mi reel de mi olduğunu anlayıp ona göre davranmaktır.
public void QuitGame()
{
#if UNITY_EDITOR // editörde isek çalışır
UnityEditor.EditorApplication.isPlaying = false;
#else // editörde değilsek çalışır
Application.Quit();
#endif // şartı sonlandırır.
}
Nesne yönelimli programlama (OOP), birbirleriyle ilişkili yöntem ve değişkenlerin bir nesne (OOP'deki "nesne"!) olarak adlandırılan şeyi oluşturmak üzere bir araya getirildiği bir programlama modelidir.
Nesne yönelimli programlamanın dört ana ilkesi vardır ve bunlar genellikle OOP'nin sütunları olarak adlandırılır. Bunlar: soyutlama, kapsülleme, kalıtım ve polimorfizmdir. Nesne yönelimli programlama modelini başarılı bir şekilde kullanmak için bu dört ilkeyi öğrenmeniz ve uygulamanız yeterlidir!
Soyutlamanın temeli bir fonksiyon ve kavram olarak ayrılabilecek kodları ayrı bir fonksiyon haline getirmek ve kullanılacağı yerde bir fonksiyon olarak çağırmaktır.
public void Update()
{
if (Input.GetMouseButtonDown(0))
{
sayi++;
Debug.Log("Sayı kazanıldı");
UIMainScene.Instance.SetNewInfoContent(uiInfo);
}
}
yerine
public void Update()
{
if (Input.GetMouseButtonDown(0))
{
SayiArtisi();
}
}
pubic void SayiArtisi()
{
sayi++;
Debug.Log("Sayı kazanıldı");
UIMainScene.Instance.SetNewInfoContent(uiInfo);
}
Bu şekilde "sayı arttırma" görevi ile bağlantılı kodlar SayiArtisi(); fonksiyonunda soyutlandı. Bu sayede hem okunaklı hale geldi, hem başka bir yerde gerekirse tekrar kullanılabilir hem de diğer kodlarla karışmadan bu işlevle ilgili düzenleme yapılabilir.
Soyutlama, kodunuzu basitleştirir ve temiz tutarak programcıların kullanımını kolaylaştırır. Soyutlamanın temel ilkesi, gereksiz karmaşıklığı diğer programcılardan gizlemek ve yalnızca kodun olması gerektiği gibi çalışması için gerekenleri ortaya çıkarmaktır. Bu, karmaşık iç işleyişi alıp yerine soyut, daha yeniden kullanılabilir kod parçaları koymak anlamına gelir. Bu ilkeye uymak, daha sonra yeniden düzenleme sürecini de basitleştirir.
Nesne yönelimli programlamanın sonraki iki ayağı olan kalıtım ve çok biçimlilik birbiriyle derinden ilişkilidir. Kalıtım, adından da anlaşılacağı gibi, farklı nesneler arasındaki ebeveyn-çocuk ilişkilerine odaklanır. Çok biçimlilik kalıtımın bir sonucudur ve bir alt sınıfın bir üst sınıftan miras aldığı şeyi değiştirmesi sürecini ifade eder. Kalıtım ve çok biçimlilik birlikte kullanıldığında bir uygulamada yazmanız gereken kod miktarını azaltabilir.
Kalıtım, kendisinden diğer sınıfların (alt sınıflar olarak adlandırılır) oluşturulabileceği bir ana sınıf (üst sınıf olarak da bilinir) oluşturma sürecidir. Bir alt sınıf, ana sınıfın tüm özelliklerini otomatik olarak üstlenir veya miras alır. Bir uygulamada farklı sınıfların benzer özellikleri paylaşması yaygın bir durumdur. Örneğin, bir video oyununda birçok farklı türde düşman sınıfı bulunabilir, ancak kendi sağlıklarını yönetme ve oyuncuya hasar verme yeteneği gibi aynı temel özellikleri paylaşmaları muhtemeldir. Kalıtım sayesinde, her bir düşman sınıfı için bu sağlık ve hasar işlevlerini yazma ihtiyacı ortadan kalkar, böylece her bir sınıfa özgü işlevleri yazmaya odaklanabilirsiniz.
MonoBehaviour, tüm temel Unity komut dosyası işlevlerinin miras aldığı temel sınıftır. MonoBehaviour olmadan, OnTriggerEnter, GetComponent'i çağıramaz, hatta Start ya da Update'i bile kullanamazdınız!
Yukarıdaki diyagramda, miras aldıkları sınıfı MonoBehaviour'dan Enemy'ye değiştirdikleri için tüm alt düşman sınıflarının Unity işlevselliğine erişme yeteneklerini kaybedecekleri görülebilir. Neyse ki, Enemy sınıfı Monobehaviour'dan miras aldığından, Enemy sınıfının çocukları da MonoBehaviour'un çocukları olarak kabul edilir!
Bir üst sınıftan temel işlevselliği miras almak yararlı olsa da, alt sınıfın üst sınıfla tam olarak aynı eylemi gerçekleştirmesini istemediğiniz birçok durum vardır. polimorfizm, bir nesnenin ana sınıfından miras aldığı işlevselliği değiştirmenize olanak tanır.
public class Enemy : MonoBehaviour
{
public void DealDamage ()
{
Player.Health -= 10;
}
}
Yukarıdaki örnekte, Enemy sınıfının, çağrıldığında Oyuncunun canından 10 puan silen bir DealDamage yöntemi vardır. Enemy sınıfının bir çocuğu olan Thief sınıfı, bu yöntemi sınıf içinde bildirmeden çağırabilir.
public class Thief : Enemy
{
private void Update()
{
if (Player.isSeen)
{
DealDamage(); // üst sınıftaki yöntem çağrılabilir
}
}
}
Hırsız'ın Düşman sınıfıyla tam olarak aynı miktarda hasar vermesini istiyorsanız bu iyidir, ancak bunun farklı bir değer olmasını istiyorsanız ne olur? Bu değişiklikler, yöntem geçersiz kılma olarak bilinen süreçle gerçekleştirilir.
Ana sınıfta geçersiz kılmak istediğiniz yöntem öncelikle geçersiz kılınmak üzere işaretlenmelidir. Bu, onu sanal bir yöntem haline getirerek yapılır:
public class Enemy : MonoBehaviour {
public virtual void DealDamage () { // virtual anahtar sözcüğü geçersiz kılmaya izin verir
Player.Health -= 10;
}
}
Bir metodun sanal olarak tanımlanması, o metodun geçersiz kılınabileceğini ancak geçersiz kılınmak zorunda olmadığını gösterir. Bu mevcut örnek için idealdir, çünkü Thief alt sınıfının DealDamage yöntemini değiştirmesi gerekebilirken, Scoundrel sınıfı gibi başka bir alt sınıfın bunu yapması gerekmeyebilir.
DealDamage virtual olarak ayarlandıktan sonra, Thief sınıfı DealDamage için kendi yöntemini oluşturarak bunu geçersiz kılabilir. Burada virtual yerine override notasyonunu kullanacağız. Artık yönteme özellikle Hırsız sınıfı için yeni işlevler ekleyebilirsiniz:
public class Thief : Enemy
{
public override void DealDamage() // üst sınıftaki sanal yöntemleri geçersiz kılabilir
{
Player.Health -= 2;
CommitPettyTheft();
}
private void Update()
{
if (Player.isSeen)
{
DealDamage();
}
}
}
Thief sınıfı artık ana Düşman sınıfından daha az miktarda hasar verir ve ayrıca Thief'e özgü yöntemlerden birini çağırır. Artık DealDamage bir Thief nesnesi tarafından Update içinde çağrıldığında, ana yöntem yerine özelleştirilmiş DealDamage yöntemi çağrılacaktır.
override edildikten sonra parent class'taki kalıtılan koda ihtiyaç duyarsak da "base" üzerinden çağırabiliriz.
...
base.DealDamage();
...
Soyutlama gibi, kapsülleme de kodunuzun altında yatan karmaşıklık ile onu kullanan diğer kodlar arasında bir ayrım seviyesinin korunmasına büyük ölçüde odaklanır. Benzerlikleri nedeniyle, bazen OOP uygulamalarının bazı takipçilerinin soyutlama ve kapsüllemeyi tipik olarak kapsülleme başlığı altında tek bir sütunda grupladığını göreceksiniz. Ancak, biz önemli olduğunu düşündüğümüz bir ayrım yapıyoruz: soyutlama tamamen kodu diğer programcılar için daha basit hale getirmek üzere özetlemekle ilgiliyken, kapsülleme tamamen değerleri ve verileri bir kapsülün içindeymiş gibi korumakla ilgilidir, böylece başkalarının neye erişip erişemeyeceğini kontrol edersiniz.
Özetle verilere dışarıdan erişimi kısıtlamakla ilgilidir.
Esasen, yapmak istediğimiz şey değişkeni "salt okunur" yapmak, diğer sınıfların değeri almasına izin vermek, ancak değeri ayarlamamaktır. Bunu yapmak için, değişkenimizin nasıl ve ne zaman kullanılabileceğini kontrol edecek olan C# get ve set yöntemlerini kullanabiliriz.
public static MainManager Instance { get; }
Bir değişkene get veya set erişimcisi eklediğiniz anda, bu değişken bir Özellik haline gelir - bu özel yöntemler aracılığıyla dahili verilere erişim sağlayan özel bir değişken türü.
Bu haliyle başka bir script üzerinden veri alınabilir ama değiştirilemez.
public static MainManager Instance { get; private set; }
Bu kodla, artık özelliğin değerini sınıf içinden ayarlayabilir, ancak yalnızca sınıf dışından alabilirsiniz. Yalnızca kendi sınıfından değişiklikleri kabul edecek şekilde kapsüllenmiştir, dış dünyadan kötüye kullanım ve bozulmaya karşı güvenlidir!
Negatif değerler almasını istemediğimiz bir kodumuz olsun.
public float ProductionSpeed;
Değerleri sınırlamak için bir yedekleme alanına ihtiyacımız var.
private float m_ProductionSpeed = 0,5f; // yedekleme alanı ekledik.
public float ProductionSpeed;
public olan ProductionSpeed'e erişildiğinde verilecek default değeri yedekleme alanından (m_ProductionSpeed) aldık. Set edilecek değer olarak da yedekleme alanını gösterdik.
private float m_ProductionSpeed = 0.5f;
public float ProductionSpeed // semicolonu sildik
{
get { return m_ProductionSpeed; } // yedekleme alanı değerini gönder
set { m_ProductionSpeed = value; } // yedekleme alanı değişkenini değiştir.
}
Aynı scrips içinde değişiklikleri yedekleme alanından almamız gerektiğinden bu scriptteki ProductionSpeed kısımlarını m_ProductionSpeed ile değiştirdik.
ProductionSpeed için set edilen değeri kısıtladık.
private float m_ProductionSpeed = 0.5f;
public float ProductionSpeed // semicolonu sildik
{
get { return m_ProductionSpeed; }
set
{
if (value < 0.0f)
{
Debug.LogError("You can't set a negative production speed!");
}
else
{
m_ProductionSpeed = value;
}
}
}
Kapsülleme, kodunuzun yalnızca açıkça tasarlandığı şekilde kullanılması için değerlere erişme ve bunları değiştirme yollarını kontrol ederek kodunuzu korur. Soyutlama gibi, kapsülleme de kodunuzun altında yatan karmaşıklık ile ona erişebilecek programcılar arasına bir ayrım katmanı yerleştirir.