株式会社グローバルゲート公式ブログ
こんにちは、株式会社グローバルゲートのモーリーです。
楽しみにしていたお盆休みも気づけばあっという間に過ぎ去り、ほんの少し…本当に少しだけですが秋の気配を感じるときもあるような、ないような。
とはいえ、日中はまだ厳しい暑さが続いていますので、皆さまも熱中症など体調管理にはくれぐれもご注意ください。
今回はユニティちゃんが秋葉原の街を探索できるようにするところまでを目指してやってみたいと思います。
ちなみに:ユニティちゃんとは
Unity開発元の日本法人であるユニティ・テクノロジーズ・ジャパンによって作られたオリジナルキャラクターです。
本名は大鳥こはく、カレーコロッケが好き。
ちなみに2013年に誕生したかなり古参のキャラクターです。
公式サイト
※ Unity Hubのインストールとプロジェクトの作成は前回の記事もあわせてご参照ください。
Unity Hubを起動し、New Projectをクリックして新しいプロジェクトを作成します。
今回は3DゲームをつくりたいのでテンプレートでUniversal 3Dを選択します。
ちなみにHigh Definition 3Dはよりハイクオリティでリアルな3Dゲームを作りたいときのテンプレートです。
プロジェクトの作成が完了するとエディタが起動します。
2Dのときと異なり、3D画面が広がっています。
まずはアセットストアから街並みのアセットをダウンロードし、配置してみましょう。
今回はゼンリンが提供している秋葉原の街を使ってみたいと思います。
Unity Asset StoreでJapanese Otaku Cityと検索してください。
詳細ページを開き、マイアセットに追加するをクリックします。
Unityエディタに戻り、上部メニューよりMy Assetsを選択します。
Package Managerが開きますので、先程のJapanese Otaku Cityを選択し、Download→Importの順にクリックします。
Import Unity Packageのウインドウが開きますので、Importをクリックします。
Importが完了すると、Projectタブの中にAssets配下にZRNAssetsというフォルダが作成されています。
この中に街並みのオブジェクトやテクスチャ、サンプルファイルなどが入っています。
実際にアキバの街並みのファイルを開いてみます。
この中の005339_.....→Scenes→Sample_00...というファイルをダブルクリックして開きます。
(フォルダ名はアセットのバージョンによって異なる可能性があります)
開き…ましたがピンク色のシルエットが表示されるだけで、これで正しいの?と思う画面になりました。
このピンクはUnityのバージョン差異によってテクスチャーが読み込めない状態を意味します。
ゼンリンのアセットは2018年リリースで今となっては古いものですので、仕方がありません。
これを修正して最新のUnityで使えるようにします。
※この工程は最新のUnityに対応したアセットでは必要ありません。
プロジェクトタブでMaterialを開きます。
本来ならテクスチャーのプレビューが表示されますが、すべてピンクの丸になっています。
このファイルを選択した状態でEdit→Rendering→Materials→Convert Selected Built-in Materials to URPを選択します。
(URPはUniversal Render Pipelineの略で、最新のUnityで使用されている新しいレンダリング方式です)
これである程度はテクスチャのプレビューが表示されました。
まだいくつかピンクの丸状態のマテリアルが残っているので、これを手作業で修正します。
先程のConvert...で修正できなかったマテリアルは1つずつ修正する必要があります。
マテリアルの1つを選択し、Inspectorを確認します。
Main Mapsに選択されている画像をダブルクリックするとProjectタブ上でハイライトされますので、このファイルが何かを覚えておきます。
次にShaderの選択をUniversal Render Pipeline → Lit に変更します。
Base Mapの◉をクリックし、Main Mapsで選択されていたファイルを選択します。
同時に、Surface OptionのAlpha Clippingのチェックをオンにします。
この作業をピンクの丸になっているマテリアル分繰り返して…
ようやく街が表示されました!
最新版のUnityに対応しているアセットなら読み込むだけですぐ使えると思います…
現状はアセット内のサンプル用シーンファイル上で作業を行っていましたので、メインのシーンファイルに街並みのみ移しておきます。
Hierarchyの中のPQ_Rewmake_AKIHABARAとTexturePlusをコピーし、Assets→Scene内のSampleSceneを開き、Hierarchy内でペーストします。これで街並みのみが使えるようになりました。
続いて、ユニティちゃんアセットをインポートします。
ユニティちゃんはUnity Asset Store上のアセットではバージョンが古いため、公式サイトからダウンロードします。
公式サイトのDATA DOWNLOADをクリックし、ライセンスや規約を確認して同意の上データをダウンロードするをクリックし、ユニティちゃん 3Dモデルデータ のDOWNLOADをクリックします。
UnityChan_v1.4.0.unitypackageがユニティちゃんアセットです。
ダウンロードしたアセットをインポートします。
メニューのAssets → Import Package → Custom Package...をクリックし、先程のファイルを選択します。
ゼンリンのアセットと同様にImport Unity Packageのウインドウが表示されますので、Importをクリックします。
ユニティちゃんもゼンリンのアセットと同様にピンクになっていますが、ユニティちゃんの場合はUnity Toon Shaderという拡張機能を入れることで解決します。
Package MangerのInstall package from git URLを選択し、com.unity.toonshaderと入力し、Installをクリックします。
ユニティちゃんがきちんと表示されました!
ステージにユニティちゃんを設置します。
プレハブ化されているオブジェクトがあるため、そちらを使いましょう。
prefabs → unitychan_dynamic.prefabを選択してHierarchyにドラッグします。
ちなみにunitychan.prefabというオブジェクトもありますが、こちらはアニメーションや動きが簡略された軽量バージョンとなります。
ユニティちゃんがアキバに降り立つ!…が、スケールがあっていませんので巨人になってしまいました。
街アセットを拡大し、サイズ感を合わせておきます。
HierarchyでPQ_Remake_AKIHABARAを選択し、InspectorのTransform → Scaleで6倍にします。
これでちょうどいい感じになりました。
ユニティちゃんを縮小した場合、地面の判定などがおかしくなってしまったので街並みを拡大する方法を取りました。
いよいよユニティちゃんを操作できるようにしていきましょう。
ユニティちゃんはキーボードの上キーで前に進み、下キーで後ろに振り向く、左右キーで向きを変える、スペースキーでジャンプ、Shiftを押しながらの矢印キーでダッシュという設定にしました。
スペースキーの入力を受け取るようにInput Systemに追加します。
Assetsタブ内のInputSystem_Actionsをダブルクリックし、Input Systemウインドウを開きます。
Actionsの+をクリックしてJumpという名称のアクションを追加し、BindingのPathでSpace(Keyboard)を選択します。
次に操作用のスクリプトを記述します。
Assetsタブの中で右クリックし、Create → Scripting → Empty C# Scriptを選択します。
ファイル名はUnityChanKeyboardControllerとしておきます。
作成されたスクリプトファイルをダブルクリックしてエディタで開き、次のスクリプトを記述します。
// TPSPlayer_InputSystem.cs (Unityちゃんに付ける)
using UnityEngine;
using UnityEngine.InputSystem;
[RequireComponent(typeof(CharacterController))]
public class TPSPlayer_InputSystem : MonoBehaviour
{
[Header("Move")]
public float walkSpeed = 2.8f;
public float runSpeed = 5.2f;
[Header("Jump/Gravity")]
public float gravity = -9.81f; // 負の値
public float jumpHeight = 1.2f;
// --- 誤ジャンプ防止&安定化 ---
[Header("Jump Stability")]
[SerializeField] float coyoteTime = 0.12f; // コヨーテ
[SerializeField] float jumpBuffer = 0.12f; // バッファ
[SerializeField] float jumpCooldown = 0.12f; // クールダウン
// --- 落下チューニング(追加)---
[Header("Fall Tuning")]
[Tooltip("落下中(velocity.y<0)に重力へ掛ける倍率。2.0〜2.5くらいが目安")]
[SerializeField] float fallMultiplier = 2.0f;
[Tooltip("ジャンプ上昇中にジャンプ鍵を離したときの重力倍率(低めジャンプ)")]
[SerializeField] float lowJumpMultiplier = 2.0f;
[Tooltip("下向きの最大速度(m/s)。絶対値で制限)")]
[SerializeField] float maxFallSpeed = 40f;
[Header("Facing")]
[Tooltip("回転速度(度/秒)。小さいほどゆっくり向きが変わる")]
public float turnSpeed = 360f;
[Range(0f, 1f)]
[Tooltip("横入力に対してどれだけ向きを合わせるか。0=全く向けない(完全ストレイフ), 1=今まで通り")]
public float strafeFacingWeight = 0.2f; // まずは 0.2 くらいがおすすめ
float coyoteTimer = 0f;
float bufferTimer = -999f;
float lastJumpAt = -999f;
CharacterController controller;
Animator animator;
GameInput input; // 生成クラス
GameInput.PlayerActions player; // ActionMap: Player
Vector3 velocity;
void Awake()
{
controller = GetComponent<CharacterController>();
animator = GetComponentInChildren<Animator>();
input = new GameInput();
player = input.Player;
}
void OnEnable() { input.Enable(); player.Enable(); }
void OnDisable() { player.Disable(); input.Disable(); }
void Update()
{
// --- 入力取得 ---
Vector2 mv = player.Move.ReadValue<Vector2>(); // WASD / Lスティック
bool sprint = player.Sprint.IsPressed(); // Shift
bool jumpDown = player.Jump.WasPressedThisFrame(); // 押した瞬間
bool jumpHeld = player.Jump.IsPressed(); // 押しっぱ
// ジャンプ早押しバッファ
if (jumpDown) bufferTimer = jumpBuffer;
// --- カメラ基準の移動方向 ---
var cam = Camera.main;
Vector3 camF = Vector3.Scale(cam.transform.forward, new Vector3(1,0,1)).normalized;
Vector3 camR = cam.transform.right;
Vector3 moveDir = (camF * mv.y + camR * mv.x);
if (moveDir.sqrMagnitude > 1f) moveDir.Normalize();
// --- 向き ---
if (moveDir.sqrMagnitude > 0.0001f)
{
// 横入力は向きにあまり影響させない(ストレイフ気味に)
Vector3 faceDir = (camF * mv.y) + (camR * (mv.x * strafeFacingWeight));
if (faceDir.sqrMagnitude > 0.0001f)
{
var targetRot = Quaternion.LookRotation(faceDir.normalized, Vector3.up);
// 割合補間ではなく、1秒あたり turnSpeed 度までで回す(回し過ぎ防止)
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
targetRot,
turnSpeed * Time.deltaTime
);
}
}
// --- 水平移動 ---
float speed = sprint ? runSpeed : walkSpeed;
controller.Move(moveDir * speed * Time.deltaTime);
// --- 接地(このフレーム固定) ---
// bool grounded = controller.isGrounded;
bool grounded = true;
// コヨーテ更新
if (grounded) coyoteTimer = coyoteTime;
else coyoteTimer -= Time.deltaTime;
bool consideredGrounded = grounded || (coyoteTimer > 0f);
// 接地中の下向き速度を抑える
if (consideredGrounded && velocity.y < 0f) velocity.y = -2f;
// --- ジャンプ判定 ---
bool canJump = (jumpHeight > 0f)
&& (bufferTimer > 0f)
&& consideredGrounded
&& (Time.time - lastJumpAt >= jumpCooldown);
if (canJump)
{
lastJumpAt = Time.time;
bufferTimer = -999f;
coyoteTimer = 0f;
// 物理で初速付与(見た目はアニメで)
velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);
if (animator && HasParam("Jump")) animator.SetTrigger("Jump");
}
// --- 重力(落下強化&低めジャンプ)※ここが追加の肝 ---
float g = gravity;
if (velocity.y < 0f) g *= fallMultiplier; // 落下中は強め
else if (!jumpHeld) g *= lowJumpMultiplier; // 上昇中にキー離したら強め
velocity.y += g * Time.deltaTime;
// 終端速度(最大落下速度)でクランプ
if (velocity.y < -maxFallSpeed) velocity.y = -maxFallSpeed;
controller.Move(velocity * Time.deltaTime);
// バッファ減衰
bufferTimer -= Time.deltaTime;
// --- Animator(任意)---
if (animator)
{
float moveAmount = mv.magnitude;
float speed01 = Mathf.Clamp01(moveAmount) * (sprint ? 1f : 0.5f);
if (HasParam("Speed")) animator.SetFloat("Speed", speed01, 0.1f, Time.deltaTime);
if (HasParam("Grounded")) animator.SetBool("Grounded", consideredGrounded);
if (HasParam("VerticalSpeed")) animator.SetFloat("VerticalSpeed", velocity.y);
}
}
bool HasParam(string name)
{
if (!animator) return false;
foreach (var p in animator.parameters) if (p.name == name) return true;
return false;
}
}
このスクリプトをInspectorタブにドラッグしてユニティちゃんにアタッチします。
これでユニティちゃんを操作できるようになりましたが、このままではユニティちゃんが画面外に出てしまいどこにいるか分からなくなってしまいます。
そこでカメラにスクリプトをアタッチし、操作キャラを追いかけるようにします。
Hierarchyタブの中で右クリックし、Create Emptyを選択してGame Objectを作成します。
そしてそのGame ObjectをユニティちゃんPrefabの配下に置き、ユニティちゃんの頭あたりに来るように調整します。
次にAssetsの中で右クリックしてEmpty C# Scriptを選択し、空のスクリプトを作成します。名前はOrbitFollowCameraとします。
ダブルクリックでエディタを開き、次のスクリプトを入力します。
// FollowCameraNoMouse.cs (Main Camera に付ける)
using UnityEngine;
public class FollowCameraNoMouse : MonoBehaviour
{
public Transform target; // Unityちゃん子の CameraTarget
public Vector3 offset = new Vector3(0, 1.4f, -4f);
[Range(-80, 80)] public float pitch = 15f; // 仰角
public float yawOffset = 0f; // 右肩越し等にしたい時は±で調整
public float posSmooth = 10f; // 追従の滑らかさ
public float rotSmooth = 12f;
public float collisionRadius = 0.25f; // 壁貫通防止
public LayerMask collisionMask = ~0;
void LateUpdate()
{
if (!target) return;
// プレイヤーの向き + オフセットでカメラの向きを決める(マウス入力なし)
float yaw = target.parent ? target.parent.eulerAngles.y + yawOffset
: target.eulerAngles.y + yawOffset;
Quaternion rot = Quaternion.Euler(pitch, yaw, 0f);
// 目標位置(ターゲットからオフセットぶん後方)
Vector3 desired = target.position + rot * offset;
// 壁貫通防止(簡易スフィアキャスト)
Vector3 from = target.position;
Vector3 dir = desired - from;
float dist = dir.magnitude;
if (Physics.SphereCast(from, collisionRadius, dir.normalized, out var hit, dist, collisionMask, QueryTriggerInteraction.Ignore))
desired = hit.point - dir.normalized * (collisionRadius * 1.1f);
// スムーズ追従
float tPos = 1f - Mathf.Exp(-posSmooth * Time.deltaTime);
float tRot = 1f - Mathf.Exp(-rotSmooth * Time.deltaTime);
transform.position = Vector3.Lerp(transform.position, desired, tPos);
transform.rotation = Quaternion.Slerp(transform.rotation, rot, tRot);
}
}
このスクリプトをMain Cameraにアタッチし、Targetでユニティちゃん配下に置いたGame Objectを選択します。
この時点で最低限の操作ができるようになりました!
ただ歩行モーションの設定をしていないため、地面を滑っているような動きになってしまいます。
ユニティちゃんアセットには歩行やジャンプのモーションがありますので、それを使えるようにします。
ユニティちゃんを選択し、InspectorのControllerでUnityChanLocomotionsを選択します。
UnityChanLocomotionsの表記部分をダブルクリックし、Animatorタブを開きます。
このコントローラーは概ねこのままで動作するのですが、ジャンプの判定と静止状態からのジャンプがうまく動きませんでしたので、その点を修正します。
Parametersタブを開き、既存のJumpを削除し、+をクリックしてTriggerを選択、名称をJumpにします。
Idleを右クリックしてMake Transitionを選択し、矢印をJumpにつなげます。
この矢印をクリックし、ConditionsでJumpを選択します。
これで一通り動くようになったのですが、現状は当たり判定を設定していないため、ユニティちゃんがビルを突き抜けてしまいます。
建物に当たり判定を設定し、この問題が起きないようにしましょう。
ユニティちゃんを選択し、Add ComponentからCharactor Contorollerを追加します。
次に建物オブジェクトをHierarchy上で選択し、Add ComponentからMesh Coliderを追加します。
(ゼンリンのアセットではBlock_◯というグループ配下が建物になっています)
これで壁を突き抜けることなく秋葉原をお散歩できます。
ということで、アキバの街を駆け抜けるユニティちゃんを完成させることができました。
実行ファイルはこちら(Windows版)
ZIPファイルをダウンロードし、3D.exeを実行すると遊べます。
ブラウザ用にビルドしてご紹介しようと思いましたがエラーが解決しませんでした…
なお、建物や高速道路などには当たり判定を追加しましたが、街の境界には何も設定していませんので市外に出ようとすると…
ユニティちゃぁぁぁん!
※ 操作不能になったらアプリを終了させてください。
ということで、今回はアセットを活用しつつUnityで3Dアクションゲームの初歩の初歩を作ってみました。
これだけでもかなり苦労したので、ゲームとして完成させるにはどれほどの年月が必要になることやら…。
前回と今回の記事でゲーム開発の一端に触れることができましたが、その苦労をわずかでも垣間見たことで、どれほどひどいクオリティのゲームでも軽々しく笑うことができなくなりました。星をみるひとでもコンボイの謎でも、リリースまでには多くの苦労があったことでしょう。KOTYも見ていて心が痛む
当社ではUnityによるゲーム開発はお受けできませんが、ホームページへのCMS導入やリニューアルなどのご依頼については随時承っております。
お気軽にお問い合わせください。
【関連記事】
カテゴリー
月別アーカイブ
ブログ内検索
執筆メンバーについて
モーリー
Webデザイナー。
当サイトのデザインと管理も担当しています。
ナミー
Webディレクター。
本社制作部の紅一点。お客様に寄り添った提案を心かげています。
タカ
サーバーエンジニア。
Webサイトにとってサーバーは命、ネットワークは血液です。Webサイトの安定稼働のために日夜注力しています。
たっくん
ITアドバイザー
Webサイトの活用方法からオフィスのネットワーク整備まで、多角的にITの活用方法をご案内させていただきます。
ノーさん
制作部ディレクター。
業種を問わず多くのお客様を担当させていただきました。Webサイトのお悩み、活用方法などぜひご相談ください。
カン
制作部デザイナー。
制作部最年少の若手ですが、だからこそ生まれるアイデア・発想にご期待ください。