この記事はロックマン風2Dアクションゲームの作り方講座の第15回です。
前回まででゲームに必要な機能を一通り実装しました。
前回の記事:
今回は最後の解説として、スマートフォン(Android)対応および広告設置、そして特殊武器獲得時の演出強化やデータ保存の実装を行っていきます。
広告実装に関してはステージ選択画面でのインタースティシャル広告とゲームオーバーからの復活を報酬としたリワード広告を実装します。スマホゲームをマネタイズする際によく採用される形式での実装になります。
スマートフォン(Android)対応
このゲームをスマートフォンからでも遊べるようにしてみましょう。
まず現状の設定ではビルドした時にWindows用のexeファイルとして出力されるので、ビルド対象プラットフォームの設定を変更します。
Build Settings画面を開き、左下の欄からAndroidを選んで[Switch Platform]ボタンを押下して切り替えます。(切り替えには少し時間が掛かります。)
スマートフォン用仮想ボタン作成
この状態でビルドを行えば(環境設定が済んでいるなら)すぐに手持ちのスマートフォンでテストプレイすることが出来るのですが、プレイヤーキャラの操作はキー入力にしか対応していないので普通の端末ではステージを遊ぶことが出来ません。
スマートフォンで起動した時はステージ攻略時の画面上に左右移動やジャンプ、射撃および武器切り替えのボタンを表示してそこから操作が行えるようにしていきます。
各種ボタンはCanvas上にUIとして作成しますが、今回はButtonコンポーネントでもEventTriggerでもなくスクリプトから画像へのタップを検出する形で実装してみましょう。
VirtualButton.cs (新規)
画像UIオブジェクトにアタッチするとボタンとして扱えるクラスを作成します。さらに押した瞬間だけではなく、「押しっぱなし」や「指を離した」状態も取得できるようにしていきます。
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 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; /// <summary> /// Canvas上の仮想ボタンの入力処理 /// </summary> public class VirtualButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler { // 入力状態変数 public bool input; // 入力中 public bool down; // 入力開始時 public bool up; // 入力終了時 // LateUpdate(Updateより後に実行) void LateUpdate () { down = false; up = false; } /// <summary> /// タップ開始時に実行 /// (IPointerDownHandlerが必要) /// </summary> public void OnPointerDown (PointerEventData eventData) { if (!input) down = true; input = true; } /// <summary> /// タップ終了時に実行 /// (IPointerUpHandlerが必要) /// </summary> public void OnPointerUp (PointerEventData eventData) { if (input) up = true; input = false; } } |
UIに対するタップ開始時などのタイミングを取得するためにOnPointerDownとOnPointerUpの2つのメソッドを定義しています。
OnPointerDownはそのUIに対してタップ開始したタイミングで呼び出され、OnPointerUpは指が離れたタイミングで1回呼び出されます。
Buttonコンポーネント等を必要とせずこのスクリプトをアタッチするだけでOKですが、IPointerDownHandlerとIPointerUpHandlerの2つを継承のためクラス右側に書くことが必要です(インターフェースと呼ばれる機能です)。
関連記事:Unity C# インターフェースの使い方・ポリモーフィズムの考え方
public変数のinput・down・upを他クラスから参照することで、そのボタンが今押されたばかりなのか、継続して入力中なのかといった情報を取得できます。ボタンが押されっぱなしの間はinputがtrueになります。これによりGetKeyDownメソッド等と近い運用が可能です。
down変数とup変数は毎フレームfalseに初期化する必要がありますが、通常のUpdateでそれをやってしまうと、他クラスからdownやupの情報を取得したいタイミングで既に初期化されてしまっている可能性が発生するのでLateUpdateを用いて通常のUpdate後に初期化が行われるようにしています。
仮想ボタンプレハブの設置
StageManagerプレハブの編集画面に入り、まずはCanvas以下に画像UIとしてVirtualButtonオブジェクトを作成します。
Source Imageはプレハブ化後にインスタンスでそれぞれ設定するのでここでは未設定で大丈夫です。
先ほどのVirtual Buttonクラスをアタッチします。この状態でプレハブ化しましょう。
仮想ボタンインスタンス作成・配置
プレハブからインスタンスを作成していき、全ての仮想ボタンをCanvas上に配置しましょう。
必要になる仮想ボタンは以下の6つです。
使用する画像は全てTextures/VirtualButtons以下にあります。
オブジェクト名 | 対応ボタン | 画像ファイル名 | PosX | PosY |
VirtualButton_Left | 左移動 | VirtualButton_Left.png | -450 | -170 |
VirtualButton_Right | 右移動 | VirtualButton_Right.png | -330 | -170 |
VirtualButton_Jump | ジャンプ | VirtualButton_Jump.png | 440 | -120 |
VirtualButton_Fire | 攻撃 | VirtualButton_Fire.png | 310 | -170 |
VirtualButton_ChangeWeaponCW | 武器切り替え(時計周り) | VirtualButton_ChangeWeaponCW.png | -335 | 190 |
VirtualButton_ChangeWeaponCCW | 武器切り替え(反時計周り) | VirtualButton_ChangeWeaponCCW.png | -505 | 190 |
※VirtualButton_ChangeWeaponCWとVirtualButton_ChangeWeaponCCWは画像のサイズを70×70くらいにすると良い
注意点として、OnPointerDown等の機能を使用する場合はシーン内にEventSystemオブジェクトが存在している必要があります。
StageManagerプレハブ内に無い場合は、新規オブジェクト作成で[UI]→[Event System]の順にクリックして追加しておきましょう。
ここからはスクリプト内でキー入力を取っていた箇所について、上記の仮想ボタンからの入力にも対応できるようにしていきます。
(ちなみにButtonUIやEventTriggerはスマートフォンのタップでも動作するので、それらしか使っていないStageSelectシーンに対しては作業不要です。)
StageManager.cs 変数宣言部
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 |
/// <summary> /// ステージマネージャクラス /// </summary> public class StageManager : MonoBehaviour { [HideInInspector] public ActorController actorController; // アクター制御クラス [HideInInspector] public CameraController cameraController; // カメラ制御クラス public Image bossHPGage; // ボス用HPゲージImage [Header ("初期エリアのAreaManager")] public AreaManager initArea; // ステージ内の最初のエリア(初期エリア) [Header ("ボス戦用BGMのAudioClip")] public AudioClip bossBGMClip; [Header ("ザコ敵がドロップするアイテムのプレハブリスト")] public GameObject[] dropItemPrefabs; // ステージ内の全エリアの配列(Startで取得) private AreaManager[] inStageAreas; // 仮想ボタン一覧 public VirtualButton virtualButton_Left; public VirtualButton virtualButton_Right; public VirtualButton virtualButton_Jump; public VirtualButton virtualButton_Fire; public VirtualButton virtualButton_ChangeWeaponCW; public VirtualButton virtualButton_ChangeWeaponCCW; |
各仮想ボタンへの参照を他クラスから使用できるようにしました。
早速Inspectorから変数名に対応するボタンへの参照をセットしておきます。
ActorController.cs
上記の仮想ボタン情報を使用して、キー入力とあわせて仮想ボタンからの入力にも対応できるよう各if文を変更していきます。
StageManagerオブジェクトの設定
Sceneの編集に戻り、WeaponGetAnimationクラスを動作できるよう設定していきましょう。
StageManagerオブジェクト(インスタンス)を選択し、まずはデフォルトで持っているStageManagerコンポーネントを削除(Remove Component)します。
通常のステージではないのでステージ進行処理を行うこのクラスは不要になるためです。
なお、Inspector上では削除操作を行ってもRemovedの表示になるだけで姿が残っているように見えますが、きちんと削除はなされているので安心してください。(このオブジェクトがプレハブのインスタンスであるためこのような表示になっています。)
また、Audio Sourceコンポーネントで再生するBGMのClipも適当な音源に設定しておきます。
最後にこのオブジェクトにWeaponGetAnimationクラスをアタッチします。
シーン内UIの設定
現在は通常ステージシーン用のHPゲージ等のUIが表示されたままですので、Canvasオブジェクト以下にあるUIオブジェクトを一旦全て非表示(非アクティブ)にしておきます。
そして「新しい武器を入手!」という文字を表示するためのTextUIオブジェクトを追加します。オブジェクト名はWeaponGetLogoとしました。
表示位置・表示内容は任意としますが、下図のように画面上部の邪魔にならない位置に置くと良いでしょう。
文字が背景と被って見づらい!という場合は文字の後ろに別のImageUIオブジェクトを用意するか、Textを縁取りしてしましょう。縁取りの方法はこのTextUIオブジェクトに対してOutlineコンポーネントを追加でアタッチするだけです。
Outlineコンポーネントによって任意の色・太さで輪郭線を引けるので覚えておきましょう。
Text表示の注意点として、標準のArialフォントを使用すると日本語の表示の際に文字化けする可能性があります。それを完全に防ぎたい場合は別途日本語対応したフォントをインポートし、TextコンポーネントのFont設定を変更する必要があります。
これで新しい武器の試し撃ち演出が出来上がりました。デバッグ時はステージセレクト画面からステージを選択し、そのままクリアすれば正しい演出を確認できます。
PlayerPrefsによるデータ保存
現在のゲームは一度アプリを終了すると、次に起動した時に全てのデータが初期化されています。スマートフォン端末内にデータを保存する仕組みがないためです。
今回はデータを保存する方法の1つであるPlayerPrefsを使用してみましょう。
PlayerPrefsを使用すると任意の数値や文字列(Value)を、対応するキー(Key)に紐づけて保存・ロードできます。キーはそれぞれ重複しない文字列を指定します。
Unity標準の機能であり、特にセットアップの手続きは不要です。早速スクリプトを編集しましょう。
Data.cs
PlayerPrefsの機能を用いてデータの保存・ロード機能を実装します。
データのロードはInitialProcess内でそのまま行います。
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 93 94 95 96 97 98 99 100 101 102 103 |
using System.Collections; using System.Collections.Generic; using UnityEngine; /// <summary> /// データマネージャー(シングルトン) /// </summary> public class Data : MonoBehaviour { #region シングルトン維持用処理(変更不要) // シングルトン維持用 public static Data instance { get; private set; } // Awake private void Awake () { // シングルトン用処理 if (instance != null) { Destroy (gameObject); return; } instance = this; DontDestroyOnLoad (gameObject); // その他起動時処理 InitialProcess (); } #endregion // シーン間で保持するデータデータ public bool isTitleDisplayed; // タイトル画面タップ済み public bool[] stageClearedFlags; // ステージ別クリアフラグ public int nowStageID; // 現在攻略中のステージID public bool[] weaponUnlocks; // 特殊武器の開放データ // 定数定義 public const int StageNum_Normal = 7; // 通常ステージ数 public const int StageNum_All = 8; // ラスボス面も含めたステージ数 public const int LastStageID = 7; // ラスボス面のステージ番号 // PlayerPrefsキー private const string Key_StageClearedFlags = "Key_StageClearedFlags"; private const string Key_WeaponUnlocks = "Key_WeaponUnlocks"; /// <summary> /// ゲーム開始時(インスタンス生成完了時)に一度だけ実行される処理 /// </summary> private void InitialProcess () { // 乱数シード値初期化 Random.InitState (System.DateTime.Now.Millisecond); // ステージクリアフラグ初期化 stageClearedFlags = new bool[StageNum_All]; // PlayerPrefsからロード for (int i = 0; i < StageNum_All; i++) { // データに1が格納されていたならステージクリア済みフラグを立てる if (PlayerPrefs.GetInt (Key_StageClearedFlags + i, 0) == 1) stageClearedFlags[i] = true; } // 特殊武器開放データ初期化 weaponUnlocks = new bool[(int)ActorController.ActorWeaponType._Max]; weaponUnlocks[0] = true; // 1個目の初期武器は自動開放 // PlayerPrefsからロード for (int i = 0; i < (int)ActorController.ActorWeaponType._Max; i++) { // データに1が格納されていたなら武器の開放済みフラグを立てる if (PlayerPrefs.GetInt (Key_WeaponUnlocks + i, 0) == 1) weaponUnlocks[i] = true; } } /// <summary> /// ステージクリアデータと武器解放データをそれぞれ保存する /// </summary> public void SaveDatas () { // ステージクリアフラグ保存 for (int i = 0; i < StageNum_All; i++) { // 該当ステージのクリア済みフラグが立っているなら1を、未クリアなら0を保存 if (stageClearedFlags[i]) PlayerPrefs.SetInt (Key_StageClearedFlags + i, 1); else PlayerPrefs.SetInt (Key_StageClearedFlags + i, 0); } // 武器開放フラグ保存 for (int i = 0; i < (int)ActorController.ActorWeaponType._Max; i++) { // 該当武器の開放済みフラグが立っているなら1を、未開放なら0を保存 if (weaponUnlocks[i]) PlayerPrefs.SetInt (Key_WeaponUnlocks + i, 1); else PlayerPrefs.SetInt (Key_WeaponUnlocks + i, 0); } // PlayerPrefsへの変更を保存 PlayerPrefs.Save (); } } |
PlayerPrefsの各メソッドを使用することでデータの読み書きが可能になります。以下が使用するメソッドの一覧です。
機能 | 使用するメソッド | 解説 |
変数データ保存(書き出し) | SetFloat (Key, Value) SetInt (Key, Value) SetString (Key, Value) |
Key:他のキーと重複なしのstring型 Value:保存したい数値・文章データ |
保存の適用 | Save () | Set〇〇系で保存した数値データをファイルに反映させる |
変数データ取得(読み込み) | GetFloat (Key, Value) GetInt (Key, Value) GetString (Key, Value) |
Key:他のキーと重複なしのstring型 Value:キーに対応する値がセーブデータに存在しなかった場合、代わりに読み取られる数値・文章 |
全データ消去 | DeleteAll () | 1キー分のデータを消去するDeleteKeyメソッドも存在する |
SetBoolやGetBoolがあれば良かったのですが、無いので今回はSetIntとGetIntで代用しています。
また、SetIntで値を保存した後はSaveメソッドも呼ばないと保存が適用されないので注意してください。
StageManager.cs内 StageClearメソッド
Dataクラス内に用意したSaveDatasメソッドを呼び出すように、ステージクリア時の処理を追加しておきます。
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 |
/// <summary> /// ステージクリア時処理 /// </summary> public void StageClear () { // ステージクリアフラグ記録 Data.instance.stageClearedFlags[Data.instance.nowStageID] = true; // 特殊武器解放 bool gainNewWeapon = false; // 新武器獲得フラグ if (Data.instance.weaponUnlocks.Length > Data.instance.nowStageID + 1) { if (!Data.instance.weaponUnlocks[Data.instance.nowStageID + 1]) { Data.instance.weaponUnlocks[Data.instance.nowStageID + 1] = true; gainNewWeapon = true; } } // データ保存 Data.instance.SaveDatas (); // 指定秒数経過後に処理を実行 DOVirtual.DelayedCall ( 5.0f, // 5.0秒遅延 () => { // シーン切り替え if (gainNewWeapon) SceneManager.LoadScene ("WeaponGetAnimation"); else SceneManager.LoadScene ("StageSelect"); } ); } |
これでテストプレイを行うと、いずれかのステージをクリアした後に再起動した時にそのデータが受け継がれていることが確認できます。
まとめ
これでスマートフォン対応および広告の表示、データの保存等まで含め全ての工程を完了しました。大変お疲れ様でした!
本講座ではロックマン風2Dアクションゲームを作るまでに必要な流れを解説・実装してきました。
まだステージの作り等シンプルな状態ではあります。キャラクターを変更したり、ギミックを追加したりしてこのゲームにあなたのオリジナリティを足していくのも良いですし、復習も含めて1から作り直してみたり、Unity入門の森の他の講座の要素を追加してみるのも良いでしょう。
あなたの作ったゲームがリリースされることを楽しみにしています!
コメント