top of page

2Dオンラインゲーム編

Lesson1 ベースを作ろう

1-0 このパートで作るゲームについて

1-0 このパートで作るゲームについて

 この「2Dオンラインゲーム編」では、Photon Unity Networking(PUN)を用いてオンラインレースゲームを作成していきます。このゲームは4人までマルチプレイが可能で、最初にゴールへ辿り着いたプレイヤーが勝利になります。

 このレッスンは2Dランゲーム編の内容を理解している前提で進みます。スプライトの扱いやTileMapについての詳しい解説は行わないので、自身のない人は都度2Dランゲーム編も確認しながら進めてください。3D脱出ゲーム編の内容も少し出てきます​。

 オンラインゲームと聞くと敷居が高く感じるかもしれませんが、難しい部分はPUN側に肩代わりしてもらうため、イメージほど難しくないはずです。とはいえ、オンラインゲーム特有の同期処理や通信処理が発生するため、オフラインゲームを一通り作れる状態になってから進めるようにしましょう(そのため、このサイトの中では上級者向けの内容になります)

​ まずはどんなゲームかイメージできるように2Dオンラインゲーム編のメインページからサンプルゲームを遊んでみてください。

1-1 新規プロジェクトを作る

1-1 新規プロジェクトを作る

 制作を始める前に、このレッスンの主軸になる「Photon Unity Networking」がどういったものか知っておきましょう。

 Photon Unity Networking(以下PUN)はUnityでマルチプレイを実装するためのフレームワークです。プロジェクトにPUNを導入することで開発者は簡単にサーバーとの通信を実装することができます。

 PUNの利点はサーバーサイドの知識をほとんど使わずにオンラインゲームの開発を行うことができる点にあります。サーバー側の管理はPUNが行ってくれるので、Unity側の実装だけでルームを作成したり、プレイヤーの情報を同期したりすることができます。

 今回はレースゲームを作成していきますが、このレッスンを応用することで様々なマルチプレイゲームを作ることができます。最初は難しく感じるかもしれませんが、慣れた後はぜひ別のジャンルのゲームでもマルチプレイを実装してみてください。

 便宜上PUNと表記していますが、この教材で使用するのは厳密にはPhoton Unity Networking2(PUN2)です。旧式のPUNから処理速度や利便性を向上したものがPUN2になります。

 現在旧式のPUNを使うことはほぼないと思われますので、2つの違いについてはあまり気にしなくても大丈夫です。ただし、インターネットで古いPUNの記事を読む場合は注意してください。

 実はPUN2はすでにアップデートが終了しており、現在は後続である「Photon Fusion」に切り替わっています。ですが2023年11月現在、Fusionの配信開始が2022年であり情報が少ないことや、頻繁に更新が入り教材の正確さを保てないことからこの教材ではPUN2を使用しています。

​(余裕があればFusion版教材も作るかも…)

Unity Tips!

 PUNの具体的な概念についてはその都度説明していきます。

 まずは新しいプロジェクトを作成しましょう。

​ Unity Hubから「新しいプロジェクト」を選択し、「2D(URP)」を選択してください。

 このテンプレートは名前の通りURPの2D版になります。URPの詳細は3D脱出ゲーム編1-1を確認してください。

​ わかりやすいプロジェクト名を決めたら、右下のボタンからプロジェクトを作成しましょう。

 プロジェクトを作成できたら、いつも通り画面サイズを16:10に設定しておいてください。

 次にパッケージを導入しましょう。

​ 今回のレッスンのプレイヤーは2D Animationを使用しています。これはスプライトにボーンを入れることで画像を動かすことができる機能です。ざっくり言うとUnity版のLive2Dになります。

​(画像元サイト

 「2Dを動かしてみたい」「2D Animationについて詳しく知りたい」という方はこちらのパッケージをインポートして、このまま進めてください。こちらのパッケージには素材の画像のみが入っており、1-2でアニメーションを作成する必要があります。

【パッケージ①】

https://drive.google.com/file/d/1NAITApOrXVteI82ckyXkcQRuhqkNUgO3/view?usp=sharing

 「2D Animationは面倒だからやりたくない」「早くオンラインゲーム部分に着手したい」という方はこちらのパッケージをインポートして、1-3まで飛ばしてください。こちらのパッケージには1-2の内容で作成するアニメーションの完成版が入っています。

【パッケージ②】

https://drive.google.com/file/d/1VJ20pP9hJwpL0uQpGDDC3x0VyDh4x1aC/view?usp=sharing

 パッケージ①をインポートした方はLesson1-2へ、パッケージ②をインポートした方はLesson1-3へ進んでください。

1-2 2D Animationの作成

1-2 2D Animationの実装

 導入したパッケージにはプレイヤーの​パーツ画像をまとめたpsbファイルが入っています。

 psb形式はPhotoshop独自のファイル形式で、png形式のような1枚絵ではなくレイヤーの情報を含んだファイルになっています。

 2D Animationを扱うときはまず、パーツをレイヤー別に分けたpsbファイルを用意してください。

(psdファイルではなくpsbファイルなので注意!)

 Photoshopを持っていない方向けに、Photoshopを使わずにpsbファイルを用意する流れを解説します。​

 まずは無料のペイントツールMediBang Paint(メディバン ペイント)をインストールしてください。

 インストールできたらMediBangを開いて、パーツをレイヤーに分けて絵を描いてください。

【注意点】

・レイヤー名は英語にしておく(名前が被ると判別しにくいのでできるだけ名前を分ける)

​・動かした際に切れ目が見えないように、パーツの見えない部分も余分に描いておく

 「ファイル」→「名前をつけて保存​」を選択してください。

 ファイルの種類をPSDにして保存してください。

 ファイルの拡張子をpsdからpsbに変更してください。

​ エラーが出るように見えるかもしれませんが、問題なく変換できます。

 完成したpsbファイルをUnityのプロジェクトへドラッグ&ドロップすることで、Unityで使用することができます。

Unity Tips!

 まずはスプライトにボーン(骨)を入れていきましょう。基本的な考え方は3Dモデルと同じです。

​ 「Sprite」→「Player」フォルダを開いて、Player1のSprite Editorを開いてください。

 Sprite Editor左上のプルダウンメニューから「Skinning Editor」を開いてください。

 左側のメニューから「Create Bone」を選択してください。

 まずはボーンの基点となる短いボーンをプレイヤーの足元に追加しましょう。

※ ボーンが見えない場合は右上の「Visibility」を選択して、スライダーを調整することでボーンの不透明度を変更できます

 全てのボーンの最上位に位置するルートボーンを作ることは、アニメーション制作の基本的な手法です。ルートボーンを作ることには「ルートボーンを操作することでモデル全体の座標や回転を調整するたことができる」というメリットがあります。

 ルートボーンから体と頭に対応するボーンを伸ばしていってください。

​ 連続でボーンが作られていきますが、ボーン作成を止めるときは右クリックしてください。

 ルートボーンの末端から左上に伸ばして、右手のボーンを作ってください。

​ 座標は後で調整するので、多少ずれても問題ありません。

4_1_10

 同じように左手のボーンも作りましょう。

 左手は体に隠れて見えませんが、大体の位置で大丈夫です​。

 ルートボーンの始点から右足、左足のボーンを伸ばしてください。

4_1_11

 ボーンの座標を調整します。

 左側のメニューから「Edit Bone」を選択して、手のボーンを画像に合う位置に移動させてください。

​ 左手は体に隠れて見えないので、右側のメニューからBodyを一時的に非表示にするなどして調整しましょう。

 作成したボーンに名前をつけておきましょう。名前をつけておくことで、後でアニメーションを作成する際に調整しやすくなります。

 (表示されていない場合)右上の「Visibility」ボタンを押してVisibilityウィンドウを開いてください。

 BoneとSpriteのボタンがありますがBoneに切り替えてください。

​ 右下のBoneウィンドウから名前を変更できるので、自分で管理しやすいように名前をつけておきましょう。

 次にジオメトリを生成します。ジオメトリは「幾何学」「構造」といった意味の単語ですが、ゲームでは「形状データ」のことを指します。ざっくり言うと画像の頂点データのことです。

 左側のメニューから「Auto Geometry」を選択して、右下の「Generate For All Visible​」ボタンを押してください。自動でジオメトリが生成されます。

1_1_15

 ジオメトリを生成すると、プレイヤーの画像がカラフルに色付けされます。

 これは「どの頂点がどのボーンの影響をどの程度うけるか」を視覚的に示した情報になります。

​ 例えば現在の状況ではプレイヤーの背中は青く染まっています。これは青いボーンの影響を強く受けるということになります。

4_1_16

 この状態でボーンを動かすと、画像も動くようになっています。

 しかし、右手のボーンを動かすと、影響を受けてほしくない体の画像まで影響を受けてしまい、画像がおかしくなってしまうと思います。

 次はパーツとボーンの影響を設定していきましょう。

※ 左側のウィンドウの「Restore Pose」ボタンでボーンを元の状態に戻せます

4_1_17

 左側のメニューから「Sprite Influence」を選択してください。

 次に、右手のボーンを選択しましょう。

 右下のウィンドウに選択中のボーンの影響を受けるパーツが表示されるので、右手の場合は「Hand」だけ残して、他は削除してください。

4_1_16

 他のボーンも影響を調整して、表示がおかしくならないように設定していきましょう。

​ 基本的に各パーツに対応したボーンだけ設定すればOKです。

​ プレイヤーを動かして、画像が破綻しなくなったら完成です。

4_1_17

 最後に右上のApplyボタンを押すのを忘れないようにしてください。

 より詳細に影響を設定したい上級者向けの機能を紹介しておきます。

 「Create Vertex」から頂点を作成することができます。自動で作成される頂点ですが、画像が破綻してしまう場合は手動で調整してください。

4_1_EX2

 他にも「Weight Slider」では「頂点がどのボーンの影響をどの程度受けるか」を設定することができます。​こちらも画像が破綻してしまう場合に調整してください。

 今回はシンプルな画像を扱うので使いませんでしたが、複雑な画像をアニメーションさせる際はこれらの設定も行ってみてください。

Unity Tips!

 次にプレイヤーの表情を切り替えるための表情リストを作ります。

 「Create」→「2D」→「Sprite Library Asset」を選択してください。

 後ほど残りのプレイヤーの分も作成するため、区別できるように名前をつけてください。

 作成したSprite Library Assetを選択して、以下の措置を行ってください。

 

・Categoryを「Head」にする

・Entryを2つにして、1つ目の名前を「Normal」にしてプレイヤーの通常時の表情を設定する

2つ目の名前を「Damage」にしてプレイヤーの被弾時の表情を設定する

 次はアニメーションを作ります。

 ドラッグ&ドロップでシーンにプレイヤーを配置してください。ボーンがしっかり設定できていれば、シーンにもボーンが表示されるはずです。

 Animationフォルダを作成してください。

 Animationフォルダ内にIdleアニメーションを作成して、プレイヤーにドラッグ&ドロップしてください。

 アニメーションを作る時はレコーディングモードにすると便利です。まずは待機のアニメーションを作ってみましょう。

① Animationウィンドウ左上のレコーディングモードボタンを押す

② キーを設定したい場所にバーを移動させる

​③ ボーンを操作して、ポーズを設定する

 アニメーションに答えはないので、皆さんの自由に待機アニメーションを作ってみてください。

​ 筆者の個人的な意見としてはアニメーション制作の際、

・足など動くと困るボーンを除いて、手や頭など、他のボーンはできるだけ常に動かした方が自然

・アクション系(攻撃など)のアニメーションはできるだけ短く、衝撃の瞬間や反動を意識する

・実際に自分の体を動かして、どういったアニメーションが自然か見てみる

 といった点を意識しています。

 自然にループするためには最初と最後のキーを合わせる必要があります。

​ 0フレーム目のキーを選択してCtrl+Cでコピー、最後のフレームに移動してCtrl+Vで貼り付けてください。

 表情の設定を行えるようにします。

 プレイヤーにAdd Componentから「Sprite Liberary」をアタッチしてください。

​ Sprite Library Assetには先ほど作成した表情リストを設定します。

 顔のスプライトを選択して「Sprite Resolver」をアタッチしてください。

​ これでプレイヤーの表情を簡単に切り替えられるようになります。

 使わない方の顔は非アクティブにしておきましょう。

 レコーディングモードでSprite Resolver内の画像をクリックすると、表情を変更するためのキーが設定されます。

​ Idleの場合はNormalを選択してください。

 注意する点として、この先作成する全てのアニメーションで表情の設定を行うようにしてください

 表情を設定しないと、例えば気絶アニメーションで表情をDamageに切り替えた後、Idleアニメーションに戻った際に表情が戻らなくなってしまいます。

 アニメーションウィンドウ左上から「Create New Clip」を選択してRunアニメーションを追加してください。

 こちらも同じようにボーンを動かしてアニメーションを作ってみてください。

 走るアニメーションは作るのが難しいですが、まずは足だけ動かして「右足を上げた状態」「左足を上げた状態」の2つのポーズを作ってみましょう。その後に手を振ったり、体を前後に揺らすようにすると自然になります。

 「待機(Idle)」「走る(Run)」「ジャンプ待機(Jump)」「ジャンプ中(Jumping)」「Stun(気絶)」「勝利(Win)」アニメーションを作ってください。

​ 注意としてJumpアニメーションはJumpingアニメーションへの繋ぎになるためジャンプする瞬間だけ作成してください。Jumpアニメーションのみループは不要です。

 Stun(気絶​)アニメーションでは表情を切り替えるのを忘れないようにしましょう。

 サンプルでは以下のようなアニメーションになっています。

​ あくまで参考なので、各アニメーションは皆さんの自由に作ってみてください。

待機(Idle)

ジャンプ開始(Jump)

4_1_Jump

気絶(Stun)

4_1_Stun

走る(Run)

4_1_Run

ジャンプ中(Jumping)

4_1_Jumping

勝利(Win)

4_1_Win

 作成したアニメーションを選択して、Loop Timeにチェックを入れてください。

​ ただし、Jumpアニメーションだけはチェックを入れない(ループしない)設定にしてください

 アニメーションが作成できたら、次はAnimator Controllerの設定をします。

​ プレイヤーにAnimationをドラッグ&ドロップした時に自動で作成されたAnimator Controllerをダブルクリックしてください。

 Animatorウィンドウを開いたら、今まで作成したアニメーションを見やすい配置に並び替えてください。

 遷移条件となるパラメータを作成しましょう。

 注意点として、この後扱うPUNではAnimatorのパラメータを自動で同期する機能がありますが、Trigger型には対応していません。PUNで同期するAnimatorにTrigger型を使わないようにしてください

​ Bool型のパラメータ「Run」「Jump」「Stun」「Win」を作成してください。

 次にトランジションを作成しましょう。

 Trigger型を扱えないため複雑になってしまいますが、順番に設定していきましょう。数が多いので難しそうに見えますが、2Dランゲーム編1-7の設定と流れはほぼ同じです。

​ IdleステートからRunステートに繋がるトランジションを作成してください。

・Has Exit Timeのチェックを外す

・Transition Duration を0.1にする

 これらの設定はJump→Jumpingの遷移以外で共通になります

​ Conditions(遷移条件)は「Runがtrueの時」に設定してください。

4_1_40

 IdleステートからJumpステートに繋がるトランジションを作成してください。

​ Conditions(遷移条件)は「Jumpがtrueの時」に設定してください。

4_1_41

 IdleステートからStunステートに繋がるトランジションを作成してください。

​ Conditions(遷移条件)は「Stunがtrueの時」に設定してください。

4_1_42

 IdleステートからWinステートに繋がるトランジションを作成してください。

​ Conditions(遷移条件)は「Winがtrueの時」に設定してください。

4_1_43

 他のステートからの遷移も設定していきます。

 (流れは同じなので一部は省略して解説していきます)

 RunステートからIdleステートに繋がるトランジションを作成してください。

​ Conditions(遷移条件)は「Runがfalseの時」に設定してください。

4_1_44

 RunからJumpへの遷移(条件はJumpがtureの時)、RunからStunへの遷移(Stunがtrueの時)、RunからWinへの遷移(Winがtrue)の時 も同じように設定してください​スクリーンショットは省略します。

​ 次にJumpからJumpingへの遷移を設定します。Jumpからの遷移先はJumpingのみです

・Has Exit Timeにチェックを入れる

・Exit Timeを1にする

・Transition Durationを0.1にする

・Conditions(遷移条件)に何も設定しない

 この設定によって、Jumpアニメーションが終了すると自動でJumpingアニメーションに切り替わるようになります。

4_1_45

 JumpingステートからIdleステートに繋がるトランジションを作成してください。

​ Conditions(遷移条件)は「Jumpがfalseの時」かつ「Runがfalseの時」に設定してください。遷移条件が2つあるので注意しましょう。

4_1_46

 JumpingステートからRunステートに繋がるトランジションを作成してください。

​ Conditions(遷移条件)は「Jumpがfalseの時」かつ「Runがtrueの時」に設定してください。こちらも遷移条件が2つあります。

4_1_47

 JumpingからStunへの遷移(Stunがtrueの時)、JumpingからWinへの遷移(Winがtrue)の時 も同じように設定してください​スクリーンショットは省略します。

 RunステートからIdleステートに繋がるトランジションを作成してください。

​ Conditions(遷移条件)は「Stunがfalseの時」かつ「Runがfalseの時」に設定してください。

4_1_48

 StunからRunへの遷移(Stunがfalseの時かつRunがtrueの時)、StunからWinへの遷移(Winがtrue)の時 も同じように設定してください。StunからJumpの遷移はなくても問題ありません。​スクリーンショットは省略します。

 見た目は複雑になってしましたが、これで遷移の設定が完了しました。

4_1_49

 最後に残りの2P、3P、4Pの設定を行います。

 2Pに再び手動でボーンを設定…という訳にもいかないので、1Pの設定をコピーしましょう。

 最初にボーンを作成したプレイヤー画像のSpriteEditorを開いてください。

​ ボーンを全て選択して、左側のメニューから「Copy Rig」を選択してください。

 2PのSprite Editorを開いてください。

 左側のメニューから「Paste Rig」を選択して、右下のPasteボタンでボーンとメッシュを貼り付けてください。

​ これで1Pと同じ設定が反映されます。

 3Pと4Pにも同じように設定を貼り付けてください。

 Sprite Library Assetは1Pのものをコピーしてください。

 他の設定は変えずに画像だけを2P、3P、4Pのものにそれぞれ差し替えてください。

 2P、3P、4Pをシーンに追加してください。

 1Pと同じように設定していきます。

 プレイヤー本体にAnimatorと作成したAnimator Controllerを設定してください。

​ Sprite Libraryもアタッチし、Sprite Library Assetを対応したものに変更してください。

 頭の画像にはSprite Resolverを設定するのを忘れないようにしましょう(1Pの設定を参考に)

 これでプレイヤーの準備が完了しました。2Pや3Pのアニメーションも1Pのものを使いまわしているので、実際に動かして確認してみてください。

​ 2D Animation は比較的簡単に画像を動かすことができるので、ぜひ自分で描いた絵のアニメーションにも挑戦してみてください。

 より高度な演出でスプライトを動かしたい場合はLive2DSprite Studioなど専用のツールを使ってアニメーションを制作し、それをUnityで実行するということもできます。

 例えばSprite StudioではUnityにはないパーティクル生成やマスクなどの機能を用いて、以下の映像のように複雑な画像操作を行うこともできます。

​(ついでにSprite Studioは個人利用なら全ての機能を無料で使えるので、筆者オススメのツールです)

Unity Tips!

1-3 プレイヤーを移動させる

1-3 プレイヤーを移動させる

 オンラインどうこうの話は一旦置いておいて、まずはオフラインでプレイヤーの移動やジャンプをできるようにしていきましょう。

 オンラインゲームを作る時はいきなりオンラインの処理を作るのではなく、オフラインで最低限の処理を作ってから同期処理などを実装するようにしましょう基本的な流れは3Dアクションゲーム編や2Dランゲーム編と同じになるので、解説は軽めに進めていきます。

 まずはシーン内のオブジェクトがPenguin1(1P)、Main Camera、Global Light 2D だけになるようにしてください(1-1でパッケージ②をインポートした場合は最初からこの状態になっています)

 まずはプレイヤーを空オブジェクトの子オブジェクトにします。後ほどプレイヤーを差し替えやすくするための措置です。

​ 空オブジェクトを作成して、プレイヤー(Penguin1)を子オブジェクトにしてください。

 追加した空オブジェクト(以下Player)にはPlayerタグを設定してください。

 Penguin1の座標と大きさを調整しましょう。

 Position : X=0 Y=-0.5 Z=0

​ Scale     : X=0.5 Y=0.5 Z=0.5

 PlayerにCapsuleCollider2D とRigidbody2D をアタッチしてください。2Dゲームなので最後に2Dがついている方を選びましょう。

 それぞれの項目を設定していきます。

【Capsule Collider 2D】

・Offeset : X=0 Y=1

・Size     : X=1 Y=2.4

​【Rigidbody 2D】

・Linear Drag(空気抵抗): 0.2

・Gravity Scale(重力の強さ): 4

Constraints

​・Freeze Rotation : Zにチェック

 2Dランゲーム編と同じように仮の地面を作ります。

 「2D Object」→「Sprites」→「Square」を追加して、BoxCollider2Dをアタッチしてください。

​ 地面になるように座標や大きさを調整したら、ゲームを実行しましょう。プレイヤーが地面に着地したらOKです。

 プレイヤーの移動を実装していきましょう。今回はアニメーションの処理も同時に行います。

​ まずはScriptフォルダを作成してください。

 Scriptフォルダ内にPlayerスクリプトを作成して、以下のように入力してください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

 

public class Player : MonoBehaviour
{
    [Header("【メインパラメータ】")]
    [SerializeField, Header("移動速度")]
    float MoveSpeed = 0.004f;
    [SerializeField, Header("移動速度上限")]
    float LimitSpeed = 0.2f;

 

    // 現在のX方向への移動速度
    float m_nowMoveSpeed = 0.0f;
    // 減速用に乗算する値
    const float MOVE_DRAG = 0.98f;

 

    // キャッシュ
    Rigidbody2D m_rigidbody2D;
    Animator m_animator;

 

    void Awake()
    {
        // 自身にアタッチされているRigidbodyを取得
        m_rigidbody2D = GetComponent<Rigidbody2D>();
        // 自身の子オブジェクトからAnimatorを検索して取得
        m_animator = GetComponentInChildren<Animator>();
    }

 

    void FixedUpdate()
    {
        // 移動量計算
        if (Input.GetKey(KeyCode.D))
        {
            m_nowMoveSpeed += MoveSpeed;
        }
        else if (Input.GetKey(KeyCode.A))
        {
            m_nowMoveSpeed += -MoveSpeed;
        }
        else
        {
            // 入力がない場合は減速する
            m_nowMoveSpeed *= MOVE_DRAG;

        }

        // 移動速度制限
        m_nowMoveSpeed = Mathf.Clamp(m_nowMoveSpeed, -LimitSpeed, LimitSpeed);

        // 移動する
        transform.position += new Vector3(m_nowMoveSpeed, 0.0f, 0.0f);

 

        // 走るアニメーション
        if (Mathf.Abs(m_nowMoveSpeed) > 0.1f)
        {
            m_animator.SetBool("Run", true);
        }
        else
        {
            m_animator.SetBool("Run", false);
        }
    }

 

    void Update()
    {
        // 移動速度に応じて画像を反転
        if (m_nowMoveSpeed >= 0.0f)
        {
            Vector3 scale = transform.GetChild(0).transform.localScale;
            scale.x = Mathf.Abs(scale.x);
            transform.GetChild(0).transform.localScale = scale;
        }
        else
        {
            Vector3 scale = transform.GetChild(0).transform.localScale;
            scale.x = -Mathf.Abs(scale.x);
            transform.GetChild(0).transform.localScale = scale;
        }
    }

}

【プログラムの解説】

GetComponentInChildren関数では自身の子オブジェクトにアタッチされているコンポーネントを検索、取得できます。今回は自身の子オブジェクトにアタッチされているAnimatorコンポーネント、つまりプレイヤーのAnimatorを取得しています。

 複数の子オブジェクトがあり、それぞれに同じ種類のコンポーネントがアタッチされている場合は最初に見つかったコンポーネントを返すので注意しましょう。

 「自分の子オブジェクトにアタッチされているAnimatorコンポーネントを全て取得したい!」という場合は

 Animator[] animators = GetComponentsInChildren<Animator>();
​ といったコードを記述することで全て配列で取得できます。

・AキーかDキーの入力を受けて移動速度を少しずつ加算することで、徐々に加速する処理を行っています。入力がない場合は移動速度に​0.98(MOVE_DARG変数)を乗算することで、徐々に減速することができます。

​ このゲームは氷のステージを走るので、慣性をかけることで氷の滑る感覚を演出しています。

・Mathf.Abs は絶対値を返す関数です。

 移動速度の絶対値を用いることで「左右関係なく一定以上の移動速度があるかどうか」を条件にしています。

 

 コードが書けたら保存して、プレイヤーにアタッチしてください。

 ゲームを実行して、AキーとDキーで移動できることを確認してください。入力を止めてもしばらくは慣性で滑ります。

 次にジャンプの処理を実装します。

​ まずは接地判定用の空オブジェクトを作成して、Playerの子オブジェクトにしてください。

※ 反転処理で「0番目の子オブジェクトを反転させる」処理を行っているため、接地判定オブジェクトはPenguin1の下になるようにしてください!

 接地判定用オブジェクトにCircleCollider2Dをアタッチしてください。

 IsTriggerにチェックを入れ、Radius(半径)は0.1に設定します。

 また、接地判定用オブジェクトの座標をX=0 Y=-0.2 Z=0 にしてください。

 これでプレイヤーの足元に丸い当たり判定が追加されます。

4_1_69

 接地判定のスクリプトを用意しましょう。

​ GroundCheckスクリプトを作成して、以下のように入力してください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

 

public class GroundCheck : MonoBehaviour
{
    // 接地しているかどうか
    bool m_isGround = false;
    public bool GetIsGround()
    {
        return m_isGround;
    }

 

    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Ground"))
        {
            m_isGround = true;
        }
    }

 

    private void OnTriggerExit2D(Collider2D collision)
    {
        if (collision.CompareTag("Ground"))
        {
            m_isGround = false;
        }
    }
}

 この教材では恒例になっている(?)接地判定のスクリプトです。Groundタグがついたオブジェクトに触れている間だけm_isGroundがtrueになります。

​ コードが書けたら保存して、接地判定用オブジェクトにアタッチしてください。

 作成した接地判定を用いて、プレイヤーのジャンプ処理を実装しましょう。ただしここではジャンプの関数を実装するだけで、実際にジャンプ用の関数を呼ぶ処理はアニメーションイベントで行います。

​ Playerスクリプトを開いて、赤い部分のコードを追加してください。青い部分は穴埋めです。
【ヒント①】Animatorを取得しているコードを参考にしてください

【ヒント②】ジャンプはプレイヤーに対して上方向に瞬間的に力を加える必要があります

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    [Header("【メインパラメータ】")]
    [SerializeField, Header("移動速度")]
    float MoveSpeed = 0.004f;
    [SerializeField, Header("移動速度上限")]
    float LimitSpeed = 0.2f;
    [SerializeField, Header("ジャンプ力")]
    float JumpPower = 20.0f;

 

    // 現在のX方向への移動速度
    float m_nowMoveSpeed = 0.0f;
    // 減速用に乗算する値
    const float MOVE_DRAG = 0.98f;
    // ジャンプ中かどうか
    bool m_isJump = false;
    // 空中にいるかどうか
    bool m_isAir = false;

 

    // キャッシュ
    Rigidbody2D m_rigidbody2D;
    Animator m_animator;
    GroundCheck m_groundCheck;

 

    void Awake()
    {
        // 自身にアタッチされているRigidbodyを取得
        m_rigidbody2D = GetComponent<Rigidbody2D>();
        // 自身の子オブジェクトからAnimatorを検索して取得
        m_animator = GetComponentInChildren<Animator>();
        // ① 自身の子オブジェクトからGroundCheckを探して取得
        (ここに入力)
    }


​~後略~

~前略~

        else
        {
            m_animator.SetBool("Run", false);
        }
    }

 

    void Update()
    {
        // 接地している時にEnterキーが押されたらジャンプする
        if (Input.GetKeyDown(KeyCode.Return) &&
            m_groundCheck.GetIsGround() && 
            m_isJump == false)
        {
            m_animator.SetBool("Jump", true);
            m_isJump = true;
        }
        // 空中判定
        if (m_isJump && m_groundCheck.GetIsGround() == false)
        {
            m_isAir = true;
        }
        // 接地時にアニメーションを戻す
        if (m_isAir && m_groundCheck.GetIsGround())
        {
            m_isJump = false;
            m_isAir = false;
            m_animator.SetBool("Jump", false);
        }

 

        // 移動速度に応じて画像を反転
        if (m_nowMoveSpeed >= 0.0f)
        {
            Vector3 scale = transform.GetChild(0).transform.localScale;
            scale.x = Mathf.Abs(scale.x);
            transform.GetChild(0).transform.localScale = scale;
        }
        else
        {
            Vector3 scale = transform.GetChild(0).transform.localScale;
            scale.x = -Mathf.Abs(scale.x);
            transform.GetChild(0).transform.localScale = scale;
        }
    }

 

    public void Jump()
    {
        // ② プレイヤーをジャンプさせる

        (ここに入力)
    }
}

 接地判定が動作するように、仮の地面にGroundタグを作成、設定しましょう。

(タグの設定については3Dアクションゲーム編1-6参照)

 プレイヤーのジャンプアニメーションは数フレーム屈んで溜めた後にジャンプするようになっています。屈んでいる途中でジャンプすると不自然なアニメーションになってしまうため、「溜め」のアニメーションが終わった瞬間にジャンプするようにしましょう。

 まずはPlayerAnimationEventスクリプトを作成して、以下のように入力してください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

 

public class PlayerAnimationEvent : MonoBehaviour
{
    public void PlayerJump()
    {
        // 自分の親オブジェクトにアタッチされている
        // Playerコンポーネントからジャンプ関数を呼び出す

        transform.parent.GetComponent<Player>().Jump();
    }
}

【プログラムの解説】

・自身の親オブジェクトをtransform.parnetで取得し、それに対してGetComponentを行っています。

 ちなみにtransform.root では自身の一番上の親オブジェクトを取得、transform.parnet では自身の1つ上の親オブジェクトを取得できます。

​ 例えば以下の画像のような親子関係が構築されていた場合、オブジェクトCのparnetはオブジェクトBですが、オブジェクトCのrootはオブジェクトAになります。

 コードが書けたら保存して、Penguin1にアタッチしてください(Animatorがついているオブジェクト)

 次にアニメーションイベントの設定をします。

 アニメーションイベントはアニメーションの任意のフレームで関数を呼び出すことができる機能です。例えば、アニメーションで足を地面に着けた瞬間に足音を鳴らす関数を実行することで、足音を実装する…なんてこともできます。

​【アニメーションイベントを用いたLessonEX】剣を振る/ボールを投げる

 

 Animationフォルダ内にあるJumpアニメーションを開いてください。

 Jumpアニメーションの12フレーム目が屈むアニメーションが終わる瞬間になります。12フレーム目にアニメーションイベントを追加しましょう。

 アニメーションイベントを選択して、インスペクターから呼び出す関数を指定しましょう。

 「(No Function Selected)」→「PlayerAnimationEvent」→「Methods」→「PlayerJump()」と選択してください。

​ これでJumpアニメーションの12フレーム目でジャンプ関数が実行されます。

 これでジャンプ処理が完成しました。

​ ゲームを実行して、Enterキーでジャンプできることを確認してみてください。

1-4 カメラを追尾させる

1-4 カメラを追尾させる

 次にプレイヤーに対してカメラを追尾させます。

 こちらも2Dランゲーム編と流れは同じなので、サクッと実装しましょう。

​ GameCameraスクリプトを作成して、以下のように入力してください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

 

public class GameCamera : MonoBehaviour
{
    [SerializeField, Header("追尾対象")]
    GameObject TargetObject;
    public void SetTargetObj(GameObject target)
    {
        TargetObject = target;
    }

 

    [SerializeField, Header("座標補正")]
    Vector3 Offset = Vector3.zero;
    [SerializeField, Header("上限座標")]
    Vector2 MaxPosition;
    [SerializeField, Header("下限座標")]
    Vector2 MinPosition;

 

    private void LateUpdate()
    {
        // ターゲットがいないなら何もしない
        if (TargetObject == null)
        {
            return;
        }

 

        // 対象に注目する
        transform.position = TargetObject.transform.position + Offset;

 

        // 座標制限
        Vector3 cameraPos = transform.position;
        cameraPos.x = Mathf.Clamp(cameraPos.x, MinPosition.x, MaxPosition.x);
        cameraPos.y = Mathf.Clamp(cameraPos.y, MinPosition.y, MaxPosition.y);
        transform.position = cameraPos;
    }
}

 シンプルに対象のオブジェクトに注目し、座標が指定した範囲外に出ないように制限するスクリプトです(ほぼ2Dランゲーム編のカメラと同じ)

 コードが書けたら保存して、メインカメラにアタッチしてください。

​ インスペクターにパラメータが表示されているので、それぞれ設定しましょう。

​【サンプルの入力例】

 追尾対象 : プレイヤーを設定

 座標補正 : X=6 Y=2 Z=-10

 上限座標 : X=380 Y=10

 加減座標 : X=0 Y=0

 Cameraのサイズを8に調整しておいてください。​マルチプレイなので他のプレイヤーが見えやすいように少し広めに調整しています。

 これでカメラがプレイヤーを追尾するようになりました。

 上限座標や下限座標はステージに合わせて随時お好みで調整してください。

1-5 ステージを作る

1-5 ステージを作る

 次にタイルマップを使ってステージを作ります。

 こちらも2Dランゲーム編2-1とほぼ同じ内容のため、軽い解説で進めていきましょう。

 「Sprite」→「2D Ice World」フォルダには、既にタイルパレットが設定されています。

​【素材元】https://assetstore.unity.com/packages/2d/environments/2d-ice-world-106818

 タイルマップを追加しましょう。

 ヒエラルキーから「2D​ Object」→「Tilemap」→「Rectangular」を追加してください。正方形のグリッド状のタイルマップが追加されます。

 「Window」→「2D」→「Tile Pallet」を選択してください。

 タイルパレットが開くので、タイルを選択して簡単なステージを作ってみてください。短くても大丈夫です。

 今回この教材では坂道対応をしていないので、坂道の素材を使いたい場合は2Dランゲーム編1-6を参考にして各自で実装してください。

 タイルマップに当たり判定がないのでTimemap Collider 2Dをアタッチしましょう。

​ 地面として判別できるようにGroundタグもつけておいてください。

 簡単なステージを作って、試しに動かしてみてください。

​ ステージの当たり判定や接地判定が動作していればOKです。

 サンプルではカウントダウン時にそれぞれのプレイヤーが待機できる場所を用意しています。形状は自由ですが、1Pから4Pまでが待機できる場所を作成しておいてください。

 背景を追加しましょう。

 「Sprite」→「2D Ice World」→「Assets」→「Sprites」フォルダにある背景用スプライトIceworld_Background_0001とIceworld_Background_0002のうち好きな方を選んで、シーン上にドラッグ&ドロップしてください。

 追加した背景画像の座標や大きさを調整してください。

 Draw ModeをTiledに変更してWidthの値を調整すると、画像が左右にループするようになります。ステージ全体を覆う長さに調整してください。

​ また、Order in Layerを調整してステージより後ろになる値(-5など)に設定しましょう。

 今回はパッケージ側で既に設定してありましたが、DrawModeをTiledにして画像をループ表示するためには、元画像のMesh Typeを「Full Rect」に設定する必要があるので注意してください。

Unity Tips!

 背景画像のさらに後ろはカメラの背景色を使用しましょう。

​ メインカメラのEnvironmentのBackgroundで空の色を指定してください。無地のシンプルな背景ですが、このレッスンではこれで進めていきます。こだわりたい人はお好みの背景画像を設定しても構いません。

1-6 PUNを導入する

1-6 PUNを導入する

 ここまではマルチプレイは一旦無視して、シンプルなアクションゲームのベースを作ってきました。

 このレッスンから、いよいよオンラインゲームとして必要な機能を実装していきます。

​ まずはPhotonへアカウント登録をしましょう。

 下記サイトにアクセスするか「Photon」で検索して、アカウントの登録を行ってください。

【Photonサイト】https://www.photonengine.com/ja-jp

 アカウントの作成からメールアドレスを登録してください。

 登録したメールアドレスにメールが来ます。URLからパスワードの設定を行いましょう。

 アカウントの作成ができたら、ダッシュボードに切り替えてください。

 ダッシュボードを開いたら「新しくアプリを作成する」ボタンをクリックしてください。

 アプリケーションタイプは「マルチプレイヤーゲーム」を選択します。

 Photonの種別は「Pun」に設定してください。

​ アプリケーション名は自分が区別しやすい名前を入力しましょう。

​ 他の項目は未入力でOKです。

 「作成する」ボタンを押すと指定した名前のアプリケーションが作成されます。

​ この画面は後で使うので、開いた状態で次の措置を行ってください。

 次にプロジェクト側にPUNを導入します。

​ 下記URLからアセットをインポートしてください。

​【PUN 2 - FREE】https://assetstore.unity.com/packages/tools/network/pun-2-free-119922

 Unityが自動で開くので、PUNのインポートを行ってください。

​ 流れは教材のパッケージをインポートする時と同じです。

 先ほどPhotonのサイトで作成したアプリケーションのIDをコピーして、AppIDにペーストしてください。これでPhotonの導入は終了です。

 いよいよオンラインゲームを作っていきます。まずはプレイヤーの同期を行いましょう。

 いきなり同期処理と言われてもイメージが湧かないかもしれませんが、皆さんが普段遊んでいるオンラインゲームと流れは同じです。

 PUNには「ルーム」という機能があります。Photonのサーバー上に仮想の部屋を作るようなイメージです。​そして、同じルームにプレイヤーが参加することで、初めて同期の処理を行うことができます。

 上の図ではルーム1、ルーム2、ルーム3はそれぞれ独立しており、基本的に干渉し合うことはありません。

 プレイヤーは人数が空いているルームに入ることで、他の参加者の情報を取得し、自分の情報を共有することができます。

 ざっくりと流れがわかったところで、早速ルームに入ってみましょう。

​ 新しいスクリプトGameManagerを作成して、以下のように入力してください。

using System.Collections;
using System.Collecti
ons.Generic;
using UnityEngine;
using Photon.Pun;       // PUNを使うために必要
using Photon.Realtime;  // PUNを使うために必要

 

public class GameManager : MonoBehaviourPunCallbacks    // 継承する
{
    void Awake()
    {
        // Photonのサーバーに接続する
        PhotonNetwork.ConnectUsingSettings();
    }

 

    // サーバーへ接続した瞬間に呼ばれる関数
    public override void OnConnectedToMaster()
    {
        // ルーム設定の作成
        RoomOptions roomOptions = new();
        roomOptions.MaxPlayers = 4;

 

        // 部屋に入る(同名の部屋がなかったら作る)
        PhotonNetwork.JoinOrCreateRoom("GameRoom",
            roomOptions,
            TypedLobby.Default);
    }

 

    // 自分がルームへ参加した瞬間に呼ばれる関数
    public override void OnJoinedRoom()
    {
        Debug.Log("ルームに参加しました");
    }

 

    // 新規でルームにプレイヤーが参加した瞬間に呼ばれる関数
    public override void OnPlayerEnteredRoom(Photon.Realtime.Player newPlayer)
    {
        Debug.Log(newPlayer.ActorNumber + "番のプレイヤーが参加しました");
    }


}

 見慣れない処理が多いですが、上から順番に見ていきましょう。書いたコードと照らし合わせて確認してください。

 

【プログラムの解説】

・PUNを扱うスクリプトには、最初に using Photon.Pun; と using Photon.Realtime; が必要です。

・PUNを扱うクラスにはMonoBehaviourPunCallbacksを継承する必要があります。

PUNではルームに入る前にサーバーへ接続しておく必要があります

 PhotonNetwork.ConnectUsingSettings(); と記述することで、Photonのサーバーへ接続することができます。

 

​・​OnConnectedToMaster関数は上記のサーバー接続に成功した瞬間に自動で呼び出されます。

 今回はサーバーに接続した瞬間にルームへの参加処理を行っています。
 

・​コードではRoomOptionsクラスのインスタンスを作成し、MaxPlayersを4に設定しています。

​ これによって1つのルームの参加人数が4人に制限されるようになります。

・PhotonNetwork.JoinOrCreateRoom 関数を実行することでルームへ参加することができます。

 JoinOrCreateの名前の通り、同じ名前のルームが既にある場合は参加し、ない場合はルームを作成して参加します。

・OnJoinedRoom関数は上記処理でルームに参加した瞬間に呼ばれる関数です。

 今回は参加できたことを示すデバッグ文を出力しています。

・OnPlayerEnteredRoom関数は自分以外のプレイヤーがルームに参加した瞬間に呼ばれる関数です。

 引数に参加したプレイヤーの情報が入っています。

 今回は参加したプレイヤーの番号を示すデバッグ文を出力しています。

 MonoBehaviourPunCallbacksを継承することで、様々なタイミングで実行される関数を使用できます。状況に合わせて使い分けるようにしましょう。

 PUN特有のコードは特に覚える必要はありません。PUNを使うときは「Photonのサーバーへ接続して、ルームへ参加する」処理が必要ということだけ最低限理解すればOKです

 新しい空オブジェクトGameを作成して、GameControllerタグを設定してください。

 GameManagerスクリプトをアタッチしましょう。

 これでルームへの参加が可能になりました。

1-7 複数起動できるようにする

1-7 複数起動できるようにする

​ 早速テストして確認したいところですが、Unityのエディターではゲームを多重起動することはできません。そのため、デバッグの際はその都度ゲームをビルドする必要があります。

 オフラインゲームにおいてはそこまで支障はないですが、オンラインゲームにおいてはこれは致命的な問題です。デバッグの度にゲームをビルドしていては、制作にかなりの時間がかかってしまいます。

 毎回ビルドせずともゲームを多重起動できるようにする「ParrelSync」というパッケージが公開されているので、今回はそれを導入しましょう。

​※ そこそこPCに負荷がかかるため、スペックが厳しい場合はパッケージの導入を飛ばして、直接ビルドするようにしてください

 まずは「Window」→「Package Manager」を開いてください。

 Package Manager左上のプラスボタンを押して「Add package from git URL」を選択してください。名前の通りGitのリポジトリからパッケージをインストールすることができます。

 以下のURLを入力して「Add」ボタンを押してください。

 インストールが完了したら、Package Managerを閉じてください。

 上部タブに「ParrelSync」の項目が追加されています。

​ 「Clones Manager」を選択してください。

 Unityでは同一のプロジェクトを複数起動することはできません

​ しかし、ParrelSyncでプロジェクトのクローンを作成し、疑似的に同一プロジェクトを複数起動することができます。

 「Create new clone」ボタンをクリックして、プロジェクトのクローンを作成してください。

 クローンが作成されたら「Open in New Editor」でプロジェクトを開いてください。

 今はクローン1つでも問題ありませんが、最終的に4人対戦をテストするためにもクローンは3つ作成しておいてください

 元のプロジェクトのゲームとクローンプロジェクトのゲームを同時に実行してみましょう。

 コンソールにルームにプレイヤーが参加したことを通知するログが表示されます。これで2人のプレイヤーが同じルームに参加し、オブジェクト情報を共有することができるようになりました。

 ParrelSyncを使用する際の注意点として、プロジェクトの編集は基本的に元のプロジェクトで行ってください

 プロジェクトを編集した後に保存して、クローンプロジェクトを開くとリロード確認のウィンドウが表示されます。ここで「Reload」を選択することで、元のプロジェクトの変更がクローンプロジェクトにも反映されます。

 クローンプロジェクトを開きながら制作をする際は注意しましょう。

1-8 プレイヤーを同期する

1-8 プレイヤーを同期する

 ​同じルームに参加しているプレイヤー同士でオブジェクトの情報を同期してみましょう。

 

 PUNにはPhotonStreamを中継地点として、情報の送受信を行う機能があります。

 「自分で作ったオブジェクトの情報を送信して、他人が作ったオブジェクトの情報を受信する」​ことで、オブジェクトの同期を行っていきます。

 一見難しそうに感じるかもしれませんが、この流れはPhoton側が勝手に行ってくれるため、あまり意識しなくても問題ありません。

 まずは同期処理を行うために必要なコンポーネントを設定していきましょう。

​ PUNには同期処理を行うためのコンポーネントが多数用意されています。

 プレイヤーにPhoton Viewコンポーネントをアタッチしてください。これはPhotonのネットワーク機能を利用するために必要なコンポーネントで、同期処理を行う際には必要不可欠です。

​ また、PhotonViewのSynchronizationを「Reliable Delta Compressed」に変更してください。

 次に「オブジェクトのどういった要素を同期するか」を設定していきます。

 オブジェクトの全ての情報を同期してしまうと通信量が増えてしまうため、必要最低限のものだけ同期するようにします。

 プレイヤーにPhoton Rigidbody 2D View をアタッチしてください。名前の通りRigidbody2Dを同期対象にするために必要なコンポーネントになります。

​(2Dがついていない方と間違えないように注意!)

​ また、Enable teleport for large distancesにチェックを入れておいてください。

 Enable teleport for large distances は、同期元と同期先の座標を比較して一定値以上離れていた場合、強制的に同期先の座標を同期元と同じ場所にテレポートさせるという設定です。

 これによって同期元と同期先で致命的なほどの座標のずれが発生しなくなります。ただし強制的にテレポートするため、移動としては不自然になる時があります。

 ちなみに世界が広くプレイヤーの移動量が大きい場合、この設定によって同期先での移動がガタガタになる場合があります(頻繁にテレポートするため)​その場合はTeleport if distance greaterの値を調整してください。

Unity Tips!

 プレイヤーの子オブジェクトPenguin1を選択してください。

​ こちらにも同期用のコンポーネントを追加します。

 Penguin1にPhoton Animator View と Photon Transform View をアタッチしてください。

 Photon Animator View ではパラメータを同期する方法を設定できます。今回は全て「Continuous(常に更新)」にしておいてください。

 Photon Transform View ではPosition、Rotation、Scale全てに​チェックを入れておいてください。

 これでコンポーネントの設定は完了です。

​ プレイヤーのPhoton View を確認すると、同期対象になっているコンポーネントの一覧を確認することができます。

 プレイヤーが完成したのでプレハブ化します。

 ただしプレハブは「Photon」→「PhotonUnityNetworking」→「Resources」フォルダに入れるようにしてください

​ プレハブ化できたらヒエラルキー内のプレイヤーは削除してください。

 PUNには同期用のInstantiate関数があり、それを使うことで複数のプレイヤーで同時にオブジェクトを生成することができます。そして前述の通り、自分が生成したプレイヤーを操作すると、Photon Viewによって他のプレイヤーに情報が同期されます。​

 プレイヤーが部屋に入った瞬間にプレハブからプレイヤーを生成してみましょう。

​ GameManagerスクリプトを開いて、赤い部分のコードを追加してください。

~前略~

        // 部屋に入る(同名の部屋がなかったら作る)
        PhotonNetwork.JoinOrCreateRoom("GameRoom",
            roomOptions,
            TypedLobby.Default);
    }

 

    // 自分がルームへ参加した瞬間に呼ばれる関数
    public override void OnJoinedRoom()
    {

        // プレイヤーを生成
        GameObject player = PhotonNetwork.Instantiate("Player",
                    Vector3.zero,
                    Quaternion.identity);

 

        // カメラに対象を教える
        Camera.main.GetComponent<GameCamera>().SetTargetObj(player);

 

        Debug.Log("ルームに参加しました");
    }

 

    // 新規でルームにプレイヤーが参加した瞬間に呼ばれる関数
    public override void OnPlayerEnteredRoom(Photon.Realtime.Player newPlayer)
    {
        Debug.Log(newPlayer.ActorNumber + "番のプレイヤーが参加しました");
    }
}

【プログラムの解説】

・PhotonNetwork.Instantiate 関数は、今まで使ってきたInstantiateと使い方は同じですが、いくつか異なる点があります。

 [注意点]

​① 第一引数はPhotonUnityNetworking内のResourcesフォルダにあるプレハブ名を指定する

② 生成するプレハブにはPhoton Viewコンポーネントがアタッチされている必要がある

③ 第二引数(座標)と第三引数(回転)を必ず指定する必要がある

④ 実行すると他プレイヤー側にもオブジェクトが生成される

 これでプレイヤーを生成&同期できましたが、まだ問題点があります。

 プレイヤーはキー入力で操作できるように作られています。しかし、このままでは生成先のプレイヤーの入力の影響も受けてしまうことになります

 簡単に言うと他人が作ったプレイヤーまで操作できてしまうということです。

 PhotonViewコンポーネントには「自分が生成したオブジェクトかどうか」を格納している場所があります。自分が生成したプレイヤーのみ操作できるようにしましょう​。

​ Playerスクリプトを開いて、赤い部分のコードを追加してください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Photon.Pun;       // PUNを使うために必要

 

public class Player : MonoBehaviour
{
    [Header("【メインパラメータ】")]
    [SerializeField, Header("移動速度")]
    float MoveSpeed = 0.004f;
    [SerializeField, Header("移動速度上限")]
    float LimitSpeed = 0.2f;
    [SerializeField, Header("ジャンプ力")]
    float JumpPower = 20.0f;

 

    // 現在のX方向への移動速度
    float m_nowMoveSpeed = 0.0f;
    // 減速用に乗算する値
    const float MOVE_DRAG = 0.98f;
    // ジャンプ中かどうか
    bool m_isJump = false;
    // 空中にいるかどうか
    bool m_isAir = false;

 

    // キャッシュ
    Rigidbody2D m_rigidbody2D;
    Animator m_animator;
    GroundCheck m_groundCheck;
    PhotonView m_photonView;

 

    void Awake()
    {
        // 自身にアタッチされているRigidbodyを取得
        m_rigidbody2D = GetComponent<Rigidbody2D>();
        // 自身の子オブジェクトからAnimatorを検索して取得
        m_animator = GetComponentInChildren<Animator>();
        // ① 自身の子オブジェクトからGroundCheckを探して取得
        m_groundCheck = GetComponentInChildren<GroundCheck>();
        // 自身にアタッチされているPhotonViewを取得
        m_photonView = GetComponent<PhotonView>();

    }

 

    void FixedUpdate()
    {
        // 生成者が自分でないなら操作を受け付けない
        if (m_photonView.IsMine == false)
        {
            return;
        }

 

        // 移動量計算
        if (Input.GetKey(KeyCode.D))
        {
            m_nowMoveSpeed += MoveSpeed;
        }
        else if (Input.GetKey(KeyCode.A))
        {
            m_nowMoveSpeed += -MoveSpeed;
        }
        else
        {
            // 入力がない場合は減速する
            m_nowMoveSpeed *= MOVE_DRAG;
        }
        // 移動速度制限
        m_nowMoveSpeed = Mathf.Clamp(m_nowMoveSpeed, -LimitSpeed, LimitSpeed);

 

        // 移動する
        transform.position += new Vector3(m_nowMoveSpeed, 0.0f, 0.0f);

 

        // 走るアニメーション
        if (Mathf.Abs(m_nowMoveSpeed) > 0.1f)
        {
            m_animator.SetBool("Run", true);
        }
        else
        {
            m_animator.SetBool("Run", false);
        }
    }

 

    void Update()
    {
        // 生成者が自分でないなら操作を受け付けない
        if (m_photonView.IsMine == false)
        {
            return;
        }

 

        // 接地している時にEnterキーが押されたらジャンプする
        if (Input.GetKeyDown(KeyCode.Return) &&
            m_groundCheck.GetIsGround() && 
            m_isJump == false)
        {
            m_animator.SetBool("Jump", true);

            m_isJump = true;
        }

        // 空中判定
        if (m_isJump && m_groundCheck.GetIsGround() == false)
        {
            m_isAir = true;
        }

        // 接地時にアニメーションを戻す
        if (m_isAir && m_groundCheck.GetIsGround())
        {
            m_isJump = false;
            m_isAir = false;
            m_animator.SetBool("Jump", false);
        }

 

        // 移動速度に応じて画像を反転
        if (m_nowMoveSpeed >= 0.0f)
        {
            Vector3 scale = transform.GetChild(0).transform.localScale;
            scale.x = Mathf.Abs(scale.x);
            transform.GetChild(0).transform.localScale = scale;
        }
        else
        {
            Vector3 scale = transform.GetChild(0).transform.localScale;
            scale.x = -Mathf.Abs(scale.x);
            transform.GetChild(0).transform.localScale = scale;
        }
    }

 

    public void Jump()
    {
        // 生成者が自分でないなら操作を受け付けない
        if (m_photonView.IsMine == false)
        {
            return;
        }

 

        // ② プレイヤーをジャンプさせる
        m_rigidbody2D.AddForce(new(0.0f, JumpPower), ForceMode2D.Impulse);
    }
}

【プログラムの解説】

・PhotonViewのIsMineは「自分が生成したオブジェクト」の場合のみtrue​になります。

 これによって自分が生成したオブジェクトのみキー入力を受け付けるようにしています。​

 これでプレイヤーを同期できるようになりました。

​ クローン(またはビルドしたゲーム)を開いて、実際に確認してみてください。

 それぞれのプレイヤーを操作して、状態を同期できていることが確認できます。

 これでベースは完成しました。

 ここまでの同期処理に関してはレースゲームに限らずどのジャンルのゲームでも活用できるものなので、自分でオンラインゲームを作る際はぜひ応用してみてください。

 他のレッスンであればこのままゲーム部分を作り込んでいくのですが、説明の都合上先にタイトルを実装していきます。次のレッスンでは「参加するプレイヤーが待機する場所を用意して、全員の準備が完了したらゲームを開始する」というロビー機能を作っていきましょう。

河原電子ビジネス専門学校
​ゲームクリエイター科

bottom of page