⚡
「.」直アクセスと 「:WaitForChild()」 を正しく使い分ける
結論だけ先に
- 存在が保証されているサービス/オブジェクト →
.で即時アクセス- レプリケーション遅延や動的生成があり得る → 初期化フェーズで
:WaitForChild()してキャッシュ- “パフォーマンス差” は CPU 観点では誤差。怖いのは ブロック時間 と ロジック停滞
1. そもそも何が違う?
parent.child(直アクセス) |
parent:WaitForChild("child") |
|
|---|---|---|
| 存在しないと… |
nil → 後続でエラー |
見つかるまで スレッド停止 |
| 典型ユース |
game:GetService("Players")script.Parent.PartA
|
RemoteEvent, Character 部品 |
| パフォーマンス | ハッシュ検索 1 回で終了 |
FindFirstChild()→条件分岐 |
| リスク | ロード順バグで即クラッシュ | 待ちすぎてフリーズ・デッドロック |
2. 実測してみた(100 万回アクセス)
local parent = workspace
local t0 = os.clock()
for i = 1, 1e6 do
local _ = parent.Terrain -- 存在する前提
end
print("dot:", os.clock() - t0)
t0 = os.clock()
for i = 1, 1e6 do
local _ = parent:WaitForChild("Terrain", 0)
end
print("wait:", os.clock() - t0)
| 手法 | 所要時間(参考値) |
|---|---|
. |
≈ 20 ms |
:WaitForChild(..., 0) |
≈ 40 ms |
差は 20 ms/100 万回。フレーム単位(16 ms ≒ 60 FPS)で数十回呼ぶ程度なら無視できる。
3. ブロッキングの落とし穴
-- 悪い例:ゲームループ内で毎フレーム待つ
RunService.Heartbeat:Connect(function()
local obj = ReplicatedStorage:WaitForChild("Bullet") -- 生成が遅れるとフリーズ
-- …
end)
- 生成が遅れる/そもそも来ない → コールバックが 永久ブロック
- デバッグも難航。「たまに固まる」症状の元凶になりがち
4. ベストプラクティス 3 箇条
-
初期化時に 1 回だけ待ち、即キャッシュ
local RS = game:GetService("ReplicatedStorage") local spawnBulletEvent = RS:WaitForChild("SpawnBullet") -
キャッシュ変数を後続で使い回す
- 毎回
WaitForChild()しない
- 毎回
-
タイムアウトを付けて安全装置
local gun = char:WaitForChild("Gun", 5) if not gun then warn("Gun が見つからずスキップ") end
5. 使い分けチャート
+┬ それは最初から必ず存在する?
│ ├─ YES → dot(.) で即アクセス
│ └─ NO
│ ┌ そのオブジェクトが来るまで待っても良い場所?
│ │ ├─ YES → 初期化で WaitForChild() → キャッシュ
│ │ └─ NO → FindFirstChild() で存在チェック+イベント駆動
└──────────────────────────
6. よくある Q&A
| Q | A |
|---|---|
:FindFirstChild() と何が違う? |
非ブロッキングで即 nil or 子を返すだけ。ループでポーリングすると重くなりやすい。 |
| タイムアウト 0 を渡すテクニックってアリ? | 「存在確認+警告」をワンライナーで書けるが、毎フレーム呼ぶのは不要。 |
| サーバー側でも WaitForChild 必須? | マップ生成を後から行う場合など、動的生成なら必要。固定配置なら不要。 |
まとめ
- CPU コスト差は誤差、だが ブロッキング時間がゲーム体験を壊す
- 初期化でキャッシュ → 以降は直アクセス が鉄則
-
ChildAdded/CharacterAddedなど イベント駆動で “待たない設計” を心掛ける
これで “`. と WaitForChild どっち問題” とはサヨナラ。安全かつ滑らかなゲーム体験を届けよう!
Discussion