holodepth

HTML5 Canvas · Sprite ve varlık sistemi

Kare animasyonu: zamanla ilerleyen kare indeksi

Sprite levhasında hangi dilimin çizileceği ( ızgara veya meta ) görsel düzen işidir; hangi karenin şu anda seçili olduğu ise zamana bağlı oyuncu durumu veya nesne durumudur. Bu sayfa kare süresini ( frame duration ), birikimli dt ile güvenli ilerlemeyi ve döngü ( loop ), tek sefer, ileri–geri ( ping-pong ) gibi oynatma modlarını Canvas oyun döngüsü açısından sabitler. Rasterın belleğe alınması Resim yükleme sayfasında; dilim koordinatlarının drawImage’e aktarımı sprite levhası başlığında kalır.

Gerçek zaman ölçeği için Delta time ve kare yaşamı için Kare yönetimi ile birlikte okuyun; oyun fazı ve duraklatma oyun durumu ile bağlanabilir.

Özet: animasyon durumu ve çizim

Veri Rol Bağlantı
dt Kare süresi ( saniye ) Birikimci
secondsPerFrame Bir sprite karesinin ekranda kalma süresi TPS = 1 / SPF
index Aktif levha hücresi ( 0 tabanlı ) Izgara
Oynatma modu Döngü / bir kez / ping-pong Son kare politikası
Çizim drawSpriteSlice ile sabit index Levha sayfası

Ayık kare ve sürekli zaman: indeks üretimi

Film şeridi gibi düşünün: görüntü sürekli değişmez, belirli aralıklarla bir sonraki kareye geçilir. Oyunda «kare» süresi ekran yenileme hızından ( 60 Hz ) bağımsız tasarlanır: yürüyüş animasyonu saniyede 12 gerçek sprite karesi gösterebilirken tuval her karede çizilir — çizilen sprite karesi iki üç ekran karesinde aynı kalabilir.

Bu ayrım, animasyonu «zaman tabanlı» yapar: düşük FPS’te oynanış yavaşlar ama animasyon saniye başına aynı sayıda sprite karesi göstermeye çalışır ( büyük dt sıçramalarında birden çok kare atlama — birikimci bölüm ). Film karesi gibi sabit ekran başına bir sprite karesi isterseniz secondsPerFrame değerini ekran süresine eşlersiniz — tasarım tercihidir.

Her varlık için ayrı zamanlayıcı tutmak doğaldır; aynı levha üzerinde birden fazla bağımsız klip ( farklı satır veya indeks aralığı ) oynatmak için hem index hem «hangi klip» kimliği ( satır ofseti ) ayrı alanlarda tutulur — levha geometrisi sprite levhası sayfasında çözülür.

Birikimci zaman ve güvenli atlama

Her güncellemede index += dt * k gibi doğrusal sürekli ilerleme basit kaydırmalarda işe yarar; sprite kareleri için tipik yol birikimci ( accumulator ): acc += dt, acc kare süresini aştıkça bir veya daha fazla kez kare sayacını artır ve fazlalığı acc’de bırak. Böylece iki kare sonra dönen oyuncu «iki kare birden atlayarak» senkron kalır.

Uzun sekme aralarında dt büyür; döngü içinde çok kez adım atmak doğru davranış olabilir ( hızlı ileri sarı ). Tavan koymak ( bir güncellemede en fazla N adım ) bazen oyun tasarımı gereği eklenir — yoksa tek tick’te tüm animasyonu tüketmiş olursunuz. Bu politika delta time tartışmasıyla paraleldir.

Sabit setInterval ile animasyon güncellemesi yapmak, ana döngüden kopuk zaman kaynağı üretir; requestAnimationFrame tabanlı oyunda animasyon ilerlemesini yine aynı oyunda ölçülen dt ile beslemek tutarlılık sağlar.

/**
 * loop: true → index 0..frameCount-1 döner; false → son karede finished.
 * dt: saniye (delta time).
 */
function createFrameClip(frameCount, secondsPerFrame, loop = true) {
  return {
    frameCount,
    secondsPerFrame,
    loop,
    acc: 0,
    index: 0,
    finished: false,
  };
}

function stepFrameClip(c, dt) {
  if (c.finished || c.frameCount <= 0 || c.secondsPerFrame <= 0) return;
  c.acc += dt;
  while (c.acc >= c.secondsPerFrame) {
    c.acc -= c.secondsPerFrame;
    const next = c.index + 1;
    if (next >= c.frameCount) {
      if (c.loop) c.index = 0;
      else {
        c.index = c.frameCount - 1;
        c.finished = true;
        break;
      }
    } else {
      c.index = next;
    }
  }
}

Kare hızı ve saniye başına kare: tasarım parametreleri

secondsPerFrame doğrudan hissettirir: 1/12 saniye tipik el çizimi hissi, 1/24 sinematik, 1/60 ise çoğu zaman neredeyse her ekran karesinde bir atlas karesi demektir. Tersi TPS ( ticks per second ) olarak da düşünülür — kodda tek temsil seçin, iki formu aynı yerde karıştırmayın.

Farklı animasyonlar farklı süreler taşır: «ateş et» tek seferlik üç kısa kare, «nefes al» uzun aralıklı iki kare. Bu yüzden sabit global «animasyon hızı» yerine klip başına secondsPerFrame yaygındır; paylaşılan sabitler için adlandırılmış sabitler ( WALK_SPF ) kullanın.

Zaman ölçeği yavaşlatma / hızlandırma ( güç toplama efekti ) çarpanı tüm klibe uygulanabilir: efektif süre secondsPerFrame / speedMul — çarpanın sıfıra yaklaşması sonsuz yavaşlama üretir, üst sınır koyun.

İleri–geri oynatma ve uç kareler

Ping-pong, son kareye gelince yönü çevirerek ileri ve geri oynatır; döngüsel klip ile aynı değildir ( son kareden birinci kareye sıçramaz ). Ateşleme veya tek yönlü yürüyüş genelde düz döngü veya bir kez oynatmadır; meşale, sarkaç gibi nesneler ping-pong ile ucuz canlanır.

frameCount === 1 durumunda yön değiştirmenin anlamı yoktur; erken dönüş korur. Uç karelerde taşmayı önlemek için indeks artırıldıktan sonra sınırla ve yönü ters çevirmek okunaklıdır — aşağıdaki yardımcı bunu yapar.

İleri–geri klip bittiğinde «duraklat» mı yoksa başa mı sarılır ürün kararıdır; aşağıdaki örnek sonsuz ping-pong varsayar.

function createPingPongClip(frameCount, secondsPerFrame) {
  return {
    frameCount,
    secondsPerFrame,
    acc: 0,
    index: 0,
    direction: 1,
  };
}

function stepPingPongClip(c, dt) {
  if (c.frameCount <= 1 || c.secondsPerFrame <= 0) return;
  c.acc += dt;
  while (c.acc >= c.secondsPerFrame) {
    c.acc -= c.secondsPerFrame;
    c.index += c.direction;
    if (c.index >= c.frameCount) {
      c.index = c.frameCount - 1;
      c.direction = -1;
    } else if (c.index < 0) {
      c.index = 0;
      c.direction = 1;
    }
  }
}

Levha ile birleştirme: indeksten dilime

Animasyon katmanı yalnız «şu an kaçıncı kare» bilgisini üretir; piksel kutusu sx, sy, sw, sh için gridCellRect veya meta tablo kullanılır. Ayrım net olunca aynı klibi farklı levhalara ( düşük / yüksek çözünürlük ) bağlamak kolaylaşır — indeks sözleşmesi aynı kalır.

Başlangıç ofseti ( levhanın üçüncü satırı yürüyüş ) sabit bir baseIndex ile modele eklenir: gridIndex = baseIndex + clip.index — böylece veri odaklı tasarlanmış atlaslar tek satırda kalabilir.

Çizim çağrısı her karede çalışır; değişen tek şey çoğu zaman clip.index’tir. Çift step çağrısı ( update ve render’da iki kez ) kareleri iki kat hızlı oynatır — güncellemeyi tek fazda yapın.

Durum makinesi ve klip seçimi

«Yürü», «zıpla», «vur» gibi üst durumlar her biri kendi FrameClip örneğini taşır; geçişte ya mevcut klibi sıfırlarsınız ya da kaldığı yerden devam ( kesintisiz geçiş nadir, bilinçli seçim gerektirir ). Animasyon değişince indeks ve biriktirici sıfırlanmazsa ilk kare atlanmış veya yarım süreyle başlamış olur.

Bu mantıksal geçişler oyun durumu ile uyumludur; durum makinesi hangi klibin seçileceğine karar verir, bu sayfa klibin içinde nasıl akacağını verir — çakışmayı önlemek için sınır burada çizilir.

Aynı varlıkta üst gövde ve alt bacak farklı klipler oynatabilir ( sentetik karakter ); senkron için ortak zaman çizgisi veya paylaşılan acc kullanılır. Konu ileri seviye rig; bu sayfa tek klibin zamanlamasına odaklanır.

Duraklatma ve sekme görünürlüğü

Duraklatıldığında stepFrameClip çağrılmazsa animasyon donar; yalnızca çizim devam ederse son kare yakılı kalır — oyun durumu politikasıyla uyumlayın. Arka planda büyük dt tek seferde birikmesini istemiyorsanız duraklatma bayrağı ile biriktiriciyi sıfırlayın veya tavanlı adım kullanın.

Sekme görünürlüğü API’si ile arka planda simülasyonu durdurmak, kullanıcı deneyimi için yaygındır; animasyon güncellemesi de durmalıdır — aksi halde sekmeye geri dönüşte klibin ortasına sıçranılmış gibi görünür.

«Boss intro» gibi kesme sahnesi animasyonlarında tek seferlik klip bitince olay üretmek ( geçiş tetikleyici ) yaygındır; finished bayrağını bir karede okuyup kuyruğa atın.

Anti-kalıplar: kare hızı ve ekran FPS’i karışması

Bu dört örnek en sık «animasyon giderek kayıyor», «60’ta iyi 120’de uçuyor» veya «duraklatınca iki kare birden sıçradı» şikâyetine yol açar; ortak payda ekran kare sayısını doğrudan sprite kare oranına bağlamaktır. Çözüm bu sayfada zaten tarif edilen saniye tabanlı birikim, tek step çağrısı ve klip başına secondsPerFrame sabitidir — ek fizik motoru gerekmez.

İndeksi her çizimde ++ ile artırmak: Çizim ne kadar sık yapılırsa animasyon o kadar hızlı görünür; düşük güç modunda veya VSync farkında oynanış hızı «titriyor» gibi algılanır. Zaman ölçeğini saniye ile tanımlayıp birikimci dt ile ilerletmek, hem delta time disiplinine uyar hem cihazdan cihaza daha tutarlı sonuç verir.

step’i hem güncellemede hem çizimde çağırmak: Aynı karede iki kez ilerleme, klip süresini yarıya indirir. Çizim ( render ) safı salt okunur kabul edin; ilerleme yalnızca simülasyon / güncelleme aşamasında çalışsın — Update vs render ayrımıyla uyumludur.

Durum değişiminde biriktirici sıfırlanmıyor: Önceki klibin acc birikimi, yeni klibin ilk karesini «hemen atla» veya yarım süreyle oynatır; indeks de ara değerde kalabilir. Klip veya üst durum değişirken geçiş anında acc = 0 ve çoğu zaman index = 0 ( veya giriş karesine sabit değer ) atamak, hareketin her seferinde aynı noktadan başlamasını sağlar; istisna ( kesintisiz süreklilik ) bilinçli dokümante edilmelidir.

secondsPerFrame olmadan ham indeks döngüsü: «Her requestAnimationFrame bir sprite karesi» modeli, üst sınırı olan bir animasyonu kilitler ve tasarımcının istediği TPS ile örtüşmeyebilir. Süreyi kodda tek bir sayı ( veya adlandırılmış sabit ) olarak tutun; böylece levha tarafında indeks taşması ve süre ayarı birbirinden ayrışır — taşan kaynak indeks ile karışan hataları ayıklamak kolaylaşır.

Bu sayfanın sınırı

İskelet animasyonu ( skeletal ), kemik ağırlıkları ve doku deformasyonu burada işlenmez. Odak: Canvas 2D sprite kare dizilerinin zamanla seçilmesi ve oynatma modlarıdır.

  • Animasyon ilerlemesi saniye ve dt ile ölçekli mi?
  • step yalnızca bir fazda mı çağrılıyor?
  • Klip / durum değişiminde indeks ve biriktirici sıfırlanıyor mu?
  • Levha indeksi üst sınırı taşmıyor mu?
  • Duraklatmada veya sekmede birikmiş dt sürprizi var mı?