holodepth

Oyun geliştirme · Web oyun döngüsü

Sahne & Entity Hafif Modeli: Mesh'ten Oyun Nesnesine

Three Mesh görsel temsilidir; oyun mantığı için hafif entity katmanı gerekir. Bu sayfa mesh ile oyun nesnesini eşler, update hook'larını döngüye bağlar: tam ECS veya archetype derinliği değil, Hexovian sınırında kalır.

Holodepth bu yaprağı «sahne grafiği nedir» 101 olarak yazmaz. Döngü ve girdi Loop & zaman bütçesi / Girdi & durum makinesi yapraklarındadır. Animasyon döngüsü saati Three.js · Animasyon döngüsü yaprağındadır. Performans dilimi Platform · FPS yaprağındadır. Tam ECS, component storage, netcode entity senkronu Hexovian sınırındadır.

Sayfayı bitirdiğinizde şu cümleyi kurabilmelisiniz: «Entity mantık taşır; mesh görsel taşır; map ile bağlanır; update hook döngüde çalışır; spawn/dispose yazılır; lite component yeter; fizik köprüsü bir sonraki izlekte mesh pozisyonunu dışarıdan yazar.»

Bu ayrımı erken kurmanın somut faydası, fizik köprüsüne geçişte ortaya çıkar: entity zaten mesh'ten bağımsız bir kimlik taşıyorsa, fizik gövdesi bu kimliğe yeni bir alan olarak eklenir — mevcut kod yeniden yazılmaz, genişletilir.

Bu sayfanın sınırı · komşu konularla ayrım

Bu sayfa web/Three oyununda hafif entity modeli anlatır:

  • Mesh ↔ game object lite eşlemesi
  • Update / fixedUpdate hook önizlemesi
  • Spawn, pool, dispose
  • İzlek handoff: fizik köprüsü

Bilinçli olarak dışarıda bırakılan konular:

Kısa ayrım: Bu sayfa «mantık nesnesi nasıl yaşar?» · Hexovian ECS «depolama ve sorgu» · Fizik köprüsü «dış motor pozisyon yazar».

Mesh ≠ oyun nesnesi

THREE.Mesh draw için vardır; can, skor, hız, AI durumu mesh üzerinde taşınmamalı. Holodepth: düz JavaScript nesnesi veya küçük sınıf «entity»; mesh yalnız view referansıdır. Bu ayrım fizik köprüsünde kritik olur: motor body ile mesh ayrı yaşar, köprü senkronlar.

Pratik test: bir entity'nin mesh'i sahneden kaldırılsa (ör. görünürlük optimizasyonu) oyun mantığı hâlâ doğru çalışmalı — can azalmalı, çarpışma sayılmalı. Eğer mantık mesh'in var olmasına bağlıysa, ayrım henüz tam kurulmamıştır.

Görsel sahne grafı ile oyun state'i aynı çizelge değildir.

Lite entity map

Holodepth minimal model: id, tags, transform (mantık uzayı), view (Object3D), components (düz kayıt). Map veya dizi; ürün ölçeğine göre. Tam ECS'teki SOA layout ve query iterator burada yok; yüzlerce nesne için yeter, binlerce benzer için instance veya Hexovian yönü düşünülür.

id alanı hem debug loglarında hem çarpışma hook'larında ortak anahtar olarak kullanılır; mesh referansını anahtar yapmak (ör. Map<Mesh, Entity>) dispose sırasında sızıntıya açıktır — string veya sayısal id daha güvenli bir sözleşmedir.

Update hook'ları

Döngü onTick(dt) içinde entity listesi gezilir: update(dt, input, fsm) mantık; ardından syncToView() mesh pozisyonuna yazar. Holodepth sırası: girdi → FSM → entity update → view sync → render. Fizik geldiğinde fixedUpdate ayrı saatte çalışabilir; bu yaprakta yalnızca hook imzası bırakılır.

update() içinde doğrudan THREE.Object3D API'sine dokunmak (ör. mesh.position.set) ayrımı bulanıklaştırır; mantık kendi transform alanını yazsın, syncToView() tek sorumlu olarak bu değeri mesh'e aktarsın.

Sahne grafı köprüsü

Entity spawn olduğunda mesh sahneye eklenir; dispose'da geometry/material ve sahneden çıkarma yapılır. Parent group ile katmanlama (dünya, aktör, UI 3D) ürün kararıdır. Holodepth: userData.entityId ile ters arama nadiren; tercih tek yönlü map.

Katmanlama aynı zamanda raycast performansını etkiler: aktör grubunu ayrı tutmak, picking'in dekor mesh'lerini taramasını önler. Bu, Three raycast omurgasındaki filtre pratiğiyle aynı motivasyonu taşır.

Yaşam döngüsü: spawn, pool, ölüm

Mermi ve parçacık gibi sık doğan nesnelerde object pool düşünülür; her spawn'da new Mesh GC baskısı yapar. Ölüm: listeden çıkar, view dispose, referans null. Holodepth: «ölü» entity'yi gizlemek yetmez; kaynak serbest bırakılmalıdır.

  • Spawn: entity + view oluştur, sahneye ekle
  • Pool get: sıfırla, yeniden kullan
  • Despawn: dispose, map'ten sil

Pool'dan çekilen bir entity'nin önceki tur kalıntıları (eski hız, eski hedef referansı) sıfırlanmadan yeniden kullanılması sinsi bir hatadır — «pool get» adımı yalnızca nesneyi geri vermemeli, tüm mutable alanları varsayılana döndürmelidir.

Component lite (ECS değil)

«Health», «Velocity», «PlayerControl» düz obje alanları olarak yaşar; sistemler ayrı fonksiyonlar (movementSystem, healthSystem). Holodepth bu kalıbı «ECS tadında» ama depolama SOA olmadan sunar. Archetype, chunk iterator ve parallel query Hexovian'da kalır.

Sistem fonksiyonlarını entity listesini elle gezen küçük döngüler olarak yazın; «genel amaçlı query motoru» erken bir soyutlamadır — birkaç düzine entity için düz for döngüsü, sorgu planlayıcısından daha hızlı yazılır ve okunur.

Güvenli entity katmanı: simüle kod

Örnekler öğretim amaçlıdır; gerçek oyunda tip ve sınır doğrulaması eklenir. Aşağıdaki iskelet entity factory'sini ve mesh'e tek yönlü senkron adımını gösterir.

Simüle · entity factory + view sync.

import * as THREE from 'three'

let nextId = 1
const MAX_ENTITIES = 256 // Ürün tavanı · kullanıcı girdisiyle şişirme

export function createEntityRegistry(scene) {
  const list = []

  function spawnBox({ x = 0, y = 0, z = 0, tags = [] } = {}) {
    if (list.length >= MAX_ENTITIES) return null

    const geo = new THREE.BoxGeometry(0.5, 0.5, 0.5)
    const mat = new THREE.MeshStandardMaterial({ color: '#6ec8ff' })
    const mesh = new THREE.Mesh(geo, mat)
    mesh.position.set(x, y, z)
    scene.add(mesh)

    const entity = {
      id: nextId++,
      tags,
      transform: { x, y, z, yaw: 0 },
      velocity: { x: 0, y: 0, z: 0 },
      view: mesh,
      alive: true,
      syncToView() {
        if (!this.view) return
        this.view.position.set(this.transform.x, this.transform.y, this.transform.z)
        this.view.rotation.y = this.transform.yaw
      },
      dispose() {
        if (this.view) {
          scene.remove(this.view)
          this.view.geometry?.dispose()
          this.view.material?.dispose()
          this.view = null
        }
        this.alive = false
      },
    }
    list.push(entity)
    return entity
  }

  function updateAll(dt, input) {
    for (const e of list) {
      if (!e.alive) continue
      // Örnek: basit WASD · gerçek oyunda component/system ayrılır
      const speed = 4
      if (input.hold.has('left')) e.transform.x -= speed * dt
      if (input.hold.has('right')) e.transform.x += speed * dt
      if (input.hold.has('up')) e.transform.z -= speed * dt
      if (input.hold.has('down')) e.transform.z += speed * dt
      e.syncToView()
    }
  }

  function despawnDead() {
    for (let i = list.length - 1; i >= 0; i--) {
      if (!list[i].alive) list.splice(i, 1)
    }
  }

  return { list, spawnBox, updateAll, despawnDead }
}

Simüle · döngü entegrasyonu.

import { pollInput } from './input-surface.js'
import { createFsm } from './game-fsm.js'
import { createEntityRegistry } from './entity-lite.js'

export function wireGameTick({ scene, onTick }) {
  const fsm = createFsm('menu')
  const world = createEntityRegistry(scene)
  const player = world.spawnBox({ tags: ['player'] })

  function gameTick(dt) {
    const input = pollInput()

    for (const edge of input.edges) {
      if (edge === 'pause') fsm.dispatch(fsm.state === 'playing' ? 'pause' : 'resume')
      if (edge === 'start' && fsm.state === 'menu') fsm.dispatch('start')
    }

    if (fsm.state === 'playing' && player) {
      world.updateAll(dt, input)
    }

    world.despawnDead()
    if (typeof onTick === 'function') onTick(dt, { fsm, world })
  }

  return { gameTick, fsm, world, player }
}

Yanlış entity kalıpları

Bu kalıpların bir kısmı «çalışıyor gibi görünür» çünkü küçük ölçekte hata gizlenir; entity sayısı veya oturum süresi arttığında bellek ve tutarlılık sorunları belirginleşir.

  • Tüm mantığı mesh.userData içinde taşımak
  • Her kare yeni mesh spawn (GC)
  • Dispose atlamak
  • Sınırsız entity sayısı (kullanıcı girdisi)
  • Pool'dan çekilen entity'yi sıfırlamadan yeniden kullanmak
  • ECS beklentisiyle lite modeli suçlamak
  • Fizik body ile mesh'i tek nesne sanmak

Üç entity profili

Üç profil de aynı mesh/entity ayrımını paylaşır; farklılaşan şey entity sayısının büyüklük mertebesi ve pool politikasıdır.

  • 01 · Entity

    Tek kahraman

    Model
    Bir player entity
    Sync
    Her tik syncToView
    Risk
    Mantık mesh'te
  • 02 · Entity

    Düşman listesi

    Model
    Dizi + pool
    Tavan
    MAX_ENTITIES
    Risk
    Çizgi içi O(n) her tik
  • 03 · Entity

    Fizik hazırlık

    Model
    Body id alanı rezerv
    Hook
    fixedUpdate slot
    Risk
    İki yönlü sync karmaşası

Hexovian sınırı: ECS nerede biter?

Holodepth lite model web vitrini ve küçük arcade için yeter. Yüzlerce heterojen component, paralel sistem, ağ doğrulamalı state için Hexovian ECS ve motor izleğine geçilir. Burada öğrenilen «mantık / görsel ayrımı» orada da geçerlidir; yalnızca depolama ve çizelgeleme derinleşir.

Geçiş sinyali genelde nettir: entity sayısı birkaç yüzü aştığında veya her sistemin her entity'yi düz döngüyle taraması profilerde görünür bir maliyete dönüştüğünde, SOA depolama ve query cache konuşulmaya başlar. Bu sayfa o eşiğe gelene kadar yeterli bir üretim yüzeyidir.

Holodepth perspektifleri

Tek yönlü sync varsayılanı

Mantık → view yazılır; view'dan mantık okumak istisnadır (picking hariç ayrı kanal). Bu yön tersine çevrildiğinde debug edilmesi en zor sınıf hatalar ortaya çıkar.

Tavan yazın

MAX_ENTITIES ürün sabiti; profil ile doğrulanır. Sabiti kod içine gömmek yerine bir yapılandırma değeri olarak tutmak, farklı cihaz sınıfları için farklı tavan denemesine izin verir.

Pool, erken optimizasyon değil

Object pool yalnızca sık spawn/despawn olan nesne sınıfları için gereklidir; sahnede sabit sayıda uzun ömürlü entity varsa pool eklemek gereksiz karmaşıklıktır.

Teşhis maddeleri

  1. Mesh'te oyun state'i kaldı mı?
  2. syncToView tek yerde mi?
  3. Spawn/dispose simetrik mi?
  4. Pool gereksiz new'i kesti mi?
  5. Döngü hook sırası korunuyor mu?
  6. Fizik body alanı ayrıldı mı?
  7. glTF veya tam ECS sızmış mı?

Holodepth içgörüsü

Hafif entity, «oyun nesnesi» dilini Three sahnesinden ayırır; fizik köprüsü ve netcode için zemin hazırlar. Web oyun döngüsü izleği burada mantık katmanını mühürler.

İzlek handoff: Three + fizik köprüsü

Web oyun döngüsü izleği (döngü → girdi → entity) bu yaprakla kapanır. Sonraki izlek Rapier / Cannon yüzeyi ile devam eder: dış fizik motoru body üretir, pozisyon entity view'a köprülenir; sabit adım ve çarpışma tepkisi orada derinleşir. Holodepth omurgası Three animasyon ve Platform FPS ile ölçümde kalır.