本記事は、Unityで3Dゲームの「最小プレイ可能コア(MVP)」を1日で作るための検証ログです。
対象は「3d ゲーム の 作り方」を検索して最短で手を動かしたい初心者〜初中級。
作るのは、①床の上で動くプレイヤー、②アイテム取得、③ゴールで終了、の3要素。
移動方式(Translate / Rigidbody.MovePosition / CharacterController)や当たり判定(OnCollision / OnTrigger)の実測感を比較し、再現手順と切り替え基準を明確化しました。
最後にスマホ(Android/iOS)向けの最小ビルド確認までを含みます。
検証設計:前提・環境・評価軸(再現性の担保)
まずは前提を固定します。
UnityはLTS系(2021LTS〜2022LTS相当でもOK)を想定。
新規「3D(URPでもBuilt-inでも可)」テンプレートで開始。
シーンにPlane(床)、Directional Light、Main Cameraを初期配置。
プレイヤーはCube→後でCapsuleに置き換え可能。
比較が目的なので見た目は最小、挙動優先です。
評価軸は4点にしました。
①“気持ちよく動く”体感(段差・斜め移動・引っかかり)、②実装コスト(コード量・依存コンポーネント)、③保守性(後からジャンプ/坂/敵などを足せる拡張余地)、④ビルド耐性(PC/スマホで破綻しないか)。
各軸は主観だけでなく、具体トラブルの発生有無と回避策も記録します。
再現手順は「最小→置換→拡張」の順。
まずTranslateで“動いた”を出し、次にRigidbody.MovePosition、最後にCharacterControllerへ置き換えて違いを確認します。
入出力は標準Input Manager(Horizontal/Vertical)を使用。
新式Input Systemを使う場合でも流れは同じです。
Step1:MVPの骨格(床・プレイヤー・カメラ・実行)(所要30分想定)
骨格の目的は「何も起きない世界」を作り、そこに最小限の“変化”を足せる状態にすること。
Project作成後、Hierarchyで右クリック→3D Object→Planeを追加し、TransformのScaleを(10,1,10)へ。
次に3D Object→Cubeを追加し、Position(0,0.5,0)。
Main CameraはPosition(0,3,-6)、Rotation(20,0,0)程度にして、プレイヤーを俯瞰する入り口を確保します。
ここでは“実行しても何も動かない”ことをわざと確認します。
Playを押し、視点・影・床の見え方をチェック。
何も起きない基準があるほど、後続の変化を検知しやすく、バグ切り分けが速くなります。
影が真っ黒/真っ白ならDirectional LightのRotationを少し傾け、Intensityを0.8前後に合わせます。
カメラ追従は後で検証しますが、先に固定カメラで「見失わない角度」を作ることが重要。
FOV(Field of View)は60付近が扱いやすく、クリッピング(Near 0.1 / Far 1000)もデフォルトで十分。
早く追従が欲しい場合は後述のFollowCameraスクリプトを付け替えます。
Step2:3方式の移動を比較(Translate / Rigidbody / CharacterController)
まずはTranslateで即“動いた”を出します。
利点はコードが最小で学習コストが低いこと。
欠点は物理と噛み合わず、段差での貫通や衝突イベントが直感どおりに起きにくい点。
とはいえ、操作感の初期チューニングには最適です。
using UnityEngine;
public class PlayerMove_Translate : MonoBehaviour
{
public float speed = 5f;
void Update()
{
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Vector3 dir = new Vector3(x, 0, z).normalized;
transform.Translate(dir * speed * Time.deltaTime);
}
}
次にRigidbody+MovePosition。
Rigidbodyを付与し(Use Gravityオン、ConstraintsでFreeze Rotation X/Z推奨)、FixedUpdateでMovePositionします。
物理世界に“居る”前提になるので、段差や衝突の整合が取りやすく、斜め移動の等速化も簡単。
AddForceより制御が読みやすいので、入門〜初中級の実装に向いています。
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class PlayerMove_Rigidbody : MonoBehaviour
{
public float speed = 5f;
Rigidbody rb;
void Awake()
{
rb = GetComponent<Rigidbody>();
rb.freezeRotation = true; // 横転対策
}
void FixedUpdate()
{
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Vector3 v = new Vector3(x, 0, z).normalized * speed;
rb.MovePosition(rb.position + v * Time.fixedDeltaTime);
}
}
最後がCharacterController。
物理剛体ではない専用の移動コンポーネントで、段差・スロープ・階段に強く、プレイヤー“っぽい”手触りを得やすいです。
Rigidbodyの力学挙動が不要なアクションに適し、衝突コールバックはOnControllerColliderHit等を使います。
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
public class PlayerMove_CC : MonoBehaviour
{
public float speed = 5f;
CharacterController cc;
void Awake(){ cc = GetComponent<CharacterController>(); }
void Update()
{
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Vector3 move = new Vector3(x, 0, z);
if(move.sqrMagnitude > 1f) move.Normalize();
cc.Move(move * speed * Time.deltaTime);
}
}
所感まとめ:短期の“動いた”はTranslate、物理と整合したい&後でジャンプ/ノックバックを入れたいならRigidbody、坂/段差/キャラ操作の素直さを優先するならCharacterController。
以降の検証はRigidbodyを主系、CCを代替案として扱います。
Step3:視界の安定化(固定・追従・軌道回転)の比較
固定カメラは実装ゼロで破綻が少ない反面、プレイヤーを見失いやすい。
追従カメラ(LateUpdateで位置合わせ)はコストがほぼゼロで、“ゲームをプレイしている感”が出ます。
軌道回転(マウス/右スティックで回す)までいくと、空間理解は上がるが酔いとチューニングコストが跳ねます。
using UnityEngine;
public class FollowCamera : MonoBehaviour
{
public Transform target;
public Vector3 offset = new Vector3(0, 3, -6);
void LateUpdate()
{
if(!target) return;
transform.position = target.position + offset;
transform.LookAt(target);
}
}
軌道回転の最小実装は、角度を蓄積して角度→位置へ変換(target周回)です。
スムージングや障害物回避(SphereCast等)は後で足せます。
初心者段階では“回せるけど強制ではない”設計にし、オプションに留める方が事故りません。
using UnityEngine;
public class OrbitCamera : MonoBehaviour
{
public Transform target;
public float distance = 6f;
public float yawSpeed = 120f;
float yaw;
void LateUpdate()
{
if(!target) return;
yaw += Input.GetAxis("Mouse X") * yawSpeed * Time.deltaTime;
Quaternion rot = Quaternion.Euler(20f, yaw, 0f);
transform.position = target.position + rot * new Vector3(0, 0, -distance);
transform.LookAt(target);
}
}
結論:MVPでは追従カメラ一本で十分。
軌道回転は導入コストが急に上がるので、レベル構造や戦闘設計が固まってから。
PC/スマホの両方で同じ“見失わない”体験を作りやすいのは固定または追従です。
Step4:当たり判定とインタラクション(TriggerとCollisionの使い分け)
「触れたら拾う」はTriggerで、「当たったら跳ね返る/乗る」はCollisionが基本。
アイテム(Sphere)はColliderの“Is Trigger”をON、Tagを“Item”。
プレイヤー側でOnTriggerEnterを受けて消し込みます。
using UnityEngine;
public class ItemPicker : MonoBehaviour
{
public int score;
void OnTriggerEnter(Collider other)
{
if(other.CompareTag("Item"))
{
score++;
Destroy(other.gameObject);
}
}
}
床/壁はBoxColliderのみ、動かす側(プレイヤー)にRigidbodyを付けると混乱が減ります。
高速移動でのすり抜けは、Rigidbody.collisionDetectionModeをContinuous Dynamic、床側はContinuousに。
MeshColliderは凹形状・パフォーマンス面で罠が多いので、入門段階ではBox/Sphere/Capsuleの組み合わせに寄せるのが安定でした。
ゴール処理はTriggerにしてTime.timeScale=0で“ひとまず停止”。
UIやステート管理を後回しにできるのが利点。
のちにGameState(Title/Playing/Result)をenumで持ち、UIを有効/無効切替する構造へ移行すると、ビルド先(スマホ)でも破綻しづらくなります。
using UnityEngine;
public class Goal : MonoBehaviour
{
public GameObject finishText;
void OnTriggerEnter(Collider other)
{
if(other.CompareTag("Player"))
{
if(finishText) finishText.SetActive(true);
Time.timeScale = 0f;
}
}
}
Step5:スマホ向けの最小確認(解像度・操作入力・負荷)
Android/iOSでの“まず映る”チェックは、Build Settingsでプラットフォーム切替→Build And Run。
最初は操作をPC前提のままでもよく、見切れ/視界/ライトの破綻がないかを確認します。
URPの場合はQuality設定のShadow Distanceが小さすぎて影が消えることがあるため、外光だけに頼らずライト/マテリアルを簡易に整えるのがコツ。
操作はVirtual Joystickに置き換えるのが王道ですが、初回は加速度やタップ移動を試すと配信イメージが掴みやすいです。
Input Systemに移行する場合は“PlayerInput”を用い、UI Action Mapを作っておくと後でメニュー導線が綺麗に伸ばせます。
重要なのは、最初のビルドで“操作最適化はやらない”と割り切ること。
性能はProfilerでフレームタイムをざっくり見る程度でOK。
DrawCallが急増していたら、床や壁をできるだけ大きめのプリミティブにまとめる、リアルタイム影を抑える、ポストプロセスを切る等の基本を先に。
アニメやAIは後回しにして“世界の芯”の軽さを確保します。
Step6:よくある落とし穴と復旧レシピ(原因→対策の1対1対応)
「床を突き抜ける」——原因は床がColliderを持たない/薄すぎる/連続判定がOff。
対策は床にBoxCollider、RigidbodyをContinuous Dynamic、Fixed Timestepを既定(0.02)に戻す。
高速移動での抜けを減らすため、速度を抑えたうえでMovePositionにするのも有効でした。
「斜め移動が速い」——方向ベクトルをnormalizedにして等速化。
段差でつっかえるならColliderをCapsuleへ変更、RigidbodyはFreeze Rotationで横転防止。
どうしても角に吸い込まれる感覚が残る場合、CharacterControllerへ置換し、Step Offset/Slide等を微調整すると体感が安定します。
「影が急に消えた」——Directional LightのShadow TypeがOff、QualityのShadow Distanceが小さすぎる、URP Assetのシャドウ設定が弱い等。
ライト角度を僅かに傾け、強度0.8前後、Soft Shadowsに変更。
地形に軽い色を入れると“見えた感”が戻ります。
最小コアの拡張ロードマップ(アクション・UI・演出)
ジャンプは“接地判定→AddForce”の2手。
Raycastで足元に地面があるときだけ縦方向へ力を与えます。
Rigidbody系なら手触りが明快、CCならVelocityを自前で積んで重力を模倣します。
いずれも「接地/空中」の状態管理が要点でした。
using UnityEngine;
[RequireComponent(typeof(Rigidbody), typeof(CapsuleCollider))]
public class JumpSample : MonoBehaviour
{
public float jumpForce = 5f;
public LayerMask ground;
Rigidbody rb;
float rayLen = 0.6f;
void Awake(){ rb = GetComponent<Rigidbody>(); }
void Update()
{
bool grounded = Physics.Raycast(transform.position, Vector3.down, rayLen, ground);
if(grounded && Input.GetButtonDown("Jump"))
rb.AddForce(Vector3.up * jumpForce, ForceMode.VelocityChange);
}
}
UIはTextMeshProでスコア→リザルト→タイトルの順が安全。
GameStateをenumで持ち、UI CanvasのSetActive切替で段階的に構築します。
SE/BGMは“成功/失敗/決定”の3音を先に置くだけで世界が締まるので、見た目より先に音を足すのも有効でした。
演出は「ライトの色温度」「カメラの揺れ(小さく)」「獲得時のスケールUp→Down」など、小さな一手で体感が大きく変化。
プロファイラでコストを見つつ“効く一手”を優先します。
結論:作業順の正解は“芯→置換→拡張”。比較結果のサマリと次の一歩
比較の結論は明快でした。
①最短で“動いた”を得るならTranslate、②物理と整合しつつ拡張したいならRigidbody.MovePosition、③段差・坂・階段の歩行感を優先するならCharacterController。
カメラはMVPでは追従一択、当たり判定は「拾う=Trigger」「ぶつかる=Collision」を明確に分ける。
スマホは“まず映る・見失わない”の2点だけ先に見る。
再現のための最小手順を最後にまとめます。
- 新規3Dプロジェクト→Plane(10,1,10)/Cube(0,0.5,0)/Camera(0,3,-6)
- PlayerMove_TranslateをCubeへ→“動く”確認→速度をInspectorで調整
- Rigidbodyを追加→PlayerMove_Rigidbodyに置き換え→段差/衝突の整合確認
- SphereにIs Trigger+Tag=Item→ItemPickerで取得→破綻がなければSE/BGMは後回し
- CylinderにIs Trigger+Tag=Goal→Goalで停止→“今日はここまで”を作る
- 必要ならFollowCameraをMain Cameraへ→見失わない視界を確保
- Android/iOSへビルド→解像度と影の破綻がないかだけ先に確認
体系的に進めたいときは、段階設計の合う教材やアセットを適宜組み合わせるのが効率的です。
わたしはステップの粒度がちょうどよかったUnity入門の森ショップを参照しつつ、この記事の「芯→置換→拡張」の順で機能を積み上げています。
まずはMVPを1本、そこから先は比較しながら育てる——それがいちばん速かったです。
