Unity’de Update() ve FixedUpdate() Fonksiyonları

alihan
6 min readJan 29, 2024

--

Unity logosunu içeren bir görsel
Unity logosu

Herkese merhaba,

Daha önceki yazımda bahsettiğim üzere 2024 hedeflerim arasında daha çok dijital içerik üretmek var. Bu gayretle ilgi alanlarım dahilinde blog postları ve youtube videoları hazırlamayı düşünüyorum. Açıkçası yazı yazmak bana daha kolay geldiği için bugünkü içeriğimi de buradan paylaşmak istedim. İlerleyen zamanlarda fırsat bulabilirsem konuyla alakalı pratikleri içeren bir video hazırlayacağım. Bugünkü yazının konusu ilk karşılaştığımda kafamı karıştıran Unity’de Update ve FixedUpdate fonksiyonları olacak. Hepinize keyifli okumalar.

Konuya geçmeden önce bize yardımcı olması için bahsetmek istediğim birkaç nokta var. Bunlardan ilki frame.

Frame nedir?

Hiç bilmeyenler için söyleyecek olursak frame, basitçe bir resim karesidir ve ekranda 1 saniye süre içerisinde belli bir sayıda resim oluşur. Bu resimlerin peşpeşe oluşuyor olmasından dolayı ortaya bir hareket ilüzyonu çıkar. Çünkü gözlerimiz saniyede en az 10 resim karesini peşpeşe gördüğünde hareketli olarak algılar. Bu 1 saniye içinde akan resim sayısına ise FPS( frame per second) denir. Filmlerde bu oran 24, canlı yayınlarda ise 30 civarındadır.

Oyunlarda ise akıcılık için bu oranın en az 60 olması gibi bir kabul bulunmaktadır. 60 FPS’i milisaniye olarak düşünürsek , (1/60)x1000=16,67 yani iki frame arasında 16,67 milisaniye bulunmaktadır diyebiliriz. Aşağıda farklı FPS değerleri için akıcılığı kıyaslayabilmemiz için bir örnek görmektesiniz.

Farklı FPS değerleri için hareketleri gösteren bir gif (from: https://tenor.com/)

Bunun dışında Unity’de bilmemiz gereken diğer bir frame kavramı ise fizik frame’idir. Bu tamamen fizik motorumuzun ayarları ile alakalıdır ve bir frame süresi içinde bir kez, birden çok kez ya da hiç fizik frame’i oluşmayabilir. İşte tam da burası Update() ve FixedUpdate() karşılaştırmamızın temelinde yatan mantığı oluşturuyor.

Ama önce bir diğer önemli nokta olan MonoBehaviour’dan biraz bahsedelim.

MonoBehaviour nedir?

MonoBehaviour, Unity API’sinin bir parçası ve oyun motorunun en temel bileşenlerinden biridir. Unity’de varsayılan olarak her şeyin türemiş olduğu bir temel sınıftır diyebiliriz. Karakterler, kameralar, scriptler ve UI elemanları gibi. Ayrıca Unity’de bir nesne eklediğimizde onun davranışlarını ve yazılım içindeki yaşam döngüsünü kontrol eder. Bunun yanında coroutinleri desteklemesi, komponent erişimine imkan vermesi, sahne görünümünde iken ekstra bilgileri göstermesi (Gizmos), oyun içinde debugging yapmaya (oyun ekranında iken değişken değerlerini izleyebilme) imkan vermesiyle geliştirme sürecini oldukça kolaylaştırır.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CubeManager : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{

}
// Update is called once per frame
void Update()
{

}
}

MonoBehaviour sınıfı beraberinde yaşam döngüsü olaylarını çalıştıracak bir çok fonksiyon içerir (MonoBehavior CallBack functions). Bunlar oyun nesnelerinin oluşturulması, güncellenmesi ve yok edilmesi sırasında otomatik olarak Unity tarafından çağırılır.

Peki neden Unity tarafından çağırılır diye soracak olursanız cevabı yine Unity’nin kendisi verecektir. Çünkü oyun motorunun kendisi oyun nesnelerinin varlığının ve davranışının tutarlı bir şekilde yönetilmesi için optimize edilmiştir. Örneğin oyun sahnesi yüklendiğinde Awake() ve Start() fonksiyonları otomatik olarak çağırılacaktır. Tabiki geliştiricilerin Unity’nin bu özelliğini bypass edip kendi yaşam döngüsü sistemlerini kodlamaları mümkündür. Fakat bu pek çok performans sorununu beraberinde getirir, iş gücü olarak çok fazla ek çalışma ister ve hataya her zaman açıktır. Burada sağladığı kolaylığa teslim olmak en iyi seçenektir.

Aşağıda Unity’de yer alan bazı yaşam döngüsü metotlarını görmektesiniz. Daha detaylı olan Unity’nin kendi dokümantasyonunu kaynakça kısmına ekledim, inceleyebilirsiniz.

  • Start(): Oyun sahnesi açıldığında bir kez çalışan fonksityondur. İlk hesaplamaları yapmak için kullanılır
  • Awake(): Oyun nesneleri yaratıldığı anda çağırılır. Başlangıç değerlerini ayarlamak için kullanılır. Start() fonksiyonunda önce çalışır.
  • Update(): Oyunun her karesinde (frame) bir kez çalışır. Oyun içi sürekli güncellemeler için kullanılır. Zamanla değişen olaylar Update() altına kodlanır.
  • FixedUpdate(): Sabit bir zaman aralığında sürekli çağırılır. Rigidbody ile yapılan işlemler genelde bruada gerçekleştirilir.
  • OnDestroy(): Oyun nesnesi yok edilmeden hemen önce çağırılır.

Şimdi gelelim yazımızın asıl konusuna, yani Update() ve FixedUpdate() fonksiyonları arasındaki farklar nedir? Niçin 2 farklı güncelleme fonksiyonuna ihtiyaç duyuyoruz.

Update() vs. FixedUpdate()

Update()

Update() fonksiyonun her karede 1 kez çağırıldığını biliyoruz. Bu da demektir ki, kare hızı ne kadar yüksekse o kadar sık çağırılır. Aynı durum tam tersi içinde geçerlidir. Yazının başlarında 60 FPS’lik bir durumda iki frame arasının 16,67 milisaniye olduğunu da hesaplamıştık.

Update() içinde kullanıcı girdileri ve animasyon güncellemeleri gibi olaylar için kullanılır. Time sınıfının deltaTime propertysi kullanılarak kare hızından bağımsız olarak çalışır yani farklı FPS’den bağıomsız olarak

FixedUpdate()

FixedUpdate() fonksiyonu ise sabit bir zaman aralığında çağırılır ve Unity’de default olarak saniyede 50 kez çalışır. Bu da iki frame arasında 20 milisaniye olması demektir. Fizik hesaplamaları (Rigidbody ile etkileşim işleri) burada yapılır. Unity’nin fizik motoru ile etkileşimi için kullanılır diyebiliriz. Çünkü Unity’nin fizik motoru(PhysX) sabit zaman aralıklarında güncellenir ve FixedUpdate bu güncellemeler ile senkronize şekilde çalışır. Oyunların 60 FPS olduğunu düşündüğümüzde her saniye başına 60 resim karesi oluşurken 50 fizik frame’i oluşmaktadır. Aradaki bu fark düzgün yönetilmediğinde ise oyunların akıcılığında ciddi problemlere yol açmaktadır.

Peki bu iki fonksiyonu karıştırmak nelere yol açar? Örneğin bir fizik hesaplaması yapacağınızı ve bunu Update() fonksiyonu içinde yaptığınızı varsayalım. Bu da demek oluyor ki farklı kare hızlarına bağlı olarak hesaplamanız değişkenlik gösterecektir, yani oyununuz farklı donanımlarda farklı şekilde çalışacaktır.

Daha spesifik bir örnek verelim. Fiziksel bir hareketi Update() fonksiyonu içinde yazdığımızı varsayalım. Karakterimize zıplama özelliği kazandırmak istedik ve bunu şu şekilde Update() fonksiyonu içine kodladık:

private float jumpForce = 20f;
void Update()
{
if (Input.GetButtonDown("Jump"))
{
this.GetComponent<Rigidbody>().AddForce(0, jumpForce, 0, ForceMode.Impulse);
}
}

Bu kod, oyuncu tuşa bastığında, karakterin Rigidbody bileşenine bir kuvvet uygular ve karakteri havaya doğru zıplatır. Fakat, kare hızı çok yüksek olan bir sistemde Update() çok sık çağırılır ve karakterin birden fazla karede zıplamasına sebep olabilir. Tam tersini düşündüğümüzde ise, yani düşük kare hızına sahip bir sistemde karakterin zıplama tepkisi gecikebilir çünkü fizik motoru ile Update fonksiyonunun çalışma zamanı senkronize olmaz ve komut hiç çalışmayabilir.

Şimdi aynı mekaniği FixedUpdate() içine yazdığımızı düşünelim.

private float jumpForce = 20f;
void FixedUpdate()
{
if (Input.GetButtonDown("Jump"))
{
this.GetComponent<Rigidbody>().AddForce(0, jumpForce, 0, ForceMode.Impulse);
}
}

Bu durumda if sorgusu default olarak her 20 milisaniyede bir yapılır. Fakat çok hızlı sistemlerde biz zıplama inputunu göndermiş olsak bile Unity bu komutu ıskalayacaktır ve karakterimiz zıplamayacaktır.

Ancak, eğer bu input kodunu Update() yerine FixedUpdate() fonksiyonu içine koyarsak, oyunun fizik motoruyla daha doğru bir şekilde etkileşim kurar ve donanımdan donanıma değişen performans sorunlarını önlemiş oluruz. Aslında bahsedilen hata tamamen şu mantık sırasının bozulmasından kaynaklanıyor. Update() ve FixedUpdate() fonskiyonları sırayla birbirini takip edercesine çalışmıyor. Bazen hızlı sistemlerde Update() fonksiyonu iki FixedUpdate() fonksiyonunun arasında birden fazla kez çalışıyor ya da yavaş sistemler için tam tersi. Bu mantık sırasındaki bozulmayı önleyebilmek içinde aşağıdaki gibi bir yol izleyebiliriz.

Doğru Kullanım

Unity’de karşılaştığımız bu problemin üzerinde gelebilmek için yapmamız gereken aslında oldukça basittir. Kullanıcıdan input aldığımız kısmı Update() içinde, rigidbody componenti üzerinde yaptığımız değişiklikleri ise FixedUpdate() içinde gerçekleştirmeliyiz. Yukarıda yaptığımız örneğin doğrusunu aşağıya yazacak olursak:

private bool jumpRequested = false;
void Update()
{
// Girdi işleme Update içinde yapılır
if (Input.GetButtonDown("Jump"))
{
jumpRequested = true;
}
}
void FixedUpdate()
{
// Fizik güncellemeleri FixedUpdate içinde yapılır
if (jumpRequested)
{
this.GetComponent<Rigidbody>().AddForce(0, jumpForce, 0, ForceMode.Impulse);
jumpRequested = false; // Zıplama isteği işlendikten sonra sıfırlanır
}
}

Unity’de, Update() metodu kare hızına bağlı olarak değişken bir zaman aralığında çağırılırken FixedUpdate() metodunun sabit bir aralıkta çağırıldığını söylemiştik. İşte aradaki bu farktan ötürü kullanıcı girdilerinin kaybolmaması için onları Update() fonksiyonu içinde çağırıyoruz. Burada boolean “jumpRequested” değişkeni Update() içinde kullanıcıdan girdi alındığında true değerini alıyor ve FixedUpdate içindeki koşul bu değere bağlanıyor. Bu yöntem ile Update() ve FixedUpdate() fonksiyonlarımızı senkronize etmiş oluyoruz. Daha sonra bu değeri false olarak güncelliyoruz ki bir sonraki fizik frame’inde karakterimiz input göndermediğimiz halde tekrar zıplamasın. Bütün bu işlemlerin sonucu olarak sahnemiz hem yüksek hem düşük kare hızlarına sahip sistemlerde istenildiği gibi çalışacaktır.

Son Söz

Konuyu şimdilik burada bırakıyorum. Umarım okuyan herkes için anlaşılır bir içerik olmuştur. Aşağıda bıraktığım linkleri takip ederek konu ile alakalı daha detaylı okumalar yapabilirsiniz. Ben de fırsat bulabilirsem konuyu örneklediğim bir video hazırlayıp sizinle paylaşabilirim. Buraya kadar okuduğunuz için hepinize teşekkür ederim. Sevgiler.

Kaynakça

--

--

No responses yet