牽引するゲームでは、自機が壁を避けるだけでは足りません。 私がCodexで制作している「灰都の救難艇」では、艇の後ろを動く救命カプセルと、その間のケーブルにも接触の判定を置きました。角を曲がるとき、艇は通れてもカプセルが建物へ引っかかるためです。
この記事は、v1で検証した牽引・接触・回収のCoreの作り方です。完成ゲームの画面や音を一から組む手順は扱いません。ゲームを直接遊ぶか、操作と制作意図を見ると、牽引の動きを確認できます。

動かせるC#サンプルで経路を比較する
.NET 10 SDKを用意し、Coreと検査操縦のZIPを展開します。追加NuGetパッケージは使いません。次をHaitoSample.csprojがある場所で実行します。
dotnet run --project HaitoSample.csproj -c Release
Wind route Won health 5 seconds 85.42043 damage 0 capsuleHealth 3,3,3 Wind no-wait comparison Lost health 5 rescued 0 seconds 9.600025 damage 3 capsuleHealth 0,3,3 Checks 73
出力のLostは検査の失敗ではありません。同じ任務へ、予告を待たない操縦を持ち込むと必須カプセルを失うことを確認しています。Program.csに成功する経路と比較対象の経路があり、Rescue.csに実ゲームの計算、RescueProgress.csに解放と自己ベストの保存規則があります。
v1.0.1では、保存記録の整合性の検査を1件追加しました。任務2のクリア記録が欠けているのに任務3の自己ベストだけ残る保存では、解放数を戻しても任務3の再訪ボタンが残っていました。解放範囲の確定後に範囲外の記録も除き、正常な任務1の自己ベストを保つ条件を確認しています。当時は69件でした。現在のZIPは敗北原因の検査4件も含むChecks 73です。牽引や敵の数値は変えていません。
救助結果は損傷と時間を別々に比較する
v1.1では、クリア時に今回の損傷・所要時間と自己ベストを並べました。損傷が少ない記録を優先し、同じ損傷なら短い時間を採用します。速く帰還できても損傷が増えた場合はベスト維持、遅くても損傷が減れば更新です。時間だけを見ると判定が逆に感じられるため、この順序を結果画面にも明記しました。
表示は、既存の保存処理が採用する前後の記録を比べて初回達成・更新・維持を確定します。結果を表示するたびに判定し直すと、更新直後の記録が自分自身と同じになり、更新表示が維持に変わってしまいます。完了時の一度だけ確定し、閲覧中は時間も文言も変えません。保存拒否時は画面内のベストと保存不可の案内を併記します。

失敗した原因を、任務名から推測しない
横風の任務でカプセルを失っても、最後の被害が風とは限りません。曲がり角への接触や巡洋艦の封鎖砲撃でも失敗します。以前の結果表示は任務ごとの一般案内へ置き換えていたため、直前の被害を区別できませんでした。
v1.2では、実際に耐久を減らした処理で対象別の原因を記録します。建物や境界への接触、横風での接触、砲弾、封鎖砲撃を分け、救命番号と再挑戦の判断を結果へ残しました。艇とカプセルを同じ刻みで失った場合も、両方を表示します。

v1.3では、艇と接続中の救命の風よけ内外を別々に表示します。実際の風を止めるShelteredをそのまま参照するため、艇だけが建物の陰にいる場合に救命まで安全とは案内しません。風向きが反転すると保護範囲も変わります。低い架橋は風を止めない既存ルールのままです。

単なる接触回数や一時的な案内文からは推測しません。無敵時間中に接触した場合は耐久を減らさず、原因も書き換えない条件を検査しました。敵の強さや航路は変えず、何を見直すかが分かる情報を追加しています。
艇・カプセル・ケーブルを分ける
手順 01
艇を動かす
壁と境界へ接触
手順 02
カプセルを引く
横風と角を検査
手順 03
ケーブルを検査
建物を横切るか
手順 04
回収する
位置・速度・時間
艇の半径は0.42、カプセルは0.48、ケーブルの長さは2.2です。接続中は通常速度を5.2から3.4へ落としました。艇から遠いカプセルを毎回同じ位置へ瞬間移動させるのではなく、ケーブルの長さを超えた分を引き、途中の建物へ接触するかも調べます。
V offset = c.Position - Ship;
if (offset.Length > CableLength)
{
V desired = Ship + offset.Unit * CableLength;
c.Position = Sweep(c.Position, desired - c.Position,
CapsuleRadius, out bool hit);
if (hit)
HurtCapsule(c, "カプセルが建物に接触。外側へ回るか切り離そう。");
V stretch = Ship - c.Position;
if (stretch.Length > CableLength + .1f)
{
Ship = Sweep(Ship, c.Position + stretch.Unit * CableLength - Ship,
ShipRadius, out _);
Velocity = new V();
}
}
カプセルが壁に止まった後も艇だけ進ませると、ケーブルが際限なく伸びます。そこで、艇の位置も最大長の近くまで制限します。接触状態からは、外側へ回る、切り離して退避する、拾い直すという選択を残しました。
接触による耐久減少には0.7秒の待ち時間を入れています。ただし無敵のまま抜けられるようにはせず、同じ壁際で押し続けるとカプセルは失われます。同じ接触状態から、押し続ける操縦は1.633秒、加速を押し続ける操縦は1.517秒で喪失しました。退避する操縦は再接続して回収できました。
加速中の薄い壁と、ケーブルだけの接触を検査する
Sweepは移動量を最大0.08ずつに分け、各位置で円と建物の矩形を検査します。斜め移動で全体が止まった場合はX、Yを順に試して壁沿いに滑らせます。この数値は本作の速度と地形で検証した値で、任意の薄壁を通り抜けない数学的保証とは扱いません。
int n = Math.Max(1, (int)Math.Ceiling(delta.Length / .08f));
V d = delta / n, p = from;
for (int i = 0; i < n; i++)
{
V full = p + d;
if (Free(full, radius)) { p = full; continue; }
contact = true;
V x = new V(p.X + d.X, p.Y);
if (Free(x, radius)) p = x;
V y = new V(p.X, p.Y + d.Y);
if (Free(y, radius)) p = y;
}
ケーブルは艇とカプセルの直線を0.08以下の間隔で調べ、半径0.025の接触を検査します。どちらの端も建物の外にいても、線が角を横切ればCableBlockedになります。接触中の艇の速度は0.35倍。ケーブルが建物の裏を通って問題なく引ける見た目を避けました。
別の記事との違い
艇だけの帰港を成功にしない
回収はカプセルが港の半径1.8以内、艇の速度が2.2以下、その状態が0.4秒続いた場合です。条件を外れたら滞在時間を0へ戻します。一度救助したものはRescuedにして再計上しません。
if (c.Health > 0 && (c.Position - Port).Length <= 1.8f
&& Velocity.Length <= 2.2f)
c.DockTime += dt;
else
c.DockTime = 0;
if (c.DockTime >= .4f)
{
c.Rescued = true;
Rescued++;
Attached = -1;
ReconnectTime = .8f;
}
最終回収とカプセル破壊の境界も検査しました。耐久0の必須カプセルを成功として数えず、すべての救助と艇の帰還がそろった場合にだけWonへ進みます。未達の帰港では、未回収の数や減速が必要な理由を画面に出します。
横風は「待つ意味があるか」まで比較した
横風を実装しても待つ意味がなかった
- 症状
- 最初の配置では、待たない大回りも無傷で成功した。
- 原因
- 風のある区間を短時間で通れ、予告を使う操縦と使わない操縦の結果が分かれなかった。
- 対処
- 風を遮らない低い架橋と、その狭い区間を組み直した。経路と周期を同条件で再実行し、別の救助順でも成功を確認した。
| 同じ横風任務の操縦方針 | 結果 | 時間 | カプセル耐久 |
|---|---|---|---|
| 予告を見て弱まる時間を待つ | 全員救助・帰還 | 85.42043秒 | 3 / 3 / 3 |
| 待たずに同じ経路を進む | 必須喪失・救助0 | 9.600025秒 | 0 / 3 / 3 |
| 中央のカプセルから先に拾う | 全員救助・帰還 | 85.42043秒 | 3 / 3 / 3 |
風の周期は静穏4秒、予告2秒、横風4秒、静穏2秒です。建物の風下には3.5の陰を作り、艇だけでなくカプセルの位置からも風を調べます。未接続のカプセルは固定具で保護し、出動後に待っているだけで目的物が壊れないようにしました。
風が動いて見えることと、風を読む必要があることは別々に確かめる。
数字は固定刻みの検査操縦の結果です。人が初めて遊んだときの難度や所要時間は測っていません。PCと375px幅のブラウザでは、実際のキーボード・マウス操縦パッド・タッチで全3任務の救助と保存を確認しました。スマホ幅はPC上のエミュレーションです。
まとめ
- 艇、カプセル、ケーブルの接触を分け、牽引物が止まったら艇の伸びすぎも制限する。
- 切り離しと再接続、減速と港内の滞在時間を設け、押し続けるだけの操作と立て直しの結果を比較する。
- 風のある任務は、予告を使う経路、使わない経路、別の救助順を実行して判断差を調べる。