前回に引き続きビリヤードゲームを作っていきます。前回はビリヤードボールの当たり判定・移動処理を作りました。
前回の記事 :
第3回の今回は「マウスクリックによってボールを打つ、ボールが穴に入ったら消える、リセット」などゲームとして成立させるまでを作っていきます。
では始めていきましょう。
Unityにおけるマウスクリック処理の作り方について
マウスクリックによって何かを行う際は、Unityに用意されている関数を使うのが最も簡単です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Update() { if( Input.GetMouseButtonDown(0) == true ) { Debug.Log( "マウスが押されたときの処理." ); } if( Input.GetMouseButton(0) == true ) { Debug.Log( "マウスが押されている最中、押しっぱなしの処理." ); } if( Input.GetMouseButtonUp(0) == true ) { Debug.Log( "マウスクリックを終了、押すのをやめたときの処理." ); } } |
これらの関数でPCマウスのクリックを取得、処理できます。
Input.GetMouseButtonDown、Input.GetMouseButton、Input.GetMouseButtonUpの3つの関数を組み合わせていけばOKです。
ここで簡単にマウスクリック以外の処理の説明をしておきます。
if文
1 2 3 4 |
if( 条件 ) { // 処理. } |
「( )」内の条件が成立する場合に「{ }」内の処理が実行されます。
(関連記事:Unity C#の条件分岐の使い方 if文・switch文)
Debug.Log
1 |
Debug.Log( " 文字列 " ); |
Unityの「Console」画面に「( )」内の文字を出力します。直接文字を打ち込む場合は「” “」で囲う必要があります。(囲うことでこれは文字列だよと示す事ができる)
Update()は前回お話しましたが、毎フレーム実行され続ける関数です。
なのでこの処理は「毎フレームif文によってマウスのクリックを監視し続けて、クリックされたら各処理を行う」というスクリプトになります。
1 |
Input.GetMouseButtonDown(0) == true |
の「( 0 )」に当たる部分は
0 | 左クリック |
1 | 右クリック |
2 | 真ん中クリック |
のようにボタンを表しています。
クリック操作によって方向を決めてボールを打つ処理を作る
では実際にクリックによってボールを打つ処理を作成していきましょう。
まずこれから作成する処理を文章で表すと
1 2 |
・クリックした位置からドラックして引っ張って方向を設定 ・手を離したときに最初の位置と離した位置の方向、距離に応じてボールを打つ |
となります。
ビリヤードのボールが動く進路となるラインの作成
クリックで方向を決める際に今どの方向を向いているのかを示すための線となるゲームオブジェクトを作成しましょう。
まずヒエラルキーの「Ball」の子に「Line」という空Objectを作成します。
「Ballを右クリック→Create Empty」です。
そして「Line」を右クリックして「UI→Canvas」を作成します。
「Canvas」はUIなど2Dのものを表示するためのものですが、今回は少し設定と大きさをを変更していきます。下記のように変更しましょう。
1 |
「 Canvas 」 オブジェクトの 「 Canvas 」 コンポーネントの 「 Render Mode 」 を 「 World Space 」 に設定。 |
1 2 3 4 5 6 |
「 RectTransform 」の値を設定 PosX = 0, PosY = 0, PosZ = 0 Width = 1, Height = 1 Rotation : x = 0, y = 90, z = 0 Scale : x = 0.06, y = 0.06, z = 0.06 |
そして設定し終えた「Canvas」を右クリックして「UI→Image」を配置します。
「Image」を下記のように設定します。
1 |
「Image」オブジェクト「Image」コンポーネントの「Color」(色)を赤の半透明に設定(ご自由で構いません)。 |
1 2 3 4 5 6 |
「 RectTransform 」 の値を設定 PosX = -25, PosY = 0, PosZ = 0 Width = 50, Height = 0.2 Rotation : x = 90, y = 0, z = 0 Scale : x = 1, y = 1, z = 1 |
これでボールの方向を示すラインができました。
ビリヤードボールの動作スクリプトの作成
これからスクリプトを記載していきますが、先に今回作成するスクリプトを作ってオブジェクトに予めセットしましょう。
まずは前回作成した「BallController」です。これは中身を書き換えますが、そのまま「Ball」にセットしておいてください。
今回、新しく2つのスクリプトを作成します。
「ProjectWindow」の「Script」フォルダで右クリック→Create→C# Script
を選択肢新しくC#ファイルを作成し名前を「Hole」と「ColorBall」とします。
「Hole」はヒエラルキー「Table」の子「Hole」の子にある6つの「Cylinder」を複数選択し、インスペクターにドラック&ドロップをしすべてのシリンダーに「Hole」コンポーネントをセットします。
「ColorBall」はプレハブになっているカラーボールのプレハブを開き、インスペクターにドラック&ドロップしてカラーボールのプレハブに「ColorBall」のコンポーネントをセットします。
カラーこれでボール全てにセットされます。
手玉ボールを打つ処理の記述
ここから本格的にスクリプトを書いていきます。
まずは先にこれから書いていく「BallController」のソースコードを掲載します。前回書いた「Start」内の処理は消してしまって構いません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
using System.Collections.Generic; using UnityEngine; public class BallController : MonoBehaviour { // メインボール. [SerializeField] GameObject mainBall = null; // 打つ力. [SerializeField] float power = 0.1f; // 方向表示用オブジェクトのトランスフォーム. [SerializeField] Transform arrow = null; // ボールリスト. [SerializeField] List<ColorBall> ballList = new List<ColorBall>(); // マウス位置保管用. Vector3 mousePosition = new Vector3(); // メインボールのリジッドボディ. Rigidbody mainRigid = null; // リセット時のためにメインボールの初期位置を保管. Vector3 mainBallDefaultPosition = new Vector3(); void Start() { mainRigid = mainBall.GetComponent<Rigidbody>(); mainBallDefaultPosition = mainBall.transform.localPosition; arrow.gameObject.SetActive( false ); } void Update() { // メインボールがアクティブなとき. if( mainBall.activeSelf == true ) { // マウスクリック開始時. if( Input.GetMouseButtonDown(0) == true ) { // 開始位置を保管. mousePosition = Input.mousePosition; // 方向線を表示. arrow.gameObject.SetActive( true ); Debug.Log( "クリック開始" ); } // マウスクリック中. if( Input.GetMouseButton( 0 ) == true ) { // 現在の位置を随時保管. Vector3 position = Input.mousePosition; // 角度を算出. Vector3 def = mousePosition - position; float rad = Mathf.Atan2( def.x, def.y ); float angle = rad * Mathf.Rad2Deg; Vector3 rot = new Vector3( 0, angle, 0 ); Quaternion qua = Quaternion.Euler( rot ); // 方向線の位置角度を設定. arrow.localRotation = qua; arrow.transform.position = mainBall.transform.position; } // マウスクリック終了時. if( Input.GetMouseButtonUp(0) == true ) { // 終了時の位置を保管. Vector3 upPosition = Input.mousePosition; // 開始位置と終了位置のベクトル計算から打ち出す方向を算出. Vector3 def = mousePosition - upPosition; Vector3 add = new Vector3( def.x, 0, def.y ); // メインボールに力を加える. mainRigid.AddForce( add * power ); // 方向線を非表示に. arrow.gameObject.SetActive( false ); Debug.Log( "クリック終了" ); } } } // --------------------------------------------------------------------- /// <summary> /// リセットボタンクリックコールバック. /// </summary> // --------------------------------------------------------------------- public void OnResetButtonClicked() { //この中身だけ後で書いていく } } |
冒頭部分
1 2 3 4 5 6 7 |
using System.Collections.Generic; using UnityEngine; public class BallController : MonoBehaviour { // 処理. } |
個々の部分は現状ではファイル名以外はお約束だと思って下さい。少しだけ説明すると。
using | 名前空間(namespace)と言われるもので、Unityで定義されているオブジェクト(UnityEngine)やシステムで定義されているものを、使いますよーっていう宣言。 |
MonoBehaviour |
クラス名のあとに「 : 」をつけて書くことを「継承」と呼び、オブジェクト指向のプログラミングにおいての基礎になります。 今回は「MonoBehaviour」というクラスを継承した「BallController」というクラスを作成しています。 「MonoBehaviour」というのはUnityで用意されているクラスで、StartやUpdateといった関数を使用できるのはこれを継承しているためです。 |
(関連記事:UnityC# クラスの継承・抽象メソッドとオーバライドの使い方)
変数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// メインボール. [SerializeField] GameObject mainBall = null; // 打つ力. [SerializeField] float power = 20f; // 方向表示用オブジェクトのトランスフォーム. [SerializeField] Transform arrow = null; // ボールリスト. [SerializeField] List<ColorBall> ballList = new List<ColorBall>(); // メインボールのリジッドボディ. Rigidbody mainRigid = null; // マウス位置保管用. Vector3 mousePosition = newVector3(); // リセット時のためにメインボールの初期位置を保管. Vector3 mainBallDefaultPosition = newVector3(); |
ここは、変数の定義です。コメント記載しているような変数の箱を予め用意していると思って下さい。
変数は基本として
1 2 3 |
[(属性)] (型) (変数名) = (初期値、ない場合も有る) <例> [SerializeField] float power = 20f; |
のように表記します。
(関連記事:Unity C# 変数と型の使い方 宣言や代入・型変換について)
「SerializeField」は前回お話した、Unityエディターのインスペクターで設定できるようになる表記です。後々ここにセットしていきます。
「GameObject」「Transform」「List」「Rigidbody」「Vector3」「float」は「型」を表しておりそれぞれ別の形の箱になっている言うイメージを持っておきましょう。それぞれの型についても簡単に解説しておきます。
GameObject | Unityのゲームオブジェクト型 |
Transform | Unityの位置、角度、スケールなどを扱う型 |
Rigidbody | Unityのリジッドボディ型 |
List | リスト型。何のリストかは「List<float>」のように表記する |
Vector3 | 3次元ベクトル型、3Dゲームを作る際はよく使います。 |
float |
単精度浮動小数点数、要は小数型、数字のあとに「f」をつける(10.0fなど)。 |
(関連記事:UnityC# Listの使い方)
Start
1 2 3 4 5 |
void Start() { mainRigid = mainBall.GetComponent<Rigidbody>(); mainBallDefaultPosition = mainBall.transform.localPosition; } |
ここでは、前回にも出てきたように「mainBallからRigidbodyを取得してmainRigid」に保管しています。
また、リセットを行うときのために「mainBall」の位置を「mainBallDefaultPosition」に保管しています。
条件分岐
1 2 |
if( mainBall.activeSelf == true ) else |
最初にも登場しましたが「if文」は「()」の中の条件が成立するときに括弧内の処理を実行します。「else」は条件が成立しない場合の処理になります。
今回は「mainBall」が「activeSelf」= ゲームオブジェクトがアクティブなら「if」の括弧内、そうでないなら「else」の括弧内の処理が実行されます。
「else」内の処理は最後に説明しますので、ここから「if」の処理の解説をしていきます。
マウスクリック開始
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
voidUpdate() { ・ ・ (略) // マウスクリック開始時. if( Input.GetMouseButtonDown(0) == true ) { // 開始位置を保管. mousePosition = Input.mousePosition; // 方向線を表示. arrow.gameObject.SetActive( true ); Debug.Log( "クリック開始" ); } ・ ・ ・ <略> } |
先にお話したように
1 |
if( Input.GetMouseButtonDown(0) == true ) |
はマウスがクリックされたときの処理です。
ソースコードのコメントに書かれているように、開始時の位置を保管、方向先を示すゲームオブジェクトの表示を行い、コンソールにログを表示しています。
マウスクリック中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// マウスクリック中. if( Input.GetMouseButton( 0 ) == true ) { // 現在の位置を随時保管. Vector3 position = Input.mousePosition; // 角度を算出. Vector3 def = mousePosition - position; float rad = Mathf.Atan2( def.x, def.y ); float angle = rad * Mathf.Rad2Deg; Vector3 rot = new Vector3( 0, angle, 0 ); Quaternion qua = Quaternion.Euler( rot ); // 方向線の位置角度を設定. arrow.localRotation = qua; arrow.transform.position = mainBall.transform.position; } |
1 |
if( Input.GetMouseButton( 0 ) == true ) |
これは前述の通りマウスを押している最中の処理です。
この中が少し複雑な計算をしていますので簡単に解説します。
まず位置を保管しているのは前と同じですが、「角度を算出」の部分だけ少し説明します。
1 |
Vector3 def = mousePosition - position; |
これはベクトル計算です。数学、物理の分野になりますが二点間のベクトルを計算で、現在のマウスの位置からクリック開始時の位置までのベクトルを算出しています。
ベクトルは
1 |
ベクトルA - ベクトルB |
の計算をすることでベクトルB→Aベクトルを得ることができるため、クリック開始位置から終了を引くことで、終了位置→開始位置のベクトル、つまりボールの飛ぶ方向を算出しています。
1 2 |
float rad = Mathf.Atan2( def.x, def.y ); float angle = rad * Mathf.Rad2Deg; |
そして、そのベクトルを「アークタンジェント」という数学の逆三角関数をつかって角度(ラジアン単位)を算出、そのラジアンの角度を度数(「°」)に変換し「angle」という変数に入れています。
アークタンジェントとは、底辺と対辺の2辺の比に対しての角度θを考える逆三角関数で、先に算出した、ボールの飛ぶ方向のベクトルのx軸からの角度を算出しています。
ベクトル、アークタンジェントを図解しておきますので参考にして下さい。
「Mathf」というのがUnityにおいて数学の公式を使うための記載になります。
1 2 |
Vector3 rot = new Vector3( 0, angle, 0 ); Quaternion qua = Quaternion.Euler( rot ); |
そして、その角度を「Vector3」として扱うために新しいVector3を作成し「rot」に保管。
その「rot」を「Quaternion」というUnityで角度を表すために用いられる型に変換し「qua」に保管しています。
1 2 3 |
// 方向線の位置角度を設定. arrow.localRotation = qua; arrow.transform.position = mainBall.transform.position; |
そして最後に、方向線の角度を「qua」に変更、位置を「mainBall」の位置に移動させています。
マウスクリック終了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// マウスクリック終了時. if( Input.GetMouseButtonUp(0) == true ) { // 終了時の位置を保管. Vector3 upPosition = Input.mousePosition; // 開始位置と終了位置のベクトル計算から打ち出す方向を算出. Vector3 def = mousePosition - upPosition; Vector3 add = new Vector3( def.x, 0, def.y ); // メインボールに力を加える. mainRigid.AddForce( add * power ); // 方向線を非表示に. arrow.gameObject.SetActive( false ); Debug.Log( "クリック終了" ); } |
最後に、同じように位置を「upPosition」に保管。最終的に打ち出す方向をベクトル計算をして算出。
1 2 |
// メインボールに力を加える. mainRigid.AddForce( add * power ); |
ここで、算出した値に「power」をかけて実際に力を加えます。
ここまで来たら実際に動かしてみましょう。
インスペクターの設定
では、Unityに戻って、「SerializeField」に設定したオブジェクトや数値を設定していきましょう。
画像を参考に「MainBallにMainBall」「ArrowにLine」「BallListに各ボール」をドラック&ドロップしましょう。「BallList」は右の数字を入力すると項目数を変更できます。
「Power」はボールを打つ強さが変わりますので自由に変更してみましょう。
手玉ボールを実際に打ってみる
ではPlayしてみましょう。
マウスをクリックして、そのままドラックして方向線が正しく動くか、クリックを離したときに線の方向に飛んでいくかを確認しましょう。
カラーボール、ビリヤード穴、ゲームリセット処理のスクリプトの作成
次に、最初に作成した「ColorBall」「Hole」のスクリプトを記述していきます。
ColorBallスクリプト
「ColorBall」の全文です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
using UnityEngine; public class ColorBall : MonoBehaviour { // リセットのための初期位置. Vector3 defaultPosition = new Vector3(); // リジッドボディ. Rigidbody rigid = null; void Start() { rigid = GetComponent<Rigidbody>(); defaultPosition = this.transform.localPosition; } // ---------------------------------------------------------------------- /// <summary> /// リセット時の処理. /// </summary> // ---------------------------------------------------------------------- public void Reset() { gameObject.SetActive( true ); // リジッドボディの速度を強制的に0にする. rigid.velocity = Vector3.zero; // リジッドボディの回転速度を強制的に0にする. rigid.angularVelocity = Vector3.zero; // 初期位置に戻す. this.transform.localPosition = defaultPosition; } } |
似たような記載が多いので詳細説明は省きますが、後半部分の解説を簡単にします。
1 2 3 4 5 6 7 8 9 10 |
public void Reset() { gameObject.SetActive( true ); // リジッドボディの速度を強制的に0にする. rigid.velocity = Vector3.zero; // リジッドボディの回転速度を強制的に0にする. rigid.angularVelocity = Vector3.zero; // 初期位置に戻す. this.transform.localPosition = defaultPosition; } |
ここで「Reset」という関数を定義しています。「pubic」というのは他の関数から呼び出せるように公開するといった意味で捉えておきましょう。
こうすることで「Reset」関数を他のスクリプトから呼び出すことができます。内部で行っていることはコメントを参考にしてください。
Holeスクリプト
「Hole」の全文です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
using UnityEngine; public class Hole : MonoBehaviour { void Start() { } void OnTriggerEnter( Collider other ) { Debug.Log( "落ちたボールの名前 : " + other.gameObject.name ); // 穴に落ちたボールを非アクティブにする. other.gameObject.SetActive( false ); } } |
1 |
void OnTriggerEnter( Collider other ) |
これは、Unityに用意されている「IsTriggerがオンになっているコライダーに他のコライダーが侵入したときに実行される関数」です。
前回「Hole」オブジェクトには、コライダーを設定しており「IsTrigger」をオンに設定していますので、ボールがこの「Hole」に侵入すると「{}」内の処理が実行されます。
リセット処理をBallControllerスクリプトに追加する
リセット処理はリセットはボタンを配置して、押したらリセットするという形にしていきます。BallControllerのOnResetButtonClicked()の中身を書いていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class BallController : MonoBehaviour { void Start() { ・・・ } void Update() { ・・・ } // --------------------------------------------------------------------- /// <summary> /// リセットボタンクリックコールバック. /// </summary> // --------------------------------------------------------------------- public void OnResetButtonClicked() { mainBall.SetActive( true ); // メインボールの速度を強制的にゼロに. mainRigid.velocity = Vector3.zero; // メインボールの回転速度を強制的にゼロに. mainRigid.angularVelocity = Vector3.zero; // メインボールを初期位置に戻す. mainBall.transform.localPosition = mainBallDefaultPosition; foreach( ColorBall ball in ballList ) { // カラーボールのリセット. ball.Reset(); } } } |
1 |
foreach( ColorBall ball in ballList ) |
「foreach」はリストや、配列などの要素全てに対して繰り返し処理を行うことができます(関連記事:Unity C#の繰り返し処理の使い方 for文・while文・foreach文)。
ここでは、「ballList」の要素「ColorBall」に「ball」という名前をつけて、すべての要素に「{}」内の処理を行います。
リセットボタンの設置
スクリプトに書いたリセット処理ををボタンを押して起動できるように画面にボタンを設置していきます。ヒエラルキーの最初に作成した「Canvas」を右クリックして「UI→Button」でボタンを設置します。
設置したボタンの位置、大きさ、色を調整していきます。まずはアンカーを設定していきます。
アンカーとは親の「Canvas」に対してどこを基準に位置を設定するかを定める機能です。
「RectTransform」左上に有る四角いマークをクリックして設定します。(Anchorの数値を直接記入して変更することも可能ですが今回はより簡単な方法を解説します)
「Button」のインスペクター「RectTransform」の右上の四角いマークをクリックし「right/top」を選択しましょう。こうすると「Canvas」の右上を基準にした数値を入力することになります。
では、数値の入力をしていきましょう。「Button」の「RectTransform」の値を変更していきます。
1 2 3 4 5 |
PosX = 0, PosY = 0, PosZ = 0 Width = 200, Height = 100 Pivot : X = 1, Y = 1 ( Pivotを変更するとPosが自動的に変更される場合があるので注意しましょう。0に戻してください。 ) Rotation、Scaleは変更なし |
入力すると画像のような位置になります。
そして文字と色を変更、ボタンを押したときの処理を登録していきます。
まずはヒエラルキーの「Button」を選択しインスペクターの「Image」の「Color」をクリックして「A」の数値を「120」ほどに変更します。カラーのそれぞれは「R=赤、G=緑、B=青、A=アルファ」となっており「アルファ」とは透明度を表します。これを下げると半透明、0にすると見えなくなります。
次に「Button」の子にある「Text」を選択、インスペクターの「Text」コンポーネントに「Reset」という文字を入力しましょう。これでボタンにResetという文字が表示されます。また、TextのFontSizeを25に変更しておきましょう。
「Button」コンポーネントの「OnClick」の右下にある「+」ボタンを押して新しいイベント登録欄を出します。
そしてその「None(Object)」となっているところに、「BallController」が設定されているヒエラルキーの「MainBall」オブジェクトをドラック&ドロップします。
ヒエラルキーにあるMainBallオブジェクトをそのままドラッグ&ドロップしてOnClick()のところに持ってきます。イベントを設定するときによく使う手順なのでここで理解しておきましょう。
すると右側に「No Function」という項目が有効になりますのでそこを開き作成した「BallController→OnResetButtonClicked」を選択します。
これでボタンを押したら、「OnResetButtonClicked」が実行されるようになりました。
ゲームとしての完成度
以上で今回作成するゲームのPCでの内容は完成となります。
しかしあくまでボールを打つ、穴に落ちたら消える、再スタートといった基本しか作っていません。
例えば、本来のビリヤードゲームであれば、落ちたボールを画面に表示したり、ステージ選択ができたりするでしょう。
また、現状白い玉が動いている最中でも更にボールが打てたりするバグも残っています。
ゲームとしての完成度はまだまだ低いと言えるでしょう。
ぜひこの記事を読んだあなたが自由にこのビリヤードゲームを拡張してみてください。
追記:ここまでの内容を実装したプロジェクトファイルを用意しました。
ビリヤードゲームのプロジェクトファイルダウンロードはこちら>>
次回は最後にこのビリヤードゲームをスマホ対応できるように、タッチ入力に変更していきます。
次回の記事:
コメント