C++OpenSiv3D入門講座 Vol. 01 クラスの基本
今回はC++でのHello World、クラスの基本的な使い方について学ぶ。 演習では、実際にSiv3Dを用いてPlayer,Enemyを生成し、動かす。
Hello World!
C++流のHello World! C++ではstd::coutが用意されており、それを使って出力ができる。 std:: とは、「stdという名前空間にある」という意味。
#include <iostream> int main(){ std::cout << "Hello World!!" << std::endl; return 0; }
出力
Cのprintf関数では%dや%sなどを使って型を指定する必要があったが、std::coutでは型を指定しなくても自動的に型を判定・表示してくれる。
#include <iostream> int main(){ int a = 12; double b = 3.14; char str[100] = "abcde"; std::cout << a << " " << b << " " << str << std::endl; return 0; }
入力
std::cinを使うと入力されたデータを変数に入れられる。
#include <iostream> int main(){ int input; std::cout << "年齢を入力してください" << std::endl; std::cin >> input;//inputに入力された数値を代入 std::cout << "あなたは"<< input << "歳なんですね。" << std::endl; return 0; }
クラスの基本的な書き方
クラスは以下のように定義でき、インスタンス(実体)化して使う。
クラスの要素には.
(コンマ)を使ってアクセスできる。
クラスは変数を要素に持つことができ、メンバ変数という。
#include <iostream> class MyClass{ public: int a; int b; }; int main(){ MyClass obj; // インスタンス化 obj.a = 10; obj.b = 20; std::cout << obj.a << "," << obj.b << std::endl; return 0; }
二次元上の座標や、2次元ベクトルを表すクラス、Vector2を作った。
クラスの要素には.
(コンマ)を使ってアクセスできる。
クラスは関数を要素に持つことができ、メンバ関数という。(下の例ではsetData関数がメンバ関数)
#include <iostream> class Vector2{ public: int x; int y; void setData(int _x, int _y){ x = _x; y = _y; } }; int main(){ Vector2 obj; obj.setData(13, 29); std::cout << obj.x << " " << obj.y << std::endl; return 0; }
コンストラクタ
さっきの例ではVector2クラスを作った後、setData関数をいちいち呼ばなければならない。 初期化をいちいち行うのは手間がかかるので、C++ではクラス生成時に呼び出されるコンストラクタというものを定義できる。
コンストラクタの書き方
クラスの名前(引数){ 初期化処理... }
Vector2クラスのコンストラクタ例
#include <iostream> class Vector2{ public: int x; int y; Vector2(int _x, int _y){ x = _x; y = _y; } }; int main(){ Vector2 obj{ 1, 2 }; //インスタンス化、コンストラクタの引数を{}内に指定 std::cout << obj.x << " " << obj.y << std::endl; return 0; }
デストラクタ
オブジェクトが破棄されるときにデストラクタが呼ばれる。
デストラクタの書き方
~クラスの名前(引数){ 初期化処理... }
デストラクタの例
#include <iostream> class MyClass{ public: ~MyClass(){ std::cout << "デストラクタが呼ばれました" << std::endl; } }; int main(){ std::cout << "メイン関数に入りました" << std::endl; MyClass obj; std::cout << "メイン関数を抜けました" << std::endl; return 0; }
メイン関数を抜ける時オブジェクトが破棄されるのでデストラクタが呼ばれる。
アクセス修飾子
publicやprivateをアクセス修飾子と言う。 publicの要素はクラス外からアクセスでき(公開)、 privateの要素はクラス外からアクセスできない(非公開)。 何も指定されていなければprivateメンバになる。 privateメンバ変数を用いることで、外から直接書き換えられたくないメンバ変数を書き換えることが出来ないようにできる。 privateは隠蔽、カプセル化などにおいて重要な機能だが、この講座では割愛する(基本的に全てpublicを使い、privateはあまり使用しない)。
#include <iostream> class MyClass { int a; public: int b; private: int c; }; int main(){ MyClass obj; //コメントを外すとコンパイルエラー //obj.a = 100; //obj.c = 100; obj.b = 100; return 0; }
以下のようにすると、クラスの外から値を参照できるが、書き換えることは出来ないようにできる。
#include <iostream> class MyClass { private: int data; public: MyClass(){ data = 3; } int getData(){ return data; } }; int main(){ MyClass obj; // コメントを外すとコンパイルエラー // obj.data = 100; std::cout << obj.getData() << std::endl; return 0; }
const変数
constをつけると値を変更不可能な変数を作ることができる。#defineの代わりに使って、定数を設定する時などに使える。
#include <iostream> int main(){ const int a = 100; //コメントを外すとコンパイルエラー //a = 200; std::cout << "aの値は:" << a << std::endl; return 0; }
まとめ
- std::coutで出力、std::cinで入力ができる。
- クラスの定義とインスタンス化、メンバ変数・メンバ関数へのアクセスの方法を学んだ。
- クラス生成時にはコンストラクタが呼び出され、オブジェクトが破棄されるときにデストラクタが呼び出される。
- publicの要素はクラス外からアクセスでき、privateの要素はクラス外からアクセスできない。
- constをつけると値を変更不可能な変数を作ることができる。
演習問題(コンソール)
- 以下のプログラムをstd::coutとstd::cinを使って書き直せ。
- 以下の様なクラスVector3を作った。
#include <iostream> int main() { int input; //入力用 printf("整数値を入力してください:"); scanf("%d", &input); printf("入力された値は、%dです。\n", input); return 0; }
class Vector3{ public: int x, y, z; };
演習問題(OpenSiv3D)
- 以下の様なPlayerクラスを作り、矢印キーで動くようにせよ。
class Player{ public: double x, y, speed; Player(){ x = 320.0; y = 240.0; speed = 50.0; } void update(){ //ここを実装 //矢印キーで移動 } //自機(円)を描画 void draw(){ Circle{ x, y, 30.0 }.draw(Color{ 0, 0, 255 }); } };
- Playerクラスの実体を作り、メインループ内でupdate関数とdraw関数を呼び出してPlayerクラスを動作させよ。
- 以下の様なEnemyクラスを作り、Playerクラスと同様に実体を作り動作を確認せよ。 今回作るEnemyはただ下に移動するだけでよい。
class Enemy{ public: double x, y, speed; //ここに座標と速さを指定できるようなコンストラクタを実装 void update(){ //ここを実装 //下方向に移動 } //敵(円)を描画 void draw(){ Circle{ x, y, 30.0 }.draw(Color{ 255, 0, 0 }); } };
ヒント
OpenSiv3Dでの処理は基本以下のような形になる
# include <Siv3D.hpp> void Main() { //毎フレーム使う変数の作成やゲーム開始前の処理 while (System::Update()) { // ここに書いた内容が毎フレーム実行される } }
-
- 欲しい処理をOpenSiv3DのDocumentで探してみよう。例えば、キーボード入力関係の関数は以下のページに記載されている。 チュートリアル 16 | キーボード入力|Siv3D リファレンス v0.6.2
- xやyにspeedを直接足すような処理では問題が発生する。Scene::DeltaTime()をうまく使ってみよう。(xやyは座標です。速さではなく距離を足してあげた方が良いので...?) よくある間違い - Siv3D
- while内でプレイヤーのインスタンスを作成すると、毎フレーム破棄されてしまう。
C++OpenSiv3D入門講座
はじめに
本資料は、C++初学者向け資料です。
C++とOpenSiv3Dで実際にシューティングゲームを作り、「C++の機能(クラス, vector, ポリモーフィズム)をゲームを作るにあたって実際にどう使うか何となく理解」することが目的になっています。
読者の想定
OpenSiv3Dの機能をフルで使わない面がありますが、ご了承下さい。
開発環境
OpenSiv3Dを導入することを考えて 開発に必要な環境、動作に必要な環境|Siv3D リファレンス v0.6.3 に適合する環境を構築しましょう。
WindowsであればVisual Studioでインストール時に「C++ によるデスクトップ開発」にチェックを入れましょう。(インストール済みの場合はVisual Studioバージョンの確認とVisual Studio Installerを起動し、インストール済み→変更から「C++ によるデスクトップ開発」にチェックがついているか確認しましょう。)
macOSであればXCodeをインストールしておきましょう。
OpenSiv3Dの導入
開発を始める(SDK のインストール)|Siv3D リファレンス v0.6.3 を参考に導入してください。
導入出来たら、上記の手順に従ってサンプルプログラムを動かすところまでやってみましょう!
各回の資料について
記事を随時追加していきます。記事が追加されていない分は予定であり、変更の可能性があります。
- クラスの基本
- ファイル分け・コンポジション
- vectorの基本
- 参照・クラスのポインタ
- 関数オーバーロード・range-based-for
- イテレータ・vector要素の削除
- remove_if・ラムダ式
- GameManagerクラス
- new・delete・スマートポインタ
- 基底・派生クラス・仮想関数・ポリモーフィズム
- 演習問題の解答
演習問題について
Siv3D.hppをインクルードしているプログラムはOpenSiv3Dを使ったアプリケーションを、そうでないプログラムはコンソールアプリケーション(文字だけ出てくる黒画面のやつ)で解くことを想定しています。
演習問題の回答は随時追加していきます。
謝辞
本資料は、やぎりさんの作成したC++Siv3D入門講座のOpenSiv3D版として作成されました。ありがとうございました。
CCS Unity入門記事 2020
こちらはCCS Advent Calendar 2020の23日目の記事です.
22日目の記事はこちら! VTuber たま〜に見ますが 良いですよねぇ(字余り) yoooomaruuuu.hatenablog.com
はじめに
今年はUnityを触っていることが多かったのでUnityについて書こうと思います.Unity勢が増えると嬉しいなあ...
Unityとは
Unityとはゲーム開発ツールです.PCからスマホ,ゲーム機,Web,VR/ARなど向けの開発に対応しています.2Dゲームも3Dゲームもこれで作れます!
読者の想定
C#に関する知識はなくてもOK
クラスベースのプログラミングをしたことがなくてもOK
とりあえず動かしてみるというよりは,応用が利きやすい内容を目指します.知っておくべきだと考えるUnityの仕様等のことを挟み周り道することがありますが,是非お付き合い下さい.
質問があればいつでも私に聞いて下さい!
この記事内でやること
世界を作る!
ものを置く!
もの(プレイヤー)を操作できるようにする!
です.本当は敵を出したりしたかったけど...時間切れ...
この状態から敵を追加したり,というアレンジはしやすい内容を目指して書いているので是非その先も作りこんでみて欲しいですね...需要があれば続きを書くかも...?
目次
- Unityのインストール
- Editor画面の紹介
- 世界を作る
- 世界にモノを置いていく
- モノを動かしてみる
- ステージづくり
- プレイヤーを動かす(Componentを作る)1
- クラスの話
- プレイヤーを動かす(Componentを作る)2
- さいごに
Unityのインストール
Unityをダウンロード
こちらからダウンロードしていきましょう.「Unity Hub をダウンロード」をクリック. インストールが終わったらUnity Hubを起動しましょう.
Unity Hubのセットアップ
Unity本体をインストールしていきます.
左サイドメニューの「インストール」→右上の「インストール」を押します.
今回は記事を書いている時に推奨リリースとなっていた「Unity 2019.4.16f1(LTS)」を選びました.特に理由が無ければ推奨版か正式版の最新で良いと思います.
インストールするモジュールを選択します.PC向けゲームを作るならそのままの設定で良いと思います.スマホや他のOS向けにビルドしたい場合は〇〇Build Supportを追加するべきですが,こちらは後からでも追加できます.
Dev toolsの「Microsoft Visual Studio Community」はそこそこ重いです.
軽いのが良いという人はこちらのチェックを外して「Visual Studio Code」+拡張機能を導入すると良いと思います.以下を参考に.
UnityでVisual Studio Codeを使用できるようにするまでの手順 - Qiita
「次へ」を押してしばらく待てばインストール完了です.
プロジェクトの作成
左サイドメニューの「プロジェクト」→「新規作成」を押します.
今回は3Dのゲームを作成しようと思うので「3D」を選択します.
「プロジェクト名」と「保存先」を好きなように決め,「作成」を押します.
Editor画面の紹介
プロジェクトの作成が終了するとEditor画面が表示されます.この画面について紹介していきます.
基本画面の紹介
Project
プロジェクトフォルダ内の情報です.デフォルトで「Assets」フォルダに「Scenes」フォルダが入っていることがわかります.
Hierarchy
この世界に存在しているオブジェクトが見れるところです. デフォルトで「Main Camera」と「Directional Light」がありますね.名前の通りカメラと照明です.
Game
ゲーム内でどのように見えているのか確認できる画面です.現在は「Main Camera」が映す映像がここに表示されるようになっています.
Scene
世界の中を自由に見る・編集することができる画面です.
デフォルトだとGameの隣のタブにあります.
ゲーム感覚でオブジェクトを移動したりできます(後述).操作は以下の通りです.
視点の移動:右クリックを押しながらマウスを動かす
前進,後退:マウスのホイール
左右に移動:マウスの真ん中のボタンを押しながらマウスを動かす
オブジェクトの選択:左クリック
Inspector
選択されているオブジェクトの詳細を表示してくれるところです.
試しにHierarchy上の「Main Camera」をクリックしてみましょう. Inspector上に座標などいろいろな情報が表示されたと思います.
Play/Pause
上部中央の再生ボタンを押すと作成中のゲームを動かすことができます.また,一時停止ボタンを押せばゲームを一時停止することもできます.
Editor画面のカスタマイズ
ウィンドウの表示/非表示,位置,レイアウト等は自由に弄れます.自分の使いやすいようカスタマイズしていくと良いでしょう. 絶対にやっておくべきカスタムが一つだけあるので紹介します.
実行時の画面の色の変更
Edit>Preferencesをクリック
Preferences>Color>Playmode tint の色を分かりやすい色に変えます.
変更後,Playボタンを押してゲームを動かしてみましょう.ゲームの実行時の画面が先ほど指定した色に変わります.これにより,編集時/ゲーム実行時の区別がつきやすくなります.
Q. 何故,編集時/ゲーム実行時の区別がつきやすくする必要がある?
A. ゲーム実行中に行った作業は(ほとんど)実行終了後に破棄されてしまう為.
ゲーム実行中だと気づかずにステージづくり等をしてしまうと...泣いてしまいます.
世界を作る
Unityでは「Scene」という世界の中にいろいろなオブジェクトを置いていきます.まずはこのSceneを作成します.
Assetsフォルダの整理
Projectウィンドウから作業していきます.基本的に使うのはAssetsフォルダ内です.
元々Assetsフォルダ内に「Scenes」というフォルダがありますが,削除します.
Assetsフォルダ直下は後々色々なものが置かれる可能性がある為,自分の作業用のフォルダを作ることをお勧めします.
Assetsフォルダのところで右クリック>Create>Folder 今回は自分の作業用のフォルダ名は「_GameFolder」としました.(_をつけたのは名前順にしたときに上の方に表示される為)
Sceneを複数作ることが今後あるかも知れないので,Scene保存用のフォルダを作ります.
先ほどと同様に_GameFolderのところで右クリックからフォルダを作成.名前は「Scenes」としました.
Sceneの作成
いよいよ世界(Scene)を作っていきます.
先ほど作成した「Scenes」フォルダのところで右クリック>Create>Scene
「Main」という名前にしました.
これにてSceneの作成は完了です.ダブルクリックして開いてみましょう.
(この際に現在開いているサンプルのSceneを保存するか聞かれる可能性がありますが,使わないので「Don't Save」でOKです.)
Hierarchyウィンドウの上部に「Main」と出ていれば正常に開けています.
世界にモノを置いていく
さて,「Main」という世界(Scene)が作成できました.ここにモノ(オブジェクト)を置いてみましょう.
オブジェクトを置いてみる
Hierarchyウィンドウで右クリック>3D Object>Cube
すると,HierarchyウィンドウにCubeが追加されました. HierarchyウィンドウのCubeをクリックして,Inspectorウィンドウから詳細を見てみましょう.
Transform欄では,現在のCubeのPosition(座標),Rotation(角度),Scale(大きさ)が見られます.
オブジェクトのPositionの(X,Y,Z)が(0,0,0)ではない位置に生成されていることがあります.このような場合にはTransformの右側の3つの点のマーク>Resetを押して位置をリセットしましょう.
オブジェクトを見る
Sceneウィンドウから先ほど作成したCubeを確認してみましょう.
見つからないor見にくい場合にはHierarchyウィンドウから見たいオブジェクト「Cube」をダブルクリックしましょう.自動でCubeの前まで画面が移動してくれます.
画像のように生成されたCubeが灰色の場合,照明が機能していない可能性があります.Lightingウィンドウから「Auto Generate」のチェックを外し,「Generate Lighting」を押しましょう(私が忘れていたためしばらく灰色のCubeのまま作業が進行します...)
オブジェクトの編集
右上の赤丸で囲んだところから編集モードを選べます.左から順に紹介していきます.
Hand Tool:視点の移動に使えます.
Move Tool:オブジェクトの移動ができます.
Rotate Tool:オブジェクトの回転ができます.
Scale Tool:オブジェクトの大きさを変更できます.
Rect Tool:こちらもオブジェクトの大きさを変更できます.Scale Toolとの違いは触ってみればわかります.試してみよう!
Move, Rotate or Scale selected objects:Move Tool + Rotate Tool + Scale Toolの欲張りセット となっています.
まずはオブジェクトの移動をしたいので,Move Toolを選択しましょう.
Move Toolを選択したら,Sceneウィンドウ上(又はHierarchyウィンドウ上)からCubeを選択しましょう.
出てきた矢印をクリックして動かすことでオブジェクトを移動できます.
色々試してみよう
その他のツールも使ってオブジェクトを回転させたり,サイズを変えたりしてみましょう.結構感覚的に操作できます.
また,InspectorウィンドウのTransformからも操作できます.Inspector上の変更したい値(テキストボックスの外)をクリックして左右に動かすことでも値を変更できます
モノを動かしてみる
UnityではRigidbodyという物理エンジンを使うことが出来ます.
が,物理エンジンを使う前にまずはUnityのGameObjectの話をしていこうと思います.
GameObjectの話
先ほどSceneを作ってCubeを置いた通り,Sceneを(主に)色々なGameObjectから構成していきます.元々あった「Main Camera」や「Directional Light」,作成した「Cube」も全てGameObjectです.
GameObjectは様々なComponentから構成されています.GameObjectがどんなComponentを持っているかによって,性質が変わってきます.
例として先ほど作成したCubeをInspectorウィンドウから見てみましょう.「Transform」「Mesh Filter」「Mesh Renderer」「Box Collider」から構成されています.それぞれのComponentを軽く紹介します.
Transform:オブジェクトの座標,角度,大きさ等を保存
Mesh Filter:メッシュ(オブジェクトの形の情報)をMesh Rendererに渡す
Mesh Renderer:Mesh Filterに渡されたメッシュを描写する
Box Collider:直方体の形の当たり判定
つまり,Cubeは位置・角度・大きさの情報,オブジェクトの描写機能,当たり判定機能から構成されています.
これらのComponentを消したり,他のComponentをつけたりすることで求める性質を持つGameObjectへと変えていくことがUnityでのゲーム制作の根幹となります.
物理エンジンのComponentをつける
さて,このCubeが動くよう,物理エンジンのComponentを付けてみます.
CubeのInspector上でAddComponentを押す
「Rigidbody」と検索をかけ,出てきたものをクリック.
こうしてCubeに物理エンジン(Rigidbody)をつけることが出来ました.ここから質量や空気抵抗等のパラメータを変えられます.詳しい説明は割愛します.
実行してみる
上部中央の再生ボタンを押して,動作を確認してみましょう.
Cubeに重力が働き,落ちていきました.これだと寂しいのでもう一つCubeを作成して引き伸ばして傾けて,坂でも作ってあげましょう. 坂は作成したCubeの位置と角度と大きさを弄ってあげるだけでOKです,以下の2点の理由により.
坂自体は動かない為物理エンジンが不要
衝突判定を行うための「Box Collider」が元々ついている(衝突判定を行うにはぶつかるオブジェクトの少なくとも一方がRigidbodyコンポーネントを持っている必要があるので注意,今回は動くCubeが持っているのでOK)
良い感じに転がっていきましたね!神ゲームです.
終
ステージづくり
これから地面の上をプレイヤー(球)が動く,という動作を目指します.
まずは必要なオブジェクトを配置していきましょう.先ほどまで使用していたCubeは要らないので消してしまいます.
プレイヤーの配置
今回は球を配置してそれをプレイヤーとしましょう.Cubeを設置した時と同様に
Hierarchyウィンドウで右クリック>3D Object>Shpereを選択します.
Inspector上で名前を変更できます.分かりやすいように名前をつけておきましょう.また,今回はプレイヤーを物理エンジンを使って動かしたい為,「Rigidbody」コンポーネントも追加します.
ステージの配置
ステージはいくつかのオブジェクト(地面,壁など)が組み合わさって構成される為,管理しやすいようにまとめておきます.
Hierarchyウィンドウで右クリック>Create Emptyを選択.空のGameObjectが作成されます.これをフォルダのように扱います.Inspector上から名前をStageとしておきました.
作成したStageで右クリック>3D Object>CubeとすることでStageの子要素にCubeを作成することができます.
Stageの子要素に壁と床をCubeで作成してみましょう.Sceneウィンドウからゲーム感覚で配置していくことができると思います.
Gameウィンドウでもステージが一覧できるようMain Cameraの位置も調整します.
ひとまずステージ完成.現在のHierarchyは以下のようになっています.
Materialをつける
全てのオブジェクトが白だと見にくいので色をつけていきます.
Unityでは(見た目上の)材質を作成し,それをオブジェクト(のRenderer(描写用コンポーネント))に割り当てることでオブジェクトの見た目を変化させます.
Materialの作成
ProjectウィンドウからAssets/_GameFolder内に「Materials」という名前でフォルダを作成します.
作成したフォルダの中で右クリック>Create>Material
作成されたら好きな名前をつけましょう.
Materialの色の変更
作成したMaterialをクリックすると,InspectorにMaterialの詳細が表示されます.
Main MapsのAlbedoの色を好きな色に変更します.
他にもShaderを変えたりMetallic属性を変えたりしてガラスっぽい素材や金属感のある素材を作ることも出来ますが,今回は説明を割愛します.
Materialの割り当て
HierarchyウィンドウからPlayerのGameObjectを選択,InspectorからMeshRendererコンポーネント(オブジェクト描写用のコンポーネント)を見てみましょう.
Materialsという項目があります.こちらのElement0の欄に先ほど作成したMaterialをドラック&ドロップします.
Playerオブジェクトの色が変わったことを確認しましょう.
同様にしてステージのオブジェクト用にもMaterialを作成し,色をつけて分かりやすくしました.ステージづくりはひとまずこれで終了です.
プレイヤーを動かす(Componentを作る)1
先ほど設置したPlayerが操作した通りに動くようにするには,「オブジェクトが自分の押したキーの方向の速度を持つようになるComponent」を追加すれば良いのです.しかしそんな都合の良いComponentが存在するはずもなく...(?)
みなさんお待ちかねプログラミングの時間です.自分でComponentを作りましょう!
Componentのソースファイルを作成
ProjectウィンドウからAssets/_GameFolder内に「Scripts」という名前でフォルダを作成します.
作成したフォルダの中で右クリック>Create>C# Script
名前を「PlayerMover」としました.一度Scriptファイルを作成したら極力ファイル名は変えない方が良いと思います.
もしファイル生成時に名前をつけ損ねて「NewBehaviourScript」という名前になってしまった場合,一度削除をして再度ファイルを作り直すことをお勧めします.
作成したファイルを開いてみる
作成したファイルをダブルクリックするとテキストエディタが起動します.デフォルトで以下のような内容が記述されていると思います.
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerMover : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } }
このファイル内の以下の箇所でこのコンポーネントがどういうものなのかを定義していきます.
public class PlayerMover : MonoBehaviour { //省略 }
定義していく際にクラスというものを使用します.クラスについては後述.
動かしてみる
これから作る予定のコンポーネントの完成品が以下のものになります.説明の前に,どのような動作になるか先に一度動かしてみます.
PlayerMover.csに以下のように打ち込んで保存.
using UnityEngine; public class PlayerMover : MonoBehaviour { [SerializeField] float speed; Rigidbody rb; Vector3 velocity; void Awake() { rb = GetComponent<Rigidbody>(); //rbの参照先をRigidbodyの実体に velocity = new Vector3(0, 0, 0); //速度の初期値を0ベクトルに } void Update() { //キーの入力から速度を決定 float dx = Input.GetAxisRaw("Horizontal"); //横方向のキー入力を取得 float dy = Input.GetAxisRaw("Vertical"); //縦方向のキー入力を取得 Vector3 direction = new Vector3(dx, 0, dy); //方向を決定 velocity = speed * direction.normalized; //速度ベクトルを決定 } void FixedUpdate() { rb.velocity = velocity; //物体の速度を書き換える } }
Unityの画面に戻り,HierarchyウィンドウからPlayerを選択,PlayerのInspectorからAddComponent>PlayerMover
PlayerMoverコンポーネントが追加されたら,Speedを好きな値に設定しましょう.
再生ボタンを押して実行してみます.WASDや矢印キーでプレイヤーを動かせました.
さて,ここからはこのスクリプトの作成方法を説明していきます.
クラスの話
一度Unityから離れてクラスについて説明します.一応知らなくても動くものは作れるので,とりあえず動かしたい!という人はスキップしてもOKです. 予期せぬ動作を防ぐために知っておくべきだと思う点について書きます.
以下のソースコードはUnity上でも動きますが動作の確認が面倒な為,眺めて貰えればOKです.
クラスとは
クラスとは自分で作る新しい型です(int型やfloat型とか言うあの型).既存の型の変数や関数を組み合わせて作ります.
クラスを使うには,次の2段階のステップを踏みます.
クラスを定義する(どのような型なのかを書く)
作った型(クラス)をインスタンス化(実体化)する
例として,Human型(クラス)を作り,インスタンス化(実体化)してtaroと名前をつけ,使用してみます.
クラスの定義
身長と体重のデータと,BMIを取得する機能からなる型(クラス)を定義すると以下のようになります.
public class Human { public float Height; //身長 public float Weight; //体重 //BMIを取得する関数 public float GetBMI(){ return Weight / ( Height * Height ); } }
Human型をfloat型の変数のHeightとWeight,関数GetBMI()から構成しました.クラスの中に含まれる変数や関数をそれぞれ「メンバ変数」,「メンバ関数」と言います.
インスタンス化,使用
クラスの定義では型がどういうものなのかを決めただけなので,使用するにはその型の実体(インスタンス)を作る必要があります.
先ほど人間というのがどういう型なのかを決めたので,使用するために太郎を作ります.
「new 型名()」によってHuman型の実体(インスタンス)を生成し,taroという名前に結び付けます.
Human taro = new Human();
クラスの中のメンバ変数には「.(ドット)」でアクセスできます.「taro.Height」と書いたらtaroの中のHeightという意味になります. 太郎の身長と体重を代入してみます.
taro.Height = 2.26; //身長を代入 taro.Weight = 105.3; //体重を代入
クラスの中のメンバ関数も同様に「.(ドット)」でアクセスできます.「taro.GetBMI()」と書けばtaroの中のGetBMI関数を呼ぶことができます.
float bmi = taro.GetBMI(); //BMIを計算してbmiという変数に入れる
Componentのクラスのインスタンス化
Humanクラスでは「new 型名()」によって新しい実体を作成していましたが,Unity上でのComponentのクラス(MonoBehaviourを継承するクラス(後述))は「new 型名()」によって実体を作成することはできません.
その代わり,GameObjectにComponentとして追加される際に実体化されます.Inspector上でAdd Componentをする際にクラスそのものをつけているのではなく,クラスを基にインスタンス化したものをつけています.
アクセス修飾子
「public float Height;」のように変数や関数の前,クラスの定義部分に書いていた「public」をアクセス修飾子と言います.
全てのメンバ変数・メンバ関数にアクセス出来てしまうと困ることがあるので,このアクセス修飾子によって利用できる範囲を制御します.
代表的なものを2つ紹介します.
public:どこからでもアクセス可能(クラスの外,他のファイルなどからでもOK)
private:クラスの内部でのみアクセス可能
例としてこんなクラスを作ってみます
public class Test{ int a; //アクセス修飾子を省略するとprivate private int b; //privateメンバ public int c; //pulicメンバ private int Func1(){ return b + c; //クラス内なのでbが使用可能 } public int Func2(){ int num = c + Func1(); //クラス内なのでfunc1が使用可能 return num; } public void SetB(int _b){ b = _b; //クラス内なのでbは使用可能 } public int GetB(){ return b; //クラス内なのでbは使用可能 } }
アクセス修飾子を省略するとprivate扱いになります.クラス内ではpublic,private両方の変数・関数の使用が可能です.
インスタンス化して使ってみます.どのメンバにアクセスできるのか,考えてみましょう.
int num; Test test = new Test(); test.a = 10; //NG.aはprivate test.b = 5; //NG.bはprivate test.c = 3; //OK!cはpublicメンバ num = test.Func1(); //NG.Func1はprivateメンバ num = test.Func2(); //OK!Func2はpublicメンバ tets.SetB(226); //OK! SetB関数の中でtest.bに226が代入される num = test.GetB(); //OK! ↑でtest.bが226になっているためnumは226になる
動作についてはソースコード内にコメントで書き込んだ通りです.クラスの外からはprivateメンバにはアクセス出来ず,publicメンバにだけアクセスできます.
メンバがどこからでも使用可能という状態は危険であるため,出来る限りのメンバはprivateにしておくのが良いと思います.
値型と参照型
C#の型は大きく分けて2つの型に分けられます.
値型:変数に直接値が格納されるもの.数値型(int, float等),bool型,enum型,構造体など.
参照型:変数が持っているのは参照情報(実体が存在する場所の情報)だけのもの.クラス,配列,文字列型など.
使用している型がどちらのタイプかにより,振る舞いが変わってきます.特に代入時(〇〇 = △△;)の振る舞いが大きく変わります.以下に例を示します.
まずは値型であるint型で以下のようなコードを考えます.
int a, b; a = 10; b = a; b = 53;
値型であるintにおいてはaとbはそれぞれ異なる実体を持ちます.また,「b=a;」とした時はaが持つ実体の中身をbにコピーするという動作をします.
aとbは異なる実体を持つため,bを書き換えてもaが書き換わることはありません.
続いて,参照型であるHumanクラスで以下のようなコードを考えます.
Human a, b; a = new Human(); a.Height = 2.26; b = a; b.Height = 1.53;
参照型の変数はそのままでは実体を持ちません.インスタンス化された実体への参照を持たせることができます.「b=a;」とした時にはbがaの指している実体を指すようになります(aの参照情報をbにコピーする).よって,aとbが同じ実体を指しているため,bの内容を変更するとaも変更されます.
値型と参照型を区別せずに使っていると変数の内容の意図しない変更を行ってしまう可能性があります.代入や関数との受け渡しには特に注意しましょう.
プレイヤーを動かす(Componentを作る)2
Unityの話に戻ります.プレイヤーを動かすComponentのクラスを作成していきます.
MonoBehaviourの話
まず,Componentを作るためのクラスについて説明します.
先ほど作成したソースファイル内で,クラスを定義している部分
public class PlayerMover : MonoBehaviour { //省略 }
の「 : MonoBehaviour」はMonoBehaviourクラスの派生したもの,という意味です.つまり,ここでは「MonoBehaviour」クラスの機能を引き継いだクラスである「PlayerMover」クラスを定義しようとしています.(派生したクラスの作成をクラスの継承と言います.クラスの継承について詳しくは割愛します.)
MonoBehaviourとは?
Componentを作るための素だと思ってもらえば大丈夫です.
MonoBehaviour(を継承したクラス)はデフォルトで用意されたイベント関数(Unity側で勝手に呼んでくれる関数)が沢山あります.その中のいくつかを紹介します.
イベント関数の紹介
void Awake():ゲームが起動された後,オブジェクトが生成された瞬間一度呼ばれます.
void Start():ゲームが起動された後,最初のフレームの前に一度呼ばれます.
void Update():毎フレーム呼ばれます.入力の処理等はここに書くと良い.
void FixedUpdate():固定時間につき一回呼ばれます.物理挙動関連の処理はここに書くと良い.
これらのフローチャートは以下のような感じです.
(詳細なフローチャートを見たい方は→イベント関数の実行順序 - Unity マニュアル)
これらの関数を記述しておけばUnity側で勝手に呼び出してくれます.この関数の中に必要な処理を書いていくことでコンポーネントの性質を決めていきます.
他にも衝突が起こった時に呼ばれるイベント関数等様々なものがありますので,必要に応じてググりましょう.
スクリプトの作成
行いたい動作
ここで一度行いたい動作を整理します.必要な処理は以下のようになります.
入力した方向を取得する
入力した方向で,指定した大きさの速度ベクトルを作成する
プレイヤーの物理エンジンの速度を書き換える
イベント関数を書く
PlayerMoverクラスの中に必要なイベント関数を作成します.初期化用のAwakeと毎フレーム呼ばれるUpdate,固定時間毎に呼ばれるFixedUpdateを作成しました.
using UnityEngine; public class PlayerMover : MonoBehaviour { void Awake() { } void Update() { } void FixedUpdate() { } }
メンバ変数の作成
今回必要なメンバ変数は
です.以下のように作成します.
using UnityEngine; public class PlayerMover : MonoBehaviour { [SerializeField] float speed; //移動スピード Rigidbody rb; //Rigidbodyコンポーネントの参照を保存する変数 Vector3 velocity; //速度ベクトル //省略 }
変数の前に"[SerializeField]"とつけることでUnityの画面上から値や参照を変えられるようになります.
speedを[SerializeField]指定しました.
[SerializeField] float speed;
物理エンジンのRigidbodyコンポーネントの型はRigidbodyです.Rigidbody型で変数を作成しました.
Rigidbody rb;
Vector3はUnityにある3次元ベクトルの型です.これを用いて算出した速度を保存しておくためのベクトルvelocityを作成しました.
Vector3 velocity;
Awakeの中身
続いてAwakeの中身です.Awakeでは今回はメンバ変数の初期化を行います.
speedはUnityの画面上で入力した値で初期化されるため,Awake内で初期化する必要があるのはrbとvelocityです. 以下のように初期化します.
using UnityEngine; public class PlayerMover : MonoBehaviour { [SerializeField] float speed; //移動スピード Rigidbody rb; //Rigidbodyコンポーネントの参照を保存する変数 Vector3 velocity; //速度ベクトル void Awake() { rb = GetComponent<Rigidbody>(); //rbの参照先をRigidbodyの実体に velocity = new Vector3(0, 0, 0); //速度の初期値を0ベクトルに } //省略 }
細かい箇所について説明していきます.
rb = GetComponent<Rigidbody>();
「GetComponent<型名>()」で自身と同じGameObjectに付いているComponentの参照を取得できます.GetComponent関数はそこそこ重いため,沢山呼ばれるようなUpdate関数やFixedUpdate関数の中で呼ばないようにすべきです.その為,初期化の段階で呼んで予め参照を取得しておきます.
今回は自身と同じGameObjectに付いているRigidbodyの参照を取得し,rbに入れました.
velocity = new Vector3(0, 0, 0);
初期の速度を0ベクトルにします.「new Vector3()」で新しいベクトルを作成しvelocityにコピー.この際,「new Vector3(0, 0, 0)」とすることでx成分,y成分,z成分それぞれに0を入れることができます.
(Vector3は値型(構造体)であり,変数作成時のデフォルトの値が(0, 0, 0)なのでこの処理は無くても動きます.)
Updateの中身
続いてUpdateの中身です.ここで行うべきことは入力の処理等であるので,
入力した方向を取得する
入力した方向で,指定した大きさの速度ベクトルを作成する
を記述します.以下のようになります.
using UnityEngine; public class PlayerMover : MonoBehaviour { //省略 void Update() { //キーの入力から速度を決定 float dx = Input.GetAxisRaw("Horizontal"); //横方向のキー入力を取得 float dy = Input.GetAxisRaw("Vertical"); //縦方向のキー入力を取得 Vector3 direction = new Vector3(dx, 0, dy); //方向を決定 velocity = speed * direction.normalized; //速度ベクトルを決定 } //省略 }
それぞれ細かい箇所について説明していきます.
//キーの入力から速度を決定 float dx = Input.GetAxisRaw("Horizontal"); //横方向のキー入力を取得 float dy = Input.GetAxisRaw("Vertical"); //縦方向のキー入力を取得
Input.GetAxisRaw();で方向キーの入力を取得,横方向と縦方向それぞれの成分をdxとdyに入れています.
Vector3 direction = new Vector3(dx, 0, dy); //方向を決定
取得した方向キーのデータから進行方向のベクトルを作成するため,「new Vector3()」で新しいベクトルを作成.この際,「new Vector3(dx, 0, dy)」とすることでx成分にdx,z成分にdyを入れることができます.
dyをz成分に入れた理由はUnityの座標軸が以下のようになっている為です.
x:左右方向,右が正
y:上下方向,上が正
z:奥手前方向,奥が正
velocity = speed * direction.normalized; //速度ベクトルを決定
(Vector3の変数).normalizedで長さを1にしたベクトルが取得できます.「direction.normalized」(長さを1にした方向ベクトル)にspeedを掛けることで,入力された向きで大きさがspeedの速度ベクトルを算出しています.
FixedUpdateの中身
最後にFixedUpdateです.ここで行うべきことは物理挙動関連の処理であるので,
- 速度の書き換え
を行います.
using UnityEngine; public class PlayerMover : MonoBehaviour { //省略 void FixedUpdate() { rb.velocity = velocity; //物体の速度を書き換える } }
FixedUpdate内部では以下のようにして速度を書き換えています.
Awakeで取得しておいたRigidbodyの参照(rb)から速度にアクセス(rb.velocity).
速度をUpdateで算出しておいたvelocityに書き換える(rb.velocity = velocity)
以上で「PlayerMover」の完成です.
AwakeとStartの使い分け
今回のPlayerMoverでは初期化にAwakeのみを使いましたが,先ほど紹介した通り初期化のための関数は2つあります(AwakeとStart).それぞれ以下のような役割で使い分けると良いと思います.
void Awake():メンバ変数の初期化,他のオブジェクトの参照の取得等
void Start():他のオブジェクトの値の取得等
このような使い分けを行うことで,以下の問題を回避できます.
Unityでは,以下のような順番でイベント関数が呼ばれて行きます.
全てのオブジェクトのAwake
全てのオブジェクトのStart
全てのオブジェクトのFixedUpdate
全てのオブジェクトのUpdate
...
「どの種類のイベント関数から呼ばれるか」という順番は固定ですが,「どのオブジェクトから呼ばれるかは未定」です.
例えば,以下の画像のような場合,AとBのどちらのStartが先に呼ばれるかによって結果が変わってしまいます.
B内のStartにおいて,A内のNumを値を使用します.この時,
Aが先の場合,Num = 10として処理が進む
Bが先の場合,A内のStartが呼ばれていない為,Num = 0(デフォルト値)として処理が進む
このような事態を避けるため,先に呼ばれるAwakeでメンバ変数の初期化や他のオブジェクトの参照の取得を行い,他のオブジェクトの値の取得は後に呼ばれるStartで行うべきだと考えます.
今回のPlayerMoverでは必要な処理がメンバ変数の初期化と他のオブジェクトの参照の取得のみであったため,Startが必要なかったという訳です.以上でPlayerMoveコンポーネントのスクリプトの説明を終わります.
さいごに
もし手元にUnityを開いてここまでの作業をして貰えていたら嬉しすぎておじさん泣いちゃいます.Unityの便利な機能の紹介があまりできなかったのが心残りです.是非使いながら色々調べてみて欲しい.
ゲームを作れるようになりたいなら実践が一番だと思います.ドンドン作ってみて,必要に応じて調べるというスタンスで良いのかなあと個人的には思います.
この記事を一度読んで全てのことを理解するのは難しいと思いますが,「あらゆるComponentをつけたり,消したりすることで求める性質を持つGameObjectへと変えていく」ということが抑えられていればゲームの制作,アレンジもスムーズに行えるようになるのではないかと思います.
敵の追加も「プレイヤーを追いかけてくるコンポーネント」だったり「敵にぶつかったら自身が消滅するコンポーネント」などを作成すれば実現できそうです.
また,全てのComponentを自分で作る必要はありません.「Assets Store」で色々DLできたり,ググると色々出てきたりします.個人でもクオリティ上げやすいのがUnityの良いところの一つだと思います.