Lua の型付き座標

ゲームでは、位置の算術演算を安全で自己説明的にするため、型付き座標システムを使用します。 すべての座標には 原点 と スケール という2つのメタデータがあります。互換性のない座標(例: オーバーマップ位置とマップタイル位置)を混ぜると、気づきにくい誤った結果ではなくランタイムエラーになります。

これは Lua でも同じです。あらゆる場所で型付き座標を優先してください。生の Tripoint と Point 型は純粋なオフセット演算にだけ使用します。つまり、無次元の2Dまたは3Dベクトル(方向や固定変位など)を計算し、その結果を型付き座標にすぐ加算または減算する場合です。


原点

座標の 原点 は、その座標を測定した基準フレームを表します。

原点文字列 意味
"rel" 無次元のオフセット。同じスケールのあらゆる座標に加算できます。
"abs" 絶対的なゲーム世界の位置。全体で安定した唯一の原点です。
"bub" 現在のリアリティバブル(ロード済みマップ領域)の角を基準にした相対位置。
"mnt" 回転を含むローカル車両(マウント)空間。veh スケールで使用します。
"sm" 特定のサブマップの角を基準にした相対位置。
"omt" 特定のオーバーマップ地形タイルの角を基準にした相対位置。
"mmr" メモリーマップ領域の角を基準にした相対位置。
"seg" セグメントの角を基準にした相対位置。
"om" 特定のオーバーマップの角を基準にした相対位置。

"rel" 原点は特殊で、型付きの変位値として機能します。一致する2つの絶対座標を減算すると "rel" の結果になり、"rel" 座標を非相対座標に加算すると、同じ非相対原点が返ります。


スケール

座標の スケール は、各ステップの単位サイズを表します。

スケール文字列 略称 サイズ
"map_square" ms ゲームタイル1個
"vehicle" veh 車両ローカルタイル
"submap" sm 12 × 12マップタイル
"overmap_terrain" omt サブマップ2個(24 × 24マップタイル)
"mem_map_region" mmr MM_REG_SIZE 個のサブマップ
"segment" seg SEG_SIZE 個のオーバーマップ地形タイル
"overmap" om OMAPX 個のオーバーマップ地形タイル

省略形(ms、sm、omt など)は、すべてのファクトリー関数名と、射影関数が受け取る文字列引数で使用されます。


型名

型付き座標の Lua 型名は、原点とスケールを PascalCase で組み合わせて作られます:

Tripoint<Origin><Scale>   →   TripointAbsMs, TripointBubSm, TripointRelOmt, …
Point<Origin><Scale>      →   PointAbsMs, PointRelSm, PointBubMs, …

有効な原点とスケールの組み合わせは31個あります。サポートされていない組み合わせ(例: TripointAbsVeh)を作ろうとするとランタイムエラーになります。


型付き座標の作成

ファクトリー関数(関数型スタイル)

coords ライブラリーは、サポートされているすべての組み合わせについて coords.tripoint_<origin>_<scale>(x, y, z) と coords.point_<origin>_<scale>(x, y) を提供します:

local player_pos  = gapi.get_avatar():get_pos_ms()   -- TripointBubMs from the API
local spawn_point = coords.tripoint_abs_ms(100, 200, 0)
local delta       = coords.tripoint_rel_ms(5, 0, 0)   -- typed offset
local omt_pos     = coords.tripoint_abs_omt(12, 8, 0)

原点とスケールを文字列で受け取る汎用コンストラクターもあります:

local p = coords.tripoint("abs", "ms", 100, 200, 0)
local q = coords.point("rel", "sm", 3, 3)

名前付きコンストラクター(OOP スタイル)

すべての型付き座標には、グローバルスコープに名前付きコンストラクターテーブルもあります:

local p = TripointAbsMs.new(100, 200, 0)      -- from x, y, z
local q = TripointAbsMs.new(raw_tripoint)      -- from raw Tripoint
local r = TripointAbsMs.new(point_coord, z)    -- from matching PointAbsMs + z
local s = TripointAbsMs.new()                  -- zero

どちらのスタイルでも同じオブジェクトが生成されます。一般にはファクトリースタイルの方が簡潔です。


座標成分の読み取り

local p = coords.tripoint_abs_ms(10, 20, 1)

print(p:x(), p:y(), p:z())   -- 10  20  1
print(p:origin())             -- "abs"
print(p:scale())              -- "ms"
print(p:type())               -- "TripointAbsMs"

local xy = p:xy()             -- PointAbsMs(10, 20); drops z, preserves origin/scale
local raw = p:raw()           -- raw Tripoint(10, 20, 1); strips all tags

成分は set_x、set_y、set_z で変更できます。


算術演算

加算

型付き座標には、次のものを加算できます:

  • 生の Point または生の Tripoint。結果は元の原点とスケールを維持します。
  • 同じスケール の "rel" 型付き座標。同じ結果型になります。
  • 別の "rel" 型付き座標に加えるオペランド。非相対の原点が優先されます。
local pos     = coords.tripoint_abs_ms(10, 20, 0)
local offset  = coords.tripoint_rel_ms(3, 0, 0)  -- typed relative offset

local new_pos = pos + offset          -- TripointAbsMs(13, 20, 0)
local also    = pos + Tripoint.new(0, 5, 0)  -- TripointAbsMs(10, 25, 0)  (raw as vector)

減算

"rel" 座標または生の値の減算は、加算の逆のように動作します。同じ原点とスケールを持つ一致した非相対座標を2つ減算すると、"rel" の結果になります:

local a   = coords.tripoint_abs_ms(15, 20, 0)
local b   = coords.tripoint_abs_ms(10, 20, 0)
local rel = a - b                              -- TripointRelMs(5, 0, 0)

乗算

整数スカラーを乗算できるのは "rel" 座標だけです:

local step  = coords.tripoint_rel_ms(1, 0, 0)
local five  = step * 5                          -- TripointRelMs(5, 0, 0)

等値と順序

2つの型付き座標は、原点、スケール、生の値がすべて一致するときだけ等しくなります。 < 演算子は (origin, scale, raw) を辞書順に並べるため、型付き座標をテーブルキーやソート済みコンテナで安全に使用できます。


射影

射影は、原点を維持したまま あるスケールから別のスケールへ座標を変換します。 ゲームのスケール階層は次のとおりです:

ms  <  sm  <  omt  <  mmr  <  seg  <  om

より粗いスケールへの射影では、負の無限大方向に丸められます(床除算)。

local abs_ms  = coords.tripoint_abs_ms(25, 26, 2)
local abs_omt = abs_ms:to_omt()   -- TripointAbsOmt(1, 1, 2)
local abs_sm  = abs_ms:to_sm()    -- TripointAbsSm(2, 2, 2)

すべての対象スケールには便利な短縮メソッドがあります: :to_ms()、:to_sm()、:to_omt()、 :to_mmr()、:to_seg()、:to_om()。汎用的な :to(scale_string) 形式も使用できます。

project_remain: 商と余りに分割する

細かい座標がどの粗いタイルに含まれるかと、そのタイル内の位置の両方を知る必要がある場合は、 project_remain を使用します。2つの値を返します:

local abs_ms = coords.tripoint_abs_ms(25, 26, 2)
local quotient, remainder = abs_ms:project_remain_omt()
-- quotient  → TripointAbsOmt(1, 1, 2):   which overmap terrain tile
-- remainder → PointOmtMs(1, 2):          offset within that tile (fine scale, omt origin)

短縮メソッド: :project_remain_sm()、:project_remain_omt()、:project_remain_mmr()、 :project_remain_seg()、:project_remain_om()。汎用形式は :project_remain("omt") です。

coords ライブラリーには、自由関数としても次のものがあります: coords.project_remain(coord, scale_string)、coords.project_remain_omt(coord) など。

project_combine: 商と余りから再構成する

project_combine は project_remain の逆です。粗い座標と細かいオフセットを受け取り、細かいスケールの絶対座標を作ります:

local quotient, remainder = abs_ms:project_remain_omt()
local restored = coords.project_combine(quotient, remainder)
-- restored → TripointAbsMs(25, 26, 2)

インスタンスメソッド quotient:project_combine(remainder) としても使用できます。


距離関数

2つの型付き座標間の距離を計算するには、原点とスケールが同じである必要があります:

local a = coords.tripoint_abs_ms(10, 10, 0)
local b = coords.tripoint_abs_ms(13, 14, 0)

local rl  = coords.rl_dist(a, b)      -- rectilinear (Manhattan / Chebyshev) distance
local trig = coords.trig_dist(a, b)   -- Euclidean distance
local sq  = coords.square_dist(a, b)  -- square (Chebyshev) distance

インスタンスメソッドもあります: a:rl_dist(b)、a:trig_dist(b)、a:square_dist(b)。


タイル列挙ヘルパー

coords ライブラリーには、標準タイル領域を覆う型付きポイント座標の配列を返すユーティリティがあります。領域の反復に便利です:

local sm_tiles  = coords.submap_tiles()           -- all PointSmMs in one submap
local bub_tiles = coords.tinymap_tiles()          -- all PointBubMs in the tinymap
local omt_tiles = coords.overmap_terrain_tiles()  -- all PointOmtMs in one overmap terrain tile
local om_tiles  = coords.overmap_tiles()          -- all PointOmMs in one overmap

生の Point と Tripoint を使う場合

Point と Tripoint はタグのない2D/3D整数ベクトルです。次の場合にだけ使用してください:

  • 固有のゲーム世界上の意味を持たない純粋なオフセットを計算するとき(方向定数、隣接方向の変位、回転結果など)。
  • 値を保存せず、型付き座標にすぐ加算または減算するとき。
-- Acceptable: raw Tripoint as a throwaway displacement vector
local neighbour = player_pos + Tripoint.new(1, 0, 0)

-- Preferred: named typed offset when the displacement has a scale context
local step = coords.tripoint_rel_ms(1, 0, 0)
local next  = player_pos + step

型付きの相当物がある場合、位置を生の Tripoint や Point として保存しないでください。 型付き形式なら演算時点でスケールの不一致によるバグを検出できますが、生の形式では誤ったマップ座標が気づかれずに生成されます。


有効な原点とスケールの組み合わせ

すべての原点がすべてのスケールで意味を持つわけではありません。サポートされている組み合わせは次のとおりです:

原点 有効なスケール
rel すべてのスケール
abs ms、sm、omt、mmr、seg、om
bub ms、sm
mnt veh のみ
sm ms のみ
omt ms、sm
mmr ms、sm、omt
seg ms、sm、omt、mmr
om ms、sm、omt、mmr、seg

サポートされていない組み合わせを作成すると、無効な型名を示すメッセージとともにランタイムエラーになります。


クイックリファレンス: coords ライブラリー API

関数 説明
coords.tripoint(origin, scale, x, y, z) 汎用型付き tripoint コンストラクター
coords.point(origin, scale, x, y) 汎用型付き point コンストラクター
coords.tripoint_<o>_<s>(x, y, z) 型付き tripoint ファクトリー(例: coords.tripoint_abs_ms)
coords.point_<o>_<s>(x, y) 型付き point ファクトリー
coords.project_remain(coord, scale) 座標を商と余りに分割
coords.project_remain_sm/omt/mmr/seg/om(coord) project_remain の短縮版
coords.project_combine(coarse, fine) 分割した組から細かい座標を再構成
coords.rl_dist(a, b) 直交(マンハッタン/チェビシェフ)距離
coords.trig_dist(a, b) ユークリッド距離
coords.square_dist(a, b) 正方形(チェビシェフ)距離
coords.submap_tiles() 1つのサブマップ内にある全 PointSmMs オフセットの配列
coords.tinymap_tiles() ティニーマップ内にある全 PointBubMs オフセットの配列
coords.overmap_terrain_tiles() 1つの OMT 内にある全 PointOmtMs オフセットの配列
coords.overmap_tiles() 1つのオーバーマップ内にある全 PointOmMs オフセットの配列