#はじめに
※Qiitaに#1~#3が無いのは仕様です
動画から見てくれていた方、お久しぶりです。
動画を見てくれる人も減ってきたし、自分も動画編集にかける時間がそれほど取れなくなってしまったので、記事で更新することになりました。
この記事はこれまでの動画を見てくれていた前提でやっているので、Qiitaから来てくれた方は以下の動画を見てくださると嬉しいです。(動画に対する指摘等あればyoutubeのコメント欄にてお願いします)
Unity講座#1 インストール
Unity講座#2 操作説明
Unity講座#3 プロジェクト作成
今回の講座では、前回作った処理を改良して、
・PCスペックに依存しない移動方法へ
・ボールが壁を抜けないような移動方法へ
・入力による操作に変更
といった内容をやっていこうと思います。
#開発環境
Unity 2019.4.5f1
Windows10 pro
#PCスペックに依存しない処理へ
Update関数は1フレームに一回呼ばれると説明しました。
1秒間にこの関数が呼ばれる回数は、実はPCの性能によって大きく変わってきます。
ゲームでは、1秒間で何枚も画像が切り替わっています。
要するにパラパラ漫画のようなものですね。
この切り替わり枚数が多いとなめらかに動いているように見えます。(逆に枚数が少ないと動作が飛び飛びに見えてしまいます)
Gameタブにある、Statsのボタンを押してみましょう。
GraphicsのところにあるFPS(Frame Per Second)が、この一秒間に何枚画像を更新できるかの情報になっています。
自分の環境では、FPSが1764になっていますね。つまり、
transform.position += new Vector3(0,0,1.0f);
が1秒間に1800回近くも行われていたため、移動速度がめちゃくちゃ早かったわけです。
秒速1800メートルとかマッハ5くらいになるのかな?
前回の最後で0.02fに直して、やっと見える速度になったのはこれが原因です。
ゲームが完成したあと、プレイする人々のPCスペックはバラバラです。
自分のPCの環境ではスムーズに動く移動速度にしたところで、自分よりハイスペックな人にとっては移動が早すぎるし、低スペックな人にとっては移動が遅すぎることになります。
そうならないようにUnity側で対処方法が用意されています。
##Time.deltaTime
公式リファレンス(タイムとフレームレートの管理)
このリンクにほぼほぼここで書いている説明が書かれています。
Time.deltaTimeというプロパティを使うことで、どのようなPCでも一定の速度で移動させることができます。
このプロパティの値は前のフレームから今のフレームまでの経過時間です。
大体FPSの逆数だと考えてもらえばわかりやすいですかね。
詳しくはこのリンクを見てもらうとわかりやすいと思います。
とりあえず、速度にかける分には問題ありません。
Time.deltaTimeが返すのは秒数なので、単位にs(秒)をかけることで、左辺と右辺の単位が揃うように使わなければいけません。
さて、いきなりこんなややこしい事言われてもわからん、となるでしょうし実際にどのように修正すればいいのかについてやっていきましょう。
public class PlayerMove : MonoBehaviour
{
[SerializeField]private float moveSpeed = 0.02f;
// Start is called before the first frame update
//このオブジェクトが作られたときに行われる処理
void Start()
{
}
// Update is called once per frame
//毎フレーム行われる処理
void Update()
{
transform.position += new Vector3(0,0,1.0f) * moveSpeed;
}
}
以上の内容は前回の処理内容をmoveSpeedの大きさを変更することで、移動速度を変更できるようにしただけのものです。
[SerializeField]private float moveSpeed = 0.02f;
ここは、float型の変数moveSpeedを宣言しています。
[SerializeField]の部分で、インスペクタ上に表示するようにしています。
publicで宣言してもインスペクタ上に表示できるようになりますが、前回解説したとおり、外部のクラスから数値をいじれてしまうようになるので、privateにした上で、インスペクタから変更できるようにしています。
一旦この状態でプレイしてみましょう。前回の動画の最後と同じ動きをしていれば問題ないです。
(フレームレートによって移動速度は違うと思います)
さて、このように、Playerのインスペクタ上にMoveSpeedが出現していますよね?
この大きさを変更してみましょう。
例えば0.02f→1.0fにしてみると、50倍の速度になります。(プレイしてみましょう)
注意が必要なのが、スクリプト上で初期化している数値よりも、インスペクタ上で指定している数値のほうが優先されることです。
スクリプト上でいくら変更しても速度は変わらないので気をつけましょう。
さて、Time.deltaTimeを使ってみましょう。
public class PlayerMove : MonoBehaviour
{
[SerializeField]private float moveSpeed = 0.02f;
// Start is called before the first frame update
//このオブジェクトが作られたときに行われる処理
void Start()
{
}
// Update is called once per frame
//毎フレーム行われる処理
void Update()
{
transform.position += new Vector3(0, 0, 1.0f) * moveSpeed * Time.deltaTime;
}
}
正面方向に 移動速度 1フレームの時間
単位(m) (方向ベクトル) 単位(m/s) 単位(s)
transform.position += new Vector3(0,0,1.0f) * moveSpeed * Time.deltaTime;
上記の通り、左辺と右辺の単位が揃っています。
Time.deltaTimeを使うときはとりあえず単位を書き出すようにしてみましょう。
この状態でプレイしてみると、一秒に0.02mというすごく遅い速度で動くようになります。
どのPCでも同じ速度で動くようになったので、こうなれば速度を小さくする必要はありません。
今回はとりあえずmoveSpeedを3にしておきましょう。
#壁を抜けない移動
前回軽く解説しましたが、transform.positionを変更する移動方法はワープです。
つまり、物理的に移動してないということは、物理エンジンによる衝突判定などがうまく働かないということですね。
ということで物理演算に必要なコンポーネント、Rigidbodyについて話していきます。
##Rigidbodyとは
Rigidbodyは、物理演算を行うためのコンポーネントです。
オブジェクトを作成したときにはColliderが自動的に追加されますが、これとはまた違うものです。
簡単に言ってしまうとColliderは接触判定を行うだけで、物理演算をして接触して跳ね返って...のような処理はしてくれません。
IsTriggerをTrueにするとイベントマスを踏んで処理を行う、といったようなときに使えます。
さて、Rigidbodyを使うことで、物理的に動かし、物理演算をしてもらうことができます。
ということでまずは前回作った処理を変更していきましょう。
上記のようにPlayerにRigidbodyを足してみましょう。
そうすると壁にぶつかって止まりますね。
RigidbodyとColliderの使い分けですが、当たり判定は欲しいが移動はしないものにはColliderを。
両方欲しいときにはRigidbodyを入れるといいでしょう。
まあこのまとめ方もかなり細かい説明を省いているので、それぞれUnity公式のリファレンスを確認するといいでしょう。
Rigidbody
Collider
#入力による移動
さて、上記の状態だと壁にぶつかったあと、壁に向かって移動し続けて、横に移動したりしますね。
現段階では球は前に進むことしかできないので、自分で動かせるようにしてみましょう。
入力を受け取る際にはUnityEngine.Inputのメソッド群を使います。
せっかくなので、キーバインドを自由にする方法で行います。
スマブラやマイクラなどをやっている方だとよく分かると思うのですが、できるならばボタンの割り当てを自分の好きにいじることができる方がいいですよね?
「Fキーでメニュー画面表示」のように実装することもできますが、拡張性が低いです。
マルチプラットフォームにも対応しているゲームエンジンなので、いちいちデバイスに合わせてif文で分岐させるなんて言うのは無理がありますよね。
つまり、「Menuボタンが押されたらメニュー画面表示」、「MenuボタンにFキーを割り当て」といった二段構えで設定することで、実装を楽にすることができます。
マルチプラットフォームの例で言うと、PS4では「MenuボタンにOptionボタンを割り当て」、Switchでは「Menuボタンに+ボタンを割り当て」という風にすればよくなり、プログラム自体を書き換える必要はなくなります。
左上にある、Edit→Project Settings→Input Managerを見てみましょう。
今回の入力で使うのは「Horizontal」と「Vertical」なので、とりあえずはここだけ見てみましょう。
ここでよく見てほしいのはNegative/Positive ButtonとAlt Negative/Positive Buttonです。
前者には左/右十字キーが割り当てられていて、後者にはa/dキーが割り当てられています。
つまり十字キーとWASDは同じ入力を意味するようにされています。
一番上のSizeの値を変えることで新しい入力を作ることができるので、自分でゲームを作った際に新しくボタンの役割を追加したい場合はぜひ活用してください。
さて、それでは実際の使い方を確認してみましょう。
public class PlayerMove : MonoBehaviour
{
[SerializeField]private float moveSpeed = 3f;
// Start is called before the first frame update
//このオブジェクトが作られたときに行われる処理
void Start()
{
}
// Update is called once per frame
//毎フレーム行われる処理
void Update()
{
float hor = Input.GetAxis("Horizontal");
float ver = Input.GetAxis("Vertical");
Vector3 moveDir = new Vector3(hor, 0, ver);
transform.position += moveDir * moveSpeed * Time.deltaTime;
}
}
Input.GetAxis(<入力ソース名>);
は-1~1の値をスティックの倒し具合によって返します。
positive方向の入力を+、negative方向の入力を-と考えて返します。
コントローラーにスティックがあれば小数も扱えますが、PCのキーボードでは半押し不可能なので、
・-1(negative buttonを押している)
・1(positive buttonを押している)
・0(どちらのボタンも押していない)
以上のどれかになります。
入力ソース名には、先程Input Managerで確認した名前を入れましょう。
String型で入れないとエラー。Input Managerに存在しない名前を入れてもエラーになるのでタイプミスには注意しましょう。
横方向の入力(Horizontal)がx軸、縦方向の入力(Vertical)がz軸なので、その通りにmoveDirとして宣言してあげると、無事入力による移動が実装できます。
さっき確認したとおり、十字キーでもWASDでも同じ動きをしてくれることを確認してみてください。
今回は何も実装しませんが、Button系列の解説もしておきます。
Input.GetButton(<入力ソース名>);
Input.GetButtonDown(<入力ソース名>);
上記の2つは、入力ソースを軸ではなくボタンとして受け取ります。
2つの違いは何かというと、ButtonDownは押したフレームのみ動作し、Buttonは押している間ずっと動作します。
これだけじゃわかりにくいかと思うので実験してみましょう。
if (Input.GetButtonDown("Jump"))
{
Debug.Log("Jump Button Pressed");
}
Update関数の最後尾にこの内容を付け足しましょう。
そして、ゲームをプレイしてスペースキーを押してみましょう。
Debug.Logはとても便利な関数です。
このようにConsoleに引数で渡した文字を表示させることができます。
変数の中身を表示させて正しい処理が行われているか確認したり、プロパティの中身を確認したりできます。
GetButtonDownなので、この場合は押して離すまでに1回しかログが出されません。
上記のプログラムをGetButton("Jump")に変更してみましょう。
すると、一瞬しか押していなくても複数回ログが出されます。
これは押されているフレームの間ずっとDebug.Logが実行されるためです。
ゲームで実装したい内容によっては使い分ける必要があるので、知っておきましょう。
また、サラッと説明してしまいましたが、Debug.Logは本当に重要なので覚えておきましょう。
#最後に
これまで4回に渡ってUnityの解説をしてきましたが、いかがでしたでしょうか?
これで最低限は動かせるようになったのではないでしょうか。
とはいえ公式リファレンスを読めば基本的にはどの機能の使い方もわかるようになっています。
これからも、新しい機能を使ってみるときにはまず公式リファレンスを確認する癖をつけてください。
(そうは言うものの、いまいち画像が足りなかったり、英語しかなかったり、和訳がガバガバだったりと、公式だけじゃわからないときもしばしば...)
言ってしまうと、Unityでのゲーム制作は
Unity <やりたいこと>
でgoogle検索をすれば大抵は解決できてしまいます。
ただそれはソースコードをそのままコピペして解決してるだけです。
自分の講座では、なるべく何をしているかを説明していますが、説明を書いていない個人ブログなども検索に出てくることがあります。
みなさんには、なんで動いているのかよくわからないコードをコピペするだけの人にはなってほしくありません。
コピペで解決するのが悪いのではなく、動作内容を理解できていないのにコピペをして作ってしまうのをやめて欲しいということです。
(とはいえ物理演算を理解するために、物理と数学の勉強しろとは言いません)
Unityの関数の処理内容の解説をしているQiita記事などもあるので、なるべくたくさんの人の解説を見て、できるだけ処理内容を理解した上で作ってください。
当然ながらこの記事についても同様です。
なるべく色々調べて正確性を上げていますが、間違いがあるかもしれません。
この記事でわからなかったことは他の人の記事やサイトなどを確認してみてください。
#参考
https://bardaxel.jp/archives/15
サークル公式Twitter
https://twitter.com/chuojoken?s=09