Unityで2Dアクションゲームを制作したときの知見
はじめに
【まずは公式リファレンスを見よう】
一番言いたいことを最初に書きました。
UnderRocketというゲーム制作を通して得た知見を(自分用のメモとして)残しておきます。
Unityを学び始めて1年未満くらいの人向けの内容です。
特に以下のようなものを実装したい人向けです
・2Dのアクションゲーム
・WebGLビルドをし、公開する
・ハイスコアを保存、共有したい
(UnderRocketは以下のサイトで遊べるので、ぜひプレイしてみてください^^)
UnderRocket | unityroom
汚いですがコードはこちら
UnderRocket GitHub
TilemapとCinemachine
ステージ外を移さないカメラ移動
Cinemachineを利用します。
インストールの仕方を調べると、AssetStoreからインストールという記事がたくさんありますが、
現在はUnityエディタからインストールできます。
一番注意する点として、カメラの枠より小さい範囲を映そうとすると上手くいきません。
ステージの大きさや、映す範囲には注意してください。
Cinemachineインストール方法
Cinemachineの基本的な使い方
Tilemapを利用する
2Dゲームで素直にsceneにgameObjectを置いてステージを作成しようとすると、だいぶ大変です。
Tilemapを利用すると、簡単に描画・Collider配置ができます。
以下の2サイトを見れば、できると思うので、利用したことがない人はぜひ
コガネブログ
Qiita
Tilemapのtileひとつひとつの間隔が空いてしまう
原因: SpritesのPixels Per Unitのサイズがタイルに合っていない
上の画像の場合は、Pixels Per Unitの値を16にする
TilemapとCinemachineを共存させる方法
Cinemachineの映す範囲を、Cinemachine ConfinerのBounding Shape2Dで指定します。
ここで、代入するものは、Tilemapとは別でステージの範囲に合わせて作成したCompositeCollider2D。
当然ですが、TilemapのcompositeColliderをカメラの枠にするとうまく動きません。
そのため、ステージの枠用のCompositeCollider2Dを別で用意する必要があるのです。
ただし、Tilemapと別ステージ枠を設定することで、本来狭すぎてCinemachineで映せなかった部分を、実際のステージより大きくすることで映すということができます(ステージ外が映ってしまいますが)。
NCMB
NCMBを利用し、オンラインランキング機能を実装する
今回オンラインランキング機能を実装するにあたり、
NCMB(Nifty Cloud Mobile Backend)というサービスを利用しました。
無料でひと月100万回APIリクエストできるすごいやつです。
NCMB公式サイト
公式のマニュアルや、記事を漁れば環境構築と基本的な実装は難しくないはずです。
クイックスタート(公式)
機能別コード例
コルーチンをいい感じに
前座: NCMBはUnityのWebGLビルドに対応しているのか
開発している途中で、実装は間違っていないはずでUnityエディタ上では動くのに、ブラウザ上では上手く動かないということがありました。
調べると「NCMBはUnityのWebGLに対応していない」とか「WebGL専用のスクリプトを用意してます」みたいなニュアンスの記事を目にします。
結論を言うと専用のスクリプトを使わなくても、WebGL版でもちゃんと動きます。
動かないとすれば、ビルドの設定やその他諸々の問題です。
createDateが参照できない
NCMBのデータストアを利用する際、最初からデータ格納日時である「createDate」とデータ更新日時である「updateDate」が用意されています。
しかし、このcreateDateが参照できなく...ない...ない...
...ありました。
自分で作成したDBでいうところのカラム(PlayerName, Scoreなど)はobj["PlayerName"]などと参照しているので、てっきりobj["createDate"]で参照できると思っていました。
obj.CreateDateでした。
この仕様は一般的なんですかね...私はわかりません。
private NCMBObject obj;
Debug.Log(obj["createDate"].ToString()); //error
Debug.Log(obj.CreateDate.ToString()); //ok
データストアへの登録が終わったかわからない
NCMBObjectに用意されているSaveAsyncを利用してデータストアへのセーブ作業をしているのですが、この関数、エラーは返しても結果は返してくれません。
SaveAsync
例えばスコアランキングを作っていたとして、
①プレイヤーのスコアをデータストアに登録
②データストアからスコアが高い順にn人取得
とやったときに①が完了する前に②をおこなうと、②の結果が変わる可能性があります。
この問題は、プレイヤーの送信予定のデータとデータストアから持ってきたデータを比較することで解決しました。
①データストアからスコアが高い順にn人取得
②n人のスコアと、プレイヤーのデータで比較、処理(クライアント側)。
③プレイヤーのスコアをデータストアに登録
実装
当たり判定を残したまま、壁で跳ね返す
これには様々な方法があると思いますが、今回はRigidbody2Dを利用した方法です。
・Rigidbody2DのBodyTypeをDynamic
・CircleCollider2DのIsTriggerをfalse
・動きの制御はRigidbody2Dで
これさえ守れば、PhysicsMaterialの値次第で様々な跳ね返りができますね。
しかし、「壁でだけ跳ね返ってほしいのに、他のgameObjectとも物理的な衝突処理がされてしまう」という問題になりがちです。
この問題の解決策のひとつとして、「壁衝突用、他衝突フラグ用でふたつのgameObjectを用意する」があります。
・壁衝突用...CircleCollider(IsTrigger = false)、Layerを壁用のLayerと検知(他不要なものは検知しない)
・他衝突フラグ用...見た目に合わせたCollider(IsTrigger = true)、Layerは特に指定なし(Layer単位で無視したいものがあれば)
Layer同士無視するかどうかは、Edit->ProjectSettings->LayerCollisionMatrixから設定できます
毎回データストアを参照しない
UnderRocketの話をすると、各ステージごとにクリアタイム順10名と最近クリア10名の名前とタイム(or日付)を表示します。
プレイヤーがミッションクリアしたときは、ランクインしている可能性があるのでデータストアから最新の情報を読み取ります。
しかし、ミッション失敗したときはどうでしょう?私は最新の情報である必要性は低いと考えました。
そのため、ランキングデータを初回取得時にクライアント側に格納し、ランキング一覧表示や失敗時には、すでにあるデータはクライアント側から、無いデータだけをデータストアからとってくるという処理にしました。
データはDictionaryとして格納
keyはミッション名(SceneManager.GetActiveScene().nameと一致)
valueはstring2つ(playerNameと、timeまたはdate)を持つクラス
プロパティもシリアライズ化したい
「プロパティだけでいいのに、SerializeFieldでInspector上から編集したいから、仕方なくフィールドを用意。プロパティの初期値をフィールドから参照。」
[SerializeField] private int hp;
public int Hp {get; private set;}
private void Awake(){
this.Hp = this.hp;
}
こういうとき、ないですか?
プロパティの初期値をSerializeFieldで変えられたら...最高ですよね?
そんな魔法がこちら↓
コガネブログ
※公式で意図されたものかわからないのでご用心
フォント
WebGLのビルドでは、Unityのデフォルトのフォント(Arial)を利用していると日本語が表示されません。
日本語に対応したフォントに変更する必要があります。
また、フォントによって大きさが違うので、表示の変化に注意してください。
フォントを一括で変更するには、以下のサイトを参考にしました↓
フォント一括変更
ツイートボタン
WebGL版でツイートする場合、現在開いているウィンドウとは別ウィンドウで開く、という処理が必要になります。
「エディタ上ではできるけど、ブラウザ上からはツイートできない」みたいな人は必見↓
Maximum call stack size exceeded
NCMBを利用して初めてのビルドをし、unityroomに反映させたら出たエラー。
当時、ロードシーンにNCMBSettings等のgameObjectや、BGMなどをロードするgameObjectを置いていました。
そしてロードシーンのStart()でタイトルシーンへ移動するようにしていたのです。
つまり、最初の処理でやることが多すぎ、ということだったんですね。
「NCMBがダメなのか?」と当時試行錯誤しましたが、NCMB関連のgameObjectをタイトルシーンに移動させることで解決しました。
フルスクリーンのときに文字の表示が違う(大きさ)
フルスクリーンにしたときに、Canvas内の見え方が変わるというお話。
今まで見えていたTextがフルスクリーンにすると範囲をはみ出て見えなくなってしまった、なんてこともありました。
Canvasの設定でなんとかできそうですが、今はどちらのサイズでも注意する、とだけしています。
どなたか知りませんか(小声)
停止処理をtimeScaleに頼らない
動作停止の簡単な実装方法としてTime.timeScaleを0にするというものがあります。
これによりUpdateは呼ばれたままで、Time.deltaTimeが0になったり、FixedUpdateが呼ばれなくなったりします。
つまり、プレイヤーの入力は受け取るまま、時間経過によるgameObjectの動きを止められます。
最初この方法を使っていたのですが、NCMBを利用するにあたって、主にコルーチンが動かしたいときに動かせない状態になってしまいました。
自作のものなら実時間で測るコルーチンを利用すれば、できなくはないのですが...。
他の機能も実装しづらくなる可能性があるので、timeScaleを使わずフラグによって処理する形にしました。
Update()の冒頭で、止まっているフラグならRigidbody2D.simulated = falseにするなどです。
Unityの便利機能
ColliderとRigidbodyのPhysicsMaterialの違い
ColliderとPhysicsの両方にPhysicsMaterialを代入できます。
ColliderとPhysicsのPhysicsMaterialには優先順位があります。
Colliderのほうが優先順位が高く、
基本となるPhysicsMaterialはRigidBodyに、特定の部分だけ変える場合はColliderに
という認識でよさそうです。
ちなみに、何も代入しない場合のデフォルトは
friction = 0.4、bounciness = 0
詳しくはこちら↓
UnityEngine.Rigidbody2D-sharedMaterial
UnityEvent
Inspector上でボタンが押されたときにする処理を設定できて、便利ですよね。
注意する点
・UnityEventにInspector上から何も代入してないときに、Invoke()で呼ぶと、エラーにならず次の処理に進む。
・呼ぶ関数の引数は1つまで
サンプルコード
using UnityEngine.Events;
/*省略*/
[SerializeField] private UnityEvent haveDamaged; //攻撃を受けた際の処理
[SerializeField] private UnityEvent destroyMe; //自身が破壊されるときの処理
[SerializeField] private UnityEvent contactEnemy; //敵にあたったときの処理
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.CompareTag("Enemy"))
{
this.contactEnemy.Invoke();
}
if (collision.CompareTag("AttackToPlayer"))
{
this.hp--;
this.haveDamaged.Invoke();
if (this.hp <= 0)
{
this.destroyMe.Invoke();
Destroy(this.gameObject);
}
}
}
スクロールビュー
Tilemap同様、使ってみたら意外と便利だったシリーズ。
大事なのは、contentにvertualGroupをいれることです。
contentの中に空のgameObject→その中に入れたいものを複数入れるという流れ
contentに入るContentSizeFitterは「Unconstrained」にしておくことで自由にwidth/heightを変えることができます
OnEnable()
gameObjectがactiveになったときに呼ばれます。
Awake()より後、Start()より前です。
OnDisable()もあります。
Update()等目立ちすぎて、影薄くなりがちですが、大変便利です。
クリックされたときに呼ばれたいが、ボタンのように選択してほしくない
これはUnderRocketの右上歯車アイコンを押すと、設定が開く、といったときの話です。
Buttonの画像だけ変えるとそれっぽくなりますが、どうしてもゲームの入力中に間違って選択してしまい、そのままEnter/Spaceで押してしまったりします。
そこで、EventTriggerをアタッチしてPointerClickの処理を追加することで、簡単にマウスクリック時の処理ができます。
知ってるかどうかの問題ですが、本当便利ですね。
なお、クリックされたかの判定は、Imageの画像の範囲に自動で合わせてくれていました。
Author And Source
この問題について(Unityで2Dアクションゲームを制作したときの知見), 我々は、より多くの情報をここで見つけました https://qiita.com/papyrustaro/items/34beb2d87ab650be94da著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .