前回は弓(Bow)と矢(Arrow)を作成し、敵(Enemy)に攻撃できるようになりました。
前回の記事↓

今回はプレイヤー(Player)を作成し、弓矢の配置やUI(HPなど)の表示をやっていきます。
プレイヤー(Player)オブジェクトの作成
プレイヤー(Player)オブジェクトを作りましょう。
まず、空オブジェクトを作ります。
Hierarchyビューに、新規オブジェクト(CTRL+SHIFT+N)を作成し、新規ScriptもAddComponent→NewScriptで作成しておきます(いつもの流れですね)
- 名前:Player
- スクリプト名:Player
(※Playerは画面に表示される類のものではないため、Transformはそのままでも構いません。もちろんResetして全て0にしても構いません)
Playerに必要なのは
- 弓Prefab
- HP(拠点の体力)
- GOLD(所持金)
- 選択中の弓
- 指定の場所に弓の建設が出来る
- 選択中の弓のレベルアップが出来る
- 選択中の弓の売却が出来る
あたりです。
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 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { //弓Prefab public Bow bowPrefab; //HP(拠点の体力) public int hp; //GOLD(所持金) public int gold; //選択中の弓 public Bow selectBow; //指定の場所に弓の建設が出来る public void CreateBow(Transform t) { } //選択中の弓のレベルアップが出来る public void LevelUpBow() { } //選択中の弓の売却が出来る public void SellBow() { } void Update() { } } |
指定の場所に弓の建設が出来るは、所定の金額(今回はとりあえず100としました)以上お金を持っているかを調べ、持っているなら所持金を減らしbowPrefabをInstantiateするだけです。
なお、Instantiateの第二引数にTransform を渡しています。
これにより親Transformを指定しながら生成することが出来ます。
1 2 3 4 5 6 7 |
//指定の場所に弓の建設が出来る public void CreateBow(Transform t) { if (gold < 100) return; gold -= 100; selectBow = Instantiate(bowPrefab,t); } |
選択中の弓のレベルアップが出来る は、選択中のBowが無い場合は何もしないので if文 で selectBow == null
の場合と、
所持金がレベルアップの金額(今回はとりあえず150としました)に足りない(gold < 150
)場合、
何もせずに関数を抜けています。
if文でreturnされない場合。
すなわち選択されているBowがあり、所持金が足りている場合は、レベルアップ処理をする予定ですが、まだその機能がBow側に無いため金額だけ減らし、TODOコメントを書いておきます。
1 2 3 4 5 6 7 8 |
//選択中の弓のレベルアップが出来る public void LevelUpBow() { if (selectBow == null) return; //何も選択されていない if (gold < 150) return; //所持金が足りない gold -= 150; //TODO goldを消費して、弓のレベルアップをする } |
選択中の弓が売却できる も同じく、選択中のBowが無い場合はreturnしています。
選択されているBowがある場合は、Bowのレベルに応じてGoldを増やした後にBowを削除(Destroy)する必要があります。
が、まだBowにレベルの概念が無いため、削除(Destroy)と、選択中の弓を売却したということは、選択中の弓が無くなることを意味するので、 selectBow = null;
としています。
1 2 3 4 5 6 7 8 9 |
//選択中の弓の売却が出来る public void SellBow() { if (selectBow == null) return; //TODO 本当はレベルに応じて金額が変わる gold += 50; Destroy(selectBow.gameObject); selectBow = null; } |
では、Update関数から用意した関数呼び出すテストコードも書いておきます。
左クリック( Input.GetMouseButtonDown(0)
):CreateBow
右クリック( Input.GetMouseButtonDown(1)
):SellBow
を呼ぶようにしています。(LevelUpは呼んでも意味が無いため呼んでいません)
1 2 3 4 5 6 7 8 9 10 11 12 |
void Update() { if (Input.GetMouseButtonDown(0)) { CreateBow(null); } if (Input.GetMouseButtonDown(1)) { SellBow(); } } |
忘れずに保存をしてUnityEditorに戻ります。
Playerオブジェクトの編集
PlayerオブジェクトのInspectorを見るとこのように、なっていると思います。
Bow Prefabには前回最後に作ったAssets/Prefabs/Bow Prefabをセットします。 Hp は 適当に10 とでも入れておきましょう。 Gold は 1000 にしておきます。
再生して確認
では、UnityEditorで再生をしてテストしてみましょう。
左クリック をすると、Bowが生成され、 右クリックで生成したBowが消えればOKです。
なお、左クリックを連続で何回もすると・・・
このように、同じ場所にBowが何個も置かれてしまいますし、右クリックしても消えません(消えるのは最後に作られたBowだけです)。
クリックしたタイルに弓を作る
これでは、ゲームにならないので、クリックしたタイルに弓が置けるようにしたいですね。
そのためには「クリックした位置にタイルがあるかどうか」を調べる必要があります。
これは、前回の弓(Bow)が周辺の敵(Enemy)を探すときにやった当たり判定(Collider2D)とレイヤーを使う手法が使えそうです。
Layerの変更と当たり判定の追加
ただ、そのためには Assets/Prefabs/ にPrefab化してある tile_border を修正して、Layerを Default から Block に変更し、AddComponentでBoxCollider2Dを追加します。
Enemyは画像が細長かったり丸かったりしたのでSize修正をしましたが、tile_borderは正方形なので修正の必要はありません。
Playerスクリプトの修正
では、Playerスクリプトをさらに修正します。
今は左クリックでただCreateBow関数を呼んでいますが、クリックした位置がtile_borderなのかどうかを当たり判定で調べ、当たっている場合はその場所にBowを作るように以下のように修正します。
1 2 3 4 5 6 7 |
if (Input.GetMouseButtonDown(0)) { var mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition); var col = Physics2D.OverlapPoint(mousePos, LayerMask.GetMask("Block")); if (col == null) return; CreateBow(col.transform); } |
マウスの位置は Input.mousePosition
に入っているのですが、そこに入っているのは「スクリーン座標」と言われるものです。
マウスカーソルはゲーム画面を動いているわけではなく(奥行とか無いですしね)言ってしまえば画面の上を滑っているだけとも言えます。
なお今回はゲーム画面サイズを1024×768としたので、マウスを一番左下に持っていくと(0,0),一番右上に持っていくと(1024,768)がスクリーン座標としてInput.mousePosition
に入ります。
それに対して「ワールド座標」というものがあります。
これはまさにゲームの中の座標で、Inspectorで表示されているTransformのPosition等はこの「ワールド座標」での位置を表しています。
当たり判定などで使用する座標はこの「ワールド座標」の方になるので変換が必要になります。
この「スクリーン座標」→「ワールド座標」への変換が
1 |
var mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition); |
この処理になります。
今メインで使っているカメラ(Camera.main
)の ScreenToWorldPoint
(そのままの名前ですね) という関数を使って、スクリーン座標であるマウス位置を渡し、ワールド座標としてのマウス位置を取得して変数 mousePos
に格納しています。
次に、このmousePos
を使って、マウス位置のタイルを探します。
前回はBowの座標を中心としてある程度の範囲(半径)内の敵を探しましたが、今回は半径は必要が無いので、Physics2D.OverlapPoint
を使っています。
1 |
var col = Physics2D.OverlapPoint(mousePos, LayerMask.GetMask("Block")); |
そして、何も当たらなかった場合は col には null が入るのでその場合はreturn をさせ処理を抜けます。
そうでない場合(tileが当たった場合)は、colには当たっているオブジェクトが入るので、CreateBowの引数にそのオブジェクトのtransformを渡しています。
1 2 |
if (col == null) return; CreateBow(col.transform); |
CreateBow 関数もちょっと修正が必要です。
1 2 3 4 5 6 7 8 |
//弓の建設が出来る public void CreateBow(Transform t) { if (gold < 100) return; gold -= 100; selectBow = Instantiate(bowPrefab,t); selectBow.transform.localPosition = Vector3.zero; } |
生成したBowオブジェクトのlocalPosition
に Vector3.zero
を入れています。
今までほとんどがposition
(ワールド絶対座標)でしたが、今回はlocalPosition
(親オブジェクトからの相対位置)を使っています。
親からの相対位置が0ということはすなわち、「親オブジェクトと同じ場所に表示」という意味になります。
では、保存をしてUnityEditorで再生をして確かめてみましょう。
tile_borderでは無いところをクリックしても何も起こらず、tile_borderをクリックすることで、弓オブジェクトが生成されるようになったと思います。
ただ、まだ問題があります。 既に弓が生成されているtile_borderをもう一度クリックすることで同じ場所に何個でも弓が置けてしまいます。
これを防ぐのと同時に、選択中の弓のレベルアップや売却の処理もあるので
- 既に子要素に弓(Bow)がある場合は、新規で弓は作らず、既にある弓を選択する
ようにしてあげる必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
if (Input.GetMouseButtonDown(0)) { var mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition); var col = Physics2D.OverlapPoint(mousePos, LayerMask.GetMask("Block")); if (col == null) return; var childBow = col.GetComponentInChildren<Bow>(); if (childBow == null) { CreateBow(col.transform); } else { selectBow = childBow; } } |
この「既に子要素に弓(Bow)があるか」を取得するために GetComponentInChildren<Bow>();
を呼んでいます。
これは読んで字のごとく、子要素から指定(<>
の中で指定)のオブジェクトを取得する関数です。
それを childBow
に格納しています。 もし、子要素に弓(Bow)が無い場合はnullなので、その場合は今まで通り CreateBow
関数を呼んでいます。
nullではない場合は、既に子要素に弓(Bow)があった。という事なので selectBow
に格納して選択状態にします。 ここまでの修正によるPlayerスクリプト全体はこのようになります。
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 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Player : MonoBehaviour { //弓Prefab public Bow bowPrefab; //HP(拠点の体力) public int hp; //GOLD(所持金) public int gold; //選択中の弓 public Bow selectBow; //弓の建設が出来る public void CreateBow(Transform t) { if (gold < 100) return; gold -= 100; selectBow = Instantiate(bowPrefab,t); selectBow.transform.localPosition = Vector3.zero; } //選択中の弓のレベルアップが出来る public void LevelUpBow() { if (selectBow == null) return; //何も選択されていない if (gold < 150) return; //所持金が足りない gold -= 150; //弓のレベルアップをする } //選択中の弓の売却が出来る public void SellBow() { if (selectBow == null) return; //TODO 本当はレベルに応じて金額が変わる gold += 50; Destroy(selectBow.gameObject); selectBow = null; } void Update() { if (Input.GetMouseButtonDown(0)) { var mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition); var col = Physics2D.OverlapPoint(mousePos, LayerMask.GetMask("Block")); if (col == null) return; var childBow = col.GetComponentInChildren<Bow>(); if (childBow == null) { CreateBow(col.transform); } else { selectBow = childBow; } } if (Input.GetMouseButtonDown(1)) { SellBow(); } } } |
保存をして再生してみましょう。
同じ場所には複数の弓が置けなくなり、既に置いてある弓をクリックした後に右クリックすることで売却(削除)が出来るようになっているはずです。
UI作成 プレイヤー・ゲーム情報の表示と弓のレベルアップ処理
さて、プレイヤーによる弓の配置も出来るようになり、大分ゲームらしくなってきました。そろそろUIの方も作っていきたいと思います。 UIに表示するのは
- Player情報(HPやGOLD)
- ゲーム情報(現在のWave、敵の数)
- 選択中の弓への操作(レベルアップ・売却等)
です。 UI用の場所は確保してありますね。 HierarchyにあるCanvasがそうでした。このCanvasの子要素のImageの子要素にさらにUIを追加していきます。
まず、Player情報(HPやGOLD)を表示する枠を用意していきます。 HierarchyビューのCanvas/Imageを右クリックして、UI→Imageを選択します。
Canvas/Imageの子要素にさらにImageが作成されました。
追加したImageのAnchor PresetはCenter(親要素の中心からの相対位置指定)になっているので、SHIFT キーを押しながら画像の赤枠の場所をクリックし、縦はtop,横はstretchにします。

Shiftキーを押しながら選択することで、Pivot(原点)もセットされます
次に以下のように設定していきます。
- 名前:PlayerStatusUI
- Image の Source Image に tile を指定
- RectTransform は
- Left:20
- Right: 20
- Pos Y: -20
- Height: 200
このように外枠だけ作成しました。
さらに、この PlayerStatusUI の子要素として Text を追加します。(PlayerStatusUIを右クリック→UI→Text)
追加したTextをInspectorで設定していきます。
- 名前: Text(HP)
- Font : Assets/Fonts/ShigotoMemogaki-Regular-1-01
- Text : “HP:10”
- Font Style: BOLD
- Font Size:40
設定をしても画面に文字が表示されませんね?
これは、フォントサイズに対して、Textの幅が足りていないからです。 SceneビューのモードをRectToolに変更し(ショートカットキー:CTRL+T)
幅と位置を調整しましょう。(この枠内にはもう一つTextを置くので、あまり縦幅を大きく取らないようにしておきましょう) 今作った Text(HP) を複製(CTRL+D)し、さらに下に並べます。 名前は Text(GOLD) としておきます。
では、Playerの情報をUIに反映させるスクリプトを用意します。
PlayerStatusUI に AddComponent→New Script → PlayerStatusUI として、PlayerStatusUIスクリプトを作成し、下記のように編集します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class PlayerStatusUI : MonoBehaviour { public Player player; public Text hpText; public Text goldText; void Update() { hpText.text = $"HP:{player.hp}"; goldText.text = $"GOLD:{player.gold}"; } } |
1 |
using UnityEngine.UI; |
は無いと、UIのコンポーネントが扱えないため必要となります。 次に
1 2 3 |
public Player player; public Text hpText; public Text goldText; |
は、上から順に
- どのPlayerの情報を表示するか(Inspectorで設定)
- PlayerのHP情報を表示するためのText(Inspectorで設定)
- PlayerのGOLD情報を表示するためのText(Inspectorで設定)
になります。 そして、
1 2 3 4 5 |
void Update() { hpText.text = $"HP:{player.hp}"; goldText.text = $"GOLD:{player.gold}"; } |
Update関数は毎フレーム呼ばれるので、その中でそれぞれのTextにPlayerの情報をセットしています。
$"HP:{player.hp}";
という、人によっては見なれない書式が出てきていますね。 これは 文字列を表す ""
の前に $
記号を付けると、 {
~ }
の中身が文字列内に展開されるという、3年ぐらい前のUnityから使えるC#の便利機能です。保存をするとUnityEditorのInspectorはこのように、3つセットする枠が出ていると思います。 下記のように、ドラッグアンドドロップで設定します。
では再生をして確認をしてみましょう。弓の配置や売却でUIのGOLDが増減するようになっているのが分かると思います。
なおHPの方はまだPlayerへのダメージ処理が無いため確認がし辛いですが、UnityEditorは実行中にInspectorで値を直接変更することが出来ます。
PlayerのInspectorビューのHPの数値を増やしたり減らしたりしてみると、UI上のHP表示も変化するのが分かります。便利ですね。
おさらいと次回予告
今回はプレイヤーによる弓の配置と、UI画面を作り始めました。
次回はさらにUI画面を作り込み、弓オブジェクトのレベルや制作コスト・売却コスト等をやっていきます。
次回の記事↓

コメント