Tips

各作品の開発で実際に踏んだバグの記録と、作品を問わないUnityの落とし穴。出典とタグで絞れます。

竜宮城をまもれ!

01高fpsでキャラが横歩き(ムーンウォーク)する

高fpsでキャラが横歩き(ムーンウォーク)する

「移動量が閾値を超えたら向きを変える」処理で、比較基準の lastPosition を毎フレーム上書きすると高fpsで壊れます。1フレームの移動が閾値に届かず、差分が永遠に溜まらないため。vsyncを切った環境(200fps超)だけキャラが横歩きしました。

直し方: lastPosition は閾値を超えたときだけ更新する=閾値未満の微小移動を蓄積する。

竜宮城をまもれ!

02ポーズ中に演出まで止まる

ポーズ中に演出まで止まる

Time.timeScale = 0 でポーズすると Time.deltaTime が 0 になり、deltaTime で動かしている演出は全部止まります。ポーズメニューのアニメーションまで固まって気づきました。

直し方: ポーズ中も動かしたいUI・演出は Time.unscaledDeltaTime で駆動する。

竜宮城をまもれ!

03被弾フラッシュが白いまま固まる

被弾フラッシュが白いまま固まる

被弾で白フラッシュ→元の色に戻す処理は、連続ヒットで色が戻らなくなることがあります。原因は再入。2発目のコルーチンが「元の色」として白を掴み、最後にそれを復元してしまうため。敵が白いまま固まって気づきました。

直し方: フラグ1枚で二重起動を止める(再入ガード)。フラッシュ中の外部からの色変更にも世代カウンタで備える。

竜宮城をまもれ!

04撃った本人が死ぬと爆弾が不発になる

撃った本人が死ぬと爆弾が不発になる

攻撃した本人が死ぬと、飛んでいる弾や時限爆弾の演出が一緒に消える。演出コルーチンを術者のMonoBehaviour上で回していると、Destroyと同時にコルーチンも止まるためです。

直し方: 常駐の空オブジェクト(ランナー)に演出コルーチンだけ回させる。本人が倒れても爆弾はちゃんと爆発する。

竜宮城をまもれ!

05経路探索で壁を斜めにすり抜ける

経路探索で壁を斜めにすり抜ける

「次に進むセル」を取る関数が、取得と同時にスタックを更新していると壊れます。移動がキャンセルされた時(先客がいた等)に内部状態だけ進んでしまい、次の取得が2マス先や斜めのセルを返す。キャラが壁を斜めにすり抜けて歩いていました。

直し方: 取得は参照(Peek)だけにして、実際に動いた瞬間に更新(Push/Pop)を確定する。

竜宮城をまもれ!

06ドット絵がぼやける

ドット絵がぼやける

テクスチャのFilter Modeは既定がBilinear(バイリニア補間)で、拡大時にピクセル同士がにじみます。16x16のドット絵を拡大表示すると輪郭が溶けたようにぼやけて、ドット絵の「カクッとした」味が消えます。

直し方: ピクセルアートは Filter Mode: Point (no filter) に。Compression も None 推奨。

竜宮城をまもれ!

07コードの初期値を直したのに反映されない

コードの初期値を直したのに反映されない

[SerializeField] のフィールドは、最初にシリアライズされた時の値がシーンやプレハブに保存され、以後はそちらが使われます。コードの初期値を後から直しても実効値は古いまま。連射間隔を直したつもりが、シーンに焼き付いた0.01秒のままで長押し掘削が毎フレーム連射になっていました。

直し方: 値を変えたらコードと Inspector(シーン/プレハブの保存値)の両方を更新する。

竜宮城をまもれ!

08「〜」がフォント未収録で□になる

「〜」がフォント未収録で□になる

波ダッシュ(U+301C)と全角チルダ(U+FF5E)は、見た目がほぼ同じ別の文字です。ビットマップフォントは片方しか収録していないことがあり、IMEやOSによってどちらが入力されるかも変わります。乙姫のセリフに□が出て気づきました。

直し方: 日本語セリフの波線は U+FF5E(全角チルダ)に統一する。

竜宮城をまもれ!

09エディタでは正常、ビルドだけ34倍

エディタでは正常、ビルドだけ34倍

エディタは AssetDatabase、ビルドは Resources と読む経路が違うため、Art側とResources側で PPU(Pixels Per Unit)がズレていてもエディタでは気づけません。瑠璃のドット絵が Art=550 / Resources=16 のままビルドされ、配置した瞬間に34倍で表示されました。

直し方: 同じ画像を2箇所に置くならインポート設定(PPU・Filter Mode)を完全一致させる。ビルド前チェッカーで全数検査すると確実。

竜宮城をまもれ!

10enumの途中挿入で保存データが化ける

enumの途中挿入で保存データが化ける

シーンや ScriptableObject は enum を番号(int)で保存します。途中に値を挿入すると以降が1つズレて、保存済みの「3」が別の名前を指すようになり、回復スキルが範囲攻撃に化けます。既存の値の番号は動かさず、新しい値は末尾に足します。

直し方: 新しい値は必ず末尾に追加する。途中に入れたくなったら明示的な数値割り当てを使う。

竜宮城をまもれ!

11「実質無限」の999秒は16分39秒後に来る

「実質無限」の999秒は16分39秒後に来る

寿命を lifetime = 999f にして「実質無限」のつもりでいると、判定を通る以上いつか発症します。タイトル画面を16分39秒放置すると、背景の光線がキラキラ粒子として再生成されて高速回転し始めました。

直し方: 無限にしたいものは寿命判定・リスポーン対象から明示的に除外する。

竜宮城をまもれ!

12dt=0で画面シェイクが永久に揺れる

dt=0で画面シェイクが永久に揺れる

Time.deltaTime 駆動のシェイクを timeScale=0 の凍結に持ち越すと、経過時間が進まないのにランダムオフセットだけ毎フレーム適用され続け、永久に揺れます。クエイク直後に敗北すると、敗北会話の間ずっと画面が揺れていました。

直し方: dt=0 の間はオフセットを戻して静止し、解凍後に残り時間だけ揺らす。

竜宮城をまもれ!

13敵と重なった瞬間、お互い永久に待つ

敵と重なった瞬間、お互い永久に待つ

「攻撃範囲に敵がいたら移動せず待つ」AIは、待機判定の範囲が攻撃範囲より少しでも広いと詰みます。移動補間中はキャラ同士が同じセルに重なれるのに、待機は距離0を含み、攻撃は隣接4方向のみ。殴れない相手をお互いに待ち続ける永久フリーズになりました。

直し方: 待機範囲は攻撃範囲と完全一致させる。範囲を広げるときは必ず両方セットで。

竜宮城をまもれ!

14Enterで見えないボタンが押される

Enterで見えないボタンが押される

uGUIはクリックしたボタンがEventSystemの「選択」に残り続けます。あとで押したEnterやパッド決定ボタンのUnity標準Submitが、そのボタンを裏で再発火。閉じたオーバーレイの下の見えないボタンでも起きます。強化画面で進化が1段余計に購入されました。

直し方: 独自のキー/パッドナビを組むなら Navigation を無効化し、毎フレーム SetSelectedGameObject(null) で標準ナビを遮断する。

竜宮城をまもれ!

15効いていない設定値は、直した瞬間に爆発する

効いていない設定値は、直した瞬間に爆発する

設定値が未配線で実は無視されている状態のまま数値調整を重ねると、その値は動作実績ゼロの地雷になります。未配線のまま「決めた」ボスの移動間隔0.08秒/マスが、配線バグを修復した瞬間に初めて有効化され、毎秒15マスで走るテレポートボスが誕生しました。

直し方: 配線バグを直したら、その経路に乗る全パラメータを「初めて有効化される値」として再検証する。

竜宮城をまもれ!

16発動判定と効果は同じ関数で判定する

発動判定と効果は同じ関数で判定する

範囲スキルの「撃つか判定」を距離で書き、実効果を扇形で当てると、扇の外の敵に反応して発動し、正面の壁へ撃つ「無言の空撃ち」(MPとクールダウンだけ消費)が生まれます。扇形波スキルの初版は「何も起きない」と報告されました。

直し方: 照準・発動判定・効果適用は同じジオメトリ関数を共有させる。以後追加した砲撃スキルは同じ流儀で空撃ちゼロ。

竜宮城をまもれ!

17「繁殖しない海獣」が無限増殖した

「繁殖しない海獣」が無限増殖した

同じ挙動が専用系と汎用系の2系統で実装されていると、専用系だけ止めても汎用系が生き残ります。繁殖しないはずの特殊種が、巣システムだけ止めて汎用の確率繁殖フラグを立て忘れたせいで、15秒ごとに自己クローンを産んで指数的に増殖。同じ構造の穴を別種族で3回踏みました。

直し方: 機能を無効化するときは、その機能に至る全経路を列挙して全部塞ぐ。

竜宮城をまもれ!

18入力は描画座標、AIは論理セル

入力は描画座標、AIは論理セル

グリッド移動+補間描画のゲームでは「キャラのいるセル」が2つあります(スプライトの描画位置と、移動完了時に更新される論理セル)。クリック判定を論理セルにしたら移動中の海獣が選べなくなり、AIの進入判定が描画座標だったら「進入はブロックするのに攻撃が届かない」空転が起きました。両方向で踏んでいます。

直し方: 入力(クリック選択)は描画位置=見えているもの、AI(攻撃・進入・待機)は論理セル、と規約で使い分ける。

竜宮城をまもれ!

19パーティクル配置はグリッドジッター

パーティクル配置はグリッドジッター

画面全体に泡やキラキラを撒くとき、純ランダム配置は必ず塊と空白ができます。画面をパーティクル数ぶんのセルに分割し、各セル内でランダムにオフセットする「グリッドジッター法」なら、1パスで均一密度と自然なばらつきが両立します。

直し方: cols=√(個数×幅/高さ)でセルを割り、i番目の粒をセル内のランダム位置へ。タイトル画面と勝利演出は全部この方式です。

竜宮城をまもれ!

20位置演出は差分適用で重ねる

位置演出は差分適用で重ねる

攻撃の踏み込みやシェイクなどの位置演出で絶対座標を書くと、移動の補間処理と毎フレーム座標を取り合ってテレポートや位置ズレが起きます。「今回のオフセット−前回足した分」だけをtransformに加算する差分方式なら、移動と複数の演出が自然に合成されます。

直し方: 毎フレーム position += offset - applied、終了時に -= applied。歩きながら揺れても跳ばず、必ず元の位置に戻る。

竜宮城をまもれ!

21static×Time.timeはシーン再読込で巻き戻る

static×Time.timeはシーン再読込で巻き戻る

staticフィールドはシーン再読込を生き残りますが、Time.timeは0に戻ります。「Time.time < 保存時刻+間隔」型のキャッシュ判定は、再読込直後に未来の時刻と比較することになり、古い値を返し続けます。リトライ後、海獣が最大5分間寝返ったままになりました。

直し方: Time.time >= 保存時刻 の巻き戻り検出を必ず併記する(またはシーン再読込でキャッシュをリセットする)。

竜宮城をまもれ!

22タイルの見た目が変わらない? RefreshTile忘れ

タイルの見た目が変わらない? RefreshTile忘れ

TilemapはTileアセットのspriteを差し替えても、tilemap.RefreshTile(cell)を呼ぶまで再描画されません。内部数値は正しいのに見た目だけ古い、という気づきにくいバグになります。土に撒いた魔分の色が、散布3経路まるごと画面に出ていませんでした。

直し方: タイルのデータを変えたら、変更したセルを RefreshTile で再描画する。

竜宮城をまもれ!

23引きズームだとカーソルが遅く感じる

引きズームだとカーソルが遅く感じる

キーボードとパッドのカーソル移動は長押しリピート(1回1マス、間隔0.08秒)です。カメラを引いた全体表示ではマスが小さくなるぶん、同じ間隔でも画面上の移動が遅く感じます。画面上の速度が一定になるよう引き具合に反比例で速くしたところ(最大拡大 orthographicSize 8 → 全体表示 約18 で2.25倍速)、今度は速すぎて狙ったマスで止まれませんでした。GIFの左が反比例の初版、右が平方根スケール+上限1.25倍(全体表示で0.064秒間隔)です。

直し方: 体感の補正は平方根スケール+上限で控えめに。速くするのはリピート間隔だけで、1回の移動は1マスのままにする(複数マス飛ばすと通り道が4連結でなくなり、長押し掘削が途切れます)。

竜宮城をまもれ!

24斜め入力でカーソルを斜めに飛ばすと掘れない

斜め入力でカーソルを斜めに飛ばすと掘れない

掘削の条件は「隣の4マスに掘削済みのマスがあること」です。斜め入力(横と縦を同時に倒す)でカーソルを斜めに1マス飛ばすと、通り道が4連結にならないため角のマスが条件を満たせず、斜めに掘り進めませんでした。横→縦→横…と1軸ずつ交互に分解すれば、斜めに倒しっぱなし+長押しで階段状に掘れます。GIFの左が斜めに飛ぶ版、右が交互に1歩ずつ動く版です。カーソルが指していない隣のマスも掘って橋渡しする案もありましたが、指していないマスが勝手に掘れてしまうので不採用にし、動き方の側で解決しました。

直し方: 斜め入力は1軸ずつ交互に分解して通り道の4連結を保つ。端で選んだ軸が動けないときはもう片方の軸へ切り替える(端沿いで半速にならない)。

竜宮城をまもれ!

25データ削除の「はい」は数秒たつまで押せない

データ削除の「はい」は数秒たつまで押せない

設定画面の「データ削除」は、取り返しのつかない操作なので2段階の確認にしています。1段目の瑠璃の確認では「はい」が2秒、2段目の乙姫の最終確認では「削除する」が3秒、押せない状態で表示されます。押せない間はボタンを暗くし、ラベルに「削除する (3)」のように残り秒数を出して、時間が来ると通常の色に戻ります。設定画面はゲームの時間が止まったタイトル画面で開くため、秒数は時間停止の影響を受けないカウントで数えています。GIFの左は、もし確認がすぐ押せたら(連打でそのまま2段とも通ってしまう)という例、右が本作の動きです。

ポイント: キーボードやパッドの初期カーソルも、カウントダウン中は「いいえ/やめる」側に置くようにしました。決定キーを連打しても削除には進まず、勢いのままでは消せないようにしています。

竜宮城をまもれ!

26初回起動の言語はOSの設定で決める

初回起動の言語はOSの設定で決める

日本語と英語に対応したとき、初めて起動した人には何も聞かずにOSの言語で始まるようにしました。OSの言語は Unity の Application.systemLanguage で読めます。OSに問い合わせる処理を自分で書く必要はなく、SystemLanguage という列挙型(Japanese / English / ChineseSimplified など、判別できなければ Unknown)で返ってきます。本作では Japanese かどうかだけを見て、それ以外は Unknown も含めてすべて英語にしています。判定するのは PlayerPrefs に言語の保存値が無いときだけで、決めた結果はすぐ保存します。2回目以降は保存値を読むだけなので、設定画面で選び直した言語が常に優先されます。ただし設定画面の「データ削除」は PlayerPrefs.DeleteAll() で全部消すため、言語の保存値も一緒に消えて、次の起動でまたOSの言語に戻ってしまう不具合がありました。図の左がその流れ、右が直したあとの動きです。

直し方: DeleteAll() の直後に言語だけを保存し直す。言語はプレイの記録ではなく遊ぶ人の環境設定なので、データ削除の対象から外しました。言語の切り替えは設定を閉じたときにシーンを読み直して反映します。

Unityあるある

27Destroy() は即時ではない。そして「偽null」がある

Destroy() は即時ではない。そして「偽null」がある

症状: `Destroy(obj)` した直後のコードでまだ `obj` が使える。翌フレームには `obj == null` が true になるのに、`obj?.Foo()` が例外を出す。

原因: Destroy はそのフレームの末尾に実行が予約されるだけです。破棄後の参照は C# としては null ではなく、Unity が `==` 演算子をオーバーロードして null に見せています。そのため `?.` や `??` などC#言語の null 判定は素通りします。

対処: 破棄済みかは `obj == null` か `if (obj)` で判定する。同フレームで消したい場面では、Destroy 前に自分でリストから外す・フラグを立てる。

C#
Destroy(enemy);          // 実際に消えるのはフレーム末
enemies.Remove(enemy);   // リストからは自分で外す

// 翌フレーム以降
if (enemy == null) return;   // OK: Unity の == が破棄を検出
enemy?.TakeDamage(1);        // NG: 破棄済みでも呼ばれて例外
Unityあるある

28Update の中で GetComponent / Find を呼ばない

Update の中で GetComponent / Find を呼ばない

症状: オブジェクトが増えるとフレームレートが落ちる。プロファイラを見ると GetComponent や FindObjectOfType が毎フレーム何千回も呼ばれている。

原因: GetComponent はコンポーネントの線形検索、Find 系はシーン全体の走査です。1回は安くても、N体のUpdate × 相手M体の判定で N×M 回になると重さが露出します。

対処: 参照は Awake / Start で1回取って保持する。他オブジェクトの参照は Inspector で渡すか、シングルトンやレジストリ(生成時に登録・破棄時に解除)で引く。

C#
Rigidbody2D rb;
void Awake() { rb = GetComponent<Rigidbody2D>(); }   // 1回だけ

void Update()
{
    // NG: GetComponent<Rigidbody2D>().velocity = ...
    rb.linearVelocity = dir * speed;                   // OK
}
Unityあるある

29foreach の途中でリストを変更すると例外

foreach の途中でリストを変更すると例外

症状: 敵リストを foreach で回しながらダメージを与えたら `InvalidOperationException: Collection was modified` が出た。死亡処理の中で自分をリストから外していた。

原因: List の列挙子は列挙中の変更を検出して例外を投げます。ダメージ → 死亡 → コールバック → リスト変更、のように間接的に変わるケースは見落としやすい。

対処: 副作用のある処理を呼ぶループは、事前に `ToArray()` / `ToList()` でコピーして回す。単純な削除なら逆順の for ループでもよい。

C#
// 副作用(死亡でリスト変更)があるならコピーを回す
foreach (var e in enemies.ToArray())
    e.TakeDamage(10);

// 単純な削除は逆順 for
for (int i = enemies.Count - 1; i >= 0; i--)
    if (enemies[i].IsDead) enemies.RemoveAt(i);
Unityあるある

30static はシーン再読込を生き残る(再生ボタンも越えることがある)

static はシーン再読込を生き残る(再生ボタンも越えることがある)

症状: リトライでシーンを再読込したのに、前のプレイの状態が残っている。Domain Reload を切っている(Enter Play Mode Options)と、再生を止めて再開しても残る。

原因: static フィールドはシーンではなくアプリ(ドメイン)に属します。シーン再読込では初期化されず、Domain Reload を無効にしていると Play を押し直しても初期化されません。`Time.time` は再読込で 0 に戻るため、static に保存した時刻との比較も壊れます。

対処: static 状態は「どこでリセットするか」を必ず決める。`[RuntimeInitializeOnLoadMethod(SubsystemRegistration)]` で再生開始時にリセットし、シーン再読込時のリセットは1か所に集めて呼ぶ。

C#
public static class RunState
{
    public static int Score;
    public static float LastHitTime;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
    static void ResetOnPlay() { Score = 0; LastHitTime = 0f; }
}
Unityあるある

31Resources.Load の落とし穴3つ

Resources.Load の落とし穴3つ

症状: エディタでは読めたのにビルドで null が返る。ビルドサイズが妙に大きい。ビルドでコンパイルエラーが出る。

原因: ① パスに拡張子を付けると失敗する(`"Sprites/Hero.png"` はNG)。② `Resources` フォルダ配下は使っていなくても全部ビルドに入る。③ `AssetDatabase` はエディタ専用APIで、ビルドには存在しない。

対処: パスは `Resources/` からの相対・拡張子なし。Resources に置くのは実行時ロードが必要なものだけ。エディタ専用のコードは `#if UNITY_EDITOR` で囲み、ビルド側は Resources.Load を通す。

C#
// Assets/Resources/Sprites/Hero.png を読む
var s = Resources.Load<Sprite>("Sprites/Hero");   // 拡張子は付けない

#if UNITY_EDITOR
    // エディタ限定。ビルドには存在しない API
    var s2 = UnityEditor.AssetDatabase.LoadAssetAtPath<Sprite>("Assets/Art/Hero.png");
#endif
Unityあるある

32イベント購読は OnEnable / OnDisable で対にする

イベント購読は OnEnable / OnDisable で対にする

症状: 同じイベントで処理が2回走る。破棄したはずのオブジェクトのメソッドが呼ばれて MissingReferenceException。

原因: static イベントや他オブジェクトのイベントに `+=` した参照は、購読者が Destroy されても自動では外れません。Start で購読して解除を書き忘れる、あるいは再有効化のたびに `+=` が重なる、が典型です。

対処: 購読は OnEnable、解除は OnDisable と決めて必ず対にする。二重購読が怖い場合は `-=` してから `+=` する。

C#
void OnEnable()
{
    Enemy.OnDied -= HandleDied;   // 二重購読ガード
    Enemy.OnDied += HandleDied;
}

void OnDisable()
{
    Enemy.OnDied -= HandleDied;
}
Unityあるある

33UIパネルが別のUIの裏に隠れる

UIパネルが別のUIの裏に隠れる

症状: 新しく作った確認ダイアログが、既存のメニューの後ろに出て押せない。エディタ上では見えていたのに、実行するとボタンが反応しない。

原因: uGUI の前後関係は、Canvas 同士なら Sort Order、同じ Canvas 内なら Hierarchy の並び順(下が手前)で決まります。実行時に生成するUIはこの両方を意識していないと、既存パネルの数値や順序に負けます。

対処: 新規パネルを作るときは既存 Canvas の Sort Order を一覧して、その上に置く値を決める。同じ Canvas に足すなら `transform.SetAsLastSibling()` で手前に出す。

C#
// 別 Canvas なら Sort Order(既存: メニュー 30 / ポーズ 99 のような表を持つ)
canvas.overrideSorting = true;
canvas.sortingOrder = 40;

// 同じ Canvas なら並び順(下が手前)
dialog.transform.SetAsLastSibling();
ポコポコファクトリー

34ドット絵は「描くコード」で作る

ドット絵は「描くコード」で作る

工場の素材ドット絵7種(鉱石・インゴット・結晶など、24×24)は、画像生成AIではなく、Claude Code に Python の描画スクリプトを書かせて作りました。面は多角形の座標指定で塗り(上面は明るく、側面は暗く)、ハイライトや刻印はAIが座標を書いて1点ずつ置き、外周1pxの輪郭とその外側の薄い発光はコードが自動で付けます。人が置いたピクセルはなく、全部がコードです。

ポイント: 色や形は数値を直すだけで変わるので、インゴットを銅に変えるのも1行。素材を増やすときも同じ関数に座標を足すだけで、統一感が崩れません。

ポコポコファクトリー

35「0 のまま進まないクエスト」の正体は、対象を発行時にランダム固定していたこと

「0 のまま進まないクエスト」の正体は、対象を発行時にランダム固定していたこと

納品クエストが 0 個のまま進まない不具合がありました。原因は、クエストを発行するときに盤面の販売所をランダムに 1 台選んで固定し、そこにその品目が届く配線かどうかを見ていなかったこと。数量型は期限なしだったので、外れたカードが 2 枠を永久にふさいでいました。カードには対象の販売所名も出ておらず、プレイヤーには「固定されている」こと自体が分かりません。

直し方: 判定を「品目だけ」にして、どの販売所・取引所で売っても数えるようにしました。発行の候補は直近 30 秒に実際に売れた品目だけ。300 秒進まなければ静かに差し替えます。回帰テストには「販売所 2 台、対象でない方に売っても進む」を足しました。

ポコポコファクトリー

36「演出 控えめ」では軽くならない。効くのは毎フレームの再メッシュを減らすこと

「演出 控えめ」では軽くならない。効くのは毎フレームの再メッシュを減らすこと

設備を置きすぎると重くなる問題がありました。既にあった「演出 控えめ」設定はイベント時の粒の数しか減らしておらず、ノード数に比例する処理には効いていませんでした。実際に重かったのは、流線のスクロールで線のメッシュを毎フレーム作り直すこと、進捗リングの fillAmount 更新、粒 1 個につき Image 5 枚、全ノードの Transform への毎フレーム書き込みです。

ポイント: 軽量モードは「同じものを荒く・少なく・間引いて描く」。線の分割数 26→12、粒は 6 個まで・残像なし、触っていないノードは 3 フレームに 1 回更新。108 台で平均 7.1ms→3.3ms、360 台で 40ms→20ms でした。計測モードをゲームに作って数字で確かめてから採用しています。

ポコポコファクトリー

37「まとめて撤去」は演出もまとめる。1 台ぶんの音と粒を N 回鳴らさない

「まとめて撤去」は演出もまとめる。1 台ぶんの音と粒を N 回鳴らさない

範囲選択でノードをまとめて撤去できるようにしました。既存の撤去は「1 台消えるたびに低いポコ音と小さな粒」を出す作りで、ノード削除のイベントごとに呼ばれます。そのまま N 台を一度に消すと、N 台ぶんの音と粒が同じフレームに重なります。ゲーム側の撤去処理は 1 台ずつのループで正しいので、直すのは演出の側です。

直し方: 一括中だけ立てるフラグで 1 台ぶんの音と粒を止め、縮んで消える動きだけ残しました。代わりに代表の音 1 回・波 1 回・「×N 撤去 +返金」の浮き文字を選択の中心に出します。確認ダイアログには返金の合計を出し、1 台ずつ売るのと同じ返金規則にしてあります。

ポコポコファクトリー

38範囲選択モードの全面オーバーレイがパンを殺す。左以外のドラッグは背景へ委譲する

範囲選択モードの全面オーバーレイがパンを殺す。左以外のドラッグは背景へ委譲する

範囲選択モードでは盤面全面に透明な当たり判定を敷き、左ドラッグを囲み操作にしていました。その結果このモード中はパンが一切できず、囲みたい場所へ寄れません。さらに操作パネル 620×214 と案内バナー 660×44 が画面(1280×720)の 17.5% を隠し、下半分中央のノードは囲めませんでした。

直し方: オーバーレイは右ドラッグ・中ドラッグ・Space+左ドラッグを背景のパン処理へそのまま渡すようにしました(WebGL の右クリックメニューは Unity 6 のランタイムが canvas 上で抑止しています)。パネルは案内バナーごと高さ 64 の帯にして、隠れる面積を 7.7% に。帯の文字は best-fit で縮め、はみ出しをなくしました。

ペーパークエスト

39タイプ中に行末の文節が2行目へ飛ぶ。折り返しは毎フレーム計算し直される

タイプ中に行末の文節が2行目へ飛ぶ。折り返しは毎フレーム計算し直される

会話をタイプライターで出すと、行末の文節がとつぜん2行目へ落ちる不具合がありました。原因は、本文に「打ち終わった文字だけ」を入れていたことです。uGUI の折り返しは、そのとき入っている文字列から毎フレーム計算し直されるので、1行目の末尾にあった文節が幅を超えた瞬間にまるごと下の行へ移ります。セリフは文節ごとに半角スペースを入れて書くため、飛ぶ単位が文節ぶんになり はっきり見えてしまいました。

直し方: ページの最初から全文を流し込み、まだ打っていない部分を透明(α0)の色タグで包むようにしました。組版に使われる文字列が1文字目から最後まで変わらないので、行構成が動きません。頂点をゆらす効果の側も表示ずみの文字数までしか対応表を作らないため、透明なぶんには波もポップも掛かりません。

ペーパークエスト

40帰還スポーンが門の当たり判定に重なると、着いた瞬間にもどされる

帰還スポーンが門の当たり判定に重なると、着いた瞬間にもどされる

となりのマップから戻ってきたとき、着いたそばから元のマップへ送り返されてしまう不具合がありました。原因は、戻り先の出現位置が門のトリガーと重なっていたことです。門の判定の端から 0.25 しか離していないのに、プレイヤーの当たり判定は半径 0.4 あるので、着地した最初のフレームでもう門に触れており、そのまま往復し続けていました。

直し方: 出現位置を門の判定から プレイヤーの半径ぶん以上(ここでは 0.85)離しました。あわせて、マップに入った直後の 0.5 秒は門そのものを反応させない保険も入れています。位置をずらすだけだと、別の門で同じことを繰り返すためです。

ペーパークエスト

41uGUI の Text は空白に頂点を作らない。頂点番号÷4 は文字番号ではない

uGUI の Text は空白に頂点を作らない。頂点番号÷4 は文字番号ではない

特定の文字だけを波打たせる演出で、ゆれる位置が後ろへずれる不具合がありました。原因は「1文字=4頂点」を前提に、頂点番号を4で割った値をそのまま文字番号として使っていたことです。uGUI の文字組みは 半角・全角スペースや改行にはいっさい頂点を作らないので、その文字より前にある空白の数だけ効果の掛かる位置がずれます。

直し方: タグを取り除いた本文から空白を詰めて「四角形番号→文字番号」の対応表を毎回組み立て、それを引くようにしました。ずれる量は文ごとに違うので、オフセット定数でそろえてはいけません。対応づかない四角形は「素通し」に戻さず、効果を掛けないようにしておくのも大事です。

ペーパークエスト

421つの .cs に MonoBehaviour を2つ書くと、シーンの参照が壊れる

1つの .cs に MonoBehaviour を2つ書くと、シーンの参照が壊れる

シーンに置いた仕掛けが17個まとめて動かない不具合がありました。原因は、1つの .cs に MonoBehaviour を2つ以上書いていたことです。Unity は .cs 1つにつきスクリプト資産を1つしか作らず、それは先頭のクラスに結びつきます。2つめ以降をコードから AddComponent してシーンを保存すると、参照が guid の無い壊れた形で焼き込まれてしまいます。

直し方: 1ファイル1クラス(ファイル名=クラス名)に分けました。実行時に「The referenced script is missing!」と出るだけでコンパイルは通ってしまうので、疑わしいときはシーンファイルを m_Script の行で grep して、guid が書かれていない行をさがすのが早いです。

マギカホード

43氷の必殺技は、撃破の瞬間まで氷らしく

氷の必殺技は、撃破の瞬間まで氷らしく

氷の必殺技「絶対零度」が、爆発のように見えてしまう問題がありました。原因は、氷が砕ける場面に青く着色した爆発アニメを使い、敵を倒したときにも共通の爆発演出を出していたことです。色だけ変えても、広がり方や輪郭は爆発のまま。発動時の演出だけを変えても、撃破の瞬間に別の属性に見えてしまいます。

直し方: 発動時は冷気の波、凍結中は氷柱、解放時は氷の破片に変更しました。さらにダメージ処理の前に凍結中か氷の必殺技による攻撃かを記録し、撃破演出にも渡します。属性の見た目は、発動・状態変化・撃破まで一続きで確認するのがポイントです。

こもれびのお茶時間

44名簿の人物が縦に伸びる。切り抜きは元画像の比率で計算する

名簿の人物が縦に伸びる。切り抜きは元画像の比率で計算する

名簿の人物が縦に伸びて見える不具合がありました。原因は、画像を取り込んだ後の寸法を使って切り抜き範囲を計算していたことです。元の立ち絵は1024×1536ですが、取り込み時に寸法が変わると、絵そのものの縦横比と一致しません。その比率で表示枠に合わせると、顔や体つきまで変わって見えます。図は現象を説明するための再現図です。

直し方: 元画像の縦横比を基準に、一覧と詳細それぞれの枠に必要な切り抜きの高さを求めました。幅だけを変えても人物の比率を保てます。通常・笑顔の両画像を、大小の枠で確認しています。

こもれびのお茶時間

45窓の雨が花瓶の前を流れる。背景の一部を前景として重ねる

窓の雨が花瓶の前を流れる。背景の一部を前景として重ねる

窓を伝う雨水が、室内にある花瓶や花の手前まで流れる問題がありました。花瓶も窓も一枚の背景に描かれているため、背景の上に雨を重ねるだけでは、部屋の中と窓の外の前後関係を表せません。雨を薄くするだけでは重なり方は変わらず、窓の水滴まで見えにくくなります。図は描画順の違いを説明するための再現アニメーションです。

直し方: 背景と同じ位置から花瓶と花の輪郭を取り出し、「背景→雨→花瓶と花→人物」の順に描きました。花の隣のガラスでは水滴が動き、室内の花瓶には重なりません。晴れと雨の背景にも同じ順序を適用しています。

こもれびのお茶時間

46扉へ寄りすぎない入店演出。小さなズームと静かな切り替えで見せる

扉へ寄りすぎない入店演出。小さなズームと静かな切り替えで見せる

喫茶店に入る演出で、扉へ大きく寄ってから室内へ切り替えていました。強い拡大に横移動や揺り戻しが加わると、短い入店でも画面の動きが目立ちます。扉をくぐる感じは残しながら、のんびり過ごすゲームの雰囲気に合わせて、移動の大きさと切り替えるタイミングを見直しました。図は動きの違いを説明するための再現アニメーションです。

ポイント: 接近は最大1.65倍に抑え、横移動と揺り戻しをなくしました。室内の絵は固定し、接近が落ち着いてから約1秒かけて溶け込むように切り替えます。扉の開閉やベルは残し、スキップと「動きを控えめに」にも対応しています。