Tilemapで地形を配置する

Gridや行列で定義した地形を、SpriteSheetの画像で描画します。Tilemapは世界全体にも、一部分の床や装飾にも使えます。地面・壁・飾りは独立したTilemapを重ねます。

最小構成

import { Game, SpriteSheet, Tilemap } from "@simpludify/web";
const game = new Game();
const sheet = SpriteSheet.grid("terrain.png", {
  frameWidth: 32,
  frameHeight: 32,
  names: { grass: 0, wall: 1 },
});
const terrain = new Tilemap({
  sheet,
  tiles: [
    ["grass", 0, 0],
    [0, "wall", null],
  ],
  tileWidth: 32,
  tileHeight: 24,
}).place(game.engine.scene);
terrain.set(2, 1, "grass");
await game.run();

画像には指定フレームが必要です。place(scene)だけで配置し、Scene破棄時には描画と画像参照を解放します。毎フレームの手動更新やTexture管理は不要です。nullは空セル、0は最初の画像フレームです。名前はSpriteSheetの定義から補完されます。任意矩形のSpriteSheetも使用できます。

データの操作

API 意味
get(x,y)・set(x,y,id) 整数タイル座標による取得・変更。範囲外はGRID_BOUNDS。
fill(id) 全セルを同じID/nullへ変更。
inBounds(x,y) 有効な整数座標かを確認。
copyTiles() 独立したGridを取得。次のSceneでも再利用可能。
width・height セル数。サイズは構築後に変わらない。
x・y ワールド上の左上位置。負の座標も指定可能。
zIndex・visible・enabled 表示順、可視性、配置の有効状態。
dispose() Sceneから取り除いて破棄。

行列はtiles[y][x]の向きです。Gridからも構築でき、どちらの入力もコピーします。外部Gridを変更しても描画キャッシュは古くなりません。変更はTilemapのset/fillから行います。タイルの表示寸法は元フレームとは独立し、セルの幅・高さへ画像を引き伸ばします。

空・不揃いの行列はGRID_SIZE、無効な名前やフレームはSpriteSheetのエラーです。画像ロード前には数値グリッドの上限を確定できないため、Webで使用中のIDを検証します。画面外セルも検証対象です。不正な実行中の変更はwhenReadyをrejectし、旧表示を保持します。正常なIDへ戻せば復帰できます。

座標とレイヤー

import { SpriteSheet, Tilemap } from "@simpludify/core";
const map = new Tilemap({
  sheet: SpriteSheet.grid("tiles.png", { frameWidth: 16, frameHeight: 16 }),
  tiles: [
    [0, null],
    [1, 0],
  ],
  tileWidth: 32,
  tileHeight: 24,
  x: -32,
  y: 48,
});
const origin = map.tileToWorld(1, 1);
const center = map.tileToWorld(1, 1, { center: true });
const tile = map.worldToTile(center.x, center.y);
const bounds = map.tileBounds(1, 1);
console.log(origin, tile, bounds);

左上原点、右向きx・下向きyです。tileToWorldはタイル左上、center指定は中心を返します。worldToTileは左/上を含み、右/下を含まない範囲で切り捨て計算し、マップ外ならundefinedです。内部境界は右/下のセルに属します。tileBounds(x,y)はワールド単位の新しい{x,y,width,height}を返し、タイル座標の整数・範囲チェックはtileToWorldと共通です。画面座標は先にGame.pointerToWorldなどでワールド座標へ変換します。

地面・壁・装飾はそれぞれ作ってplaceしてください。zIndexは普通のRenderableと共通で、同値なら配置順です。Actorを地面と装飾の間に描けます。1枚のTilemap内にActorを差し込む順序制御はありません。初期版の配置は平行移動のみです。

所有権と描画

公開TilemapはActorを継承せず、内部で1個のActorと受動Componentを使います。既存Sceneの所有・破棄を再利用しながら、Actor.getとTilemap.getの混同を避けています。セルごとにActor/Componentを生成しません。

描画範囲のタイルだけにPixi Spriteを作ります。静止時のセル走査は0、画面内1セルの変更なら1セル更新です。画面外変更は表示範囲へ入った時に反映します。Cameraでタイル範囲が変わると現在の表示範囲を再走査します。表示中の全Spriteの描画コストは残り、特にCanvas2Dで大量のセルを全表示すると重くなります。実測値と条件を確認してください。

元画像と切り出しTextureは既存の画像共有を使います。空・非表示・無効化したマップも画像参照は保持します。最後の画像参照がなくなると切り出しと元画像を解放します。ロード中の破棄やScene準備失敗も既存のキャンセル契約に従います。

動くサンプル

npm run dev -- --example tilemap

Tile trailは3枚のTilemap、WASD移動、Camera追従、足元の書き換え、座標表示、Scene往復を含みます。石・木はSolidの壁、水は通過できるTriggerです。Toggle wall aheadで生成/破壊、Toggle collisionで見た目を保ったまま衝突を切り替えられます。

衝突を使う場合は、SceneにCollisionWorldを1つ作り、動かすActorにSolid ColliderとCollisionBodyを追加します。Tilemapのcollision.tilesへIDごとの応答を設定し、setCollision(x,y,null)でセルだけ無効、resetCollision(x,y)で継承に戻せます。visibleは描画だけ、collisionEnabledは衝突だけ、enabledは配置全体を止めます。Actor用Contactは変更せず、地形用にはTileContactEnter/Stay/Exitを購読します。詳しい契約と衝突ガイドを参照してください。

無限マップ、エディター、オートタイル、複雑なアニメーションタイルは未実装です。完全な剛体物理やCCD、任意の深い埋まりからの復帰は保証しません。

矩形統合と局所更新

new CollisionWorld(scene, { tileSearch: "rectangles" })で、同じ応答・layer・maskを持つ隣接タイルを矩形の衝突候補へ統合します。横の連続区間を作り、同じ幅の区間が続けば縦へ伸ばします。穴や異なる属性をまたがず、16×16セルの区画内に限定します。必要な区画だけ構築し、1セルの変更ならその区画だけを無効にして、次の衝突判定で再構築します。ID共通設定の変更では該当IDを含むキャッシュ区画、fillでは全キャッシュ区画が対象です。

矩形は候補の絞り込みに使い、接触セルごとの細かな判定・位置補正・TileContactイベントは従来どおりです。小さいActorや密な接触では管理コストが増える場合があるため、既定値はtileSearch: "cells"です。world.tileIndexDiagnosticsでキャッシュ中のcells/regions/chunks、構築回数などを確認できます。契約と性能上の制約を参照してください。

Tile trailは矩形検索を使用し、地形のセル数と矩形数を表示します。Toggle doorで石壁の(14,7)を開閉できます。タイルデータは即時に変わり、衝突は次の判定パス、描画は次の表示更新で反映します。接触イベント内で変更しても、そのパスの走査をやり直しません。Actorの足元に壁を置いた場合は次のパスで既存の補正を適用し、完全に埋まった状態の解消までは保証しません。

三角形・六角形

単色の三角形・六角形レイヤーはTile geometryを使います。四角形画像用Tilemap・Collisionとは独立して利用でき、座標変換・選択・隣接・更新を提供します。