5 - プレイヤーの構築
このセクションでは、このチュートリアルで使用するPlayer[プレハブ]を一から作成し、作成プロセスの全てのステップを説明します。
PUNが接続されていなくても動作するPlayer[プレハブ]を作成するのは、ちょっとしたテストやデバッグ、そしてネットワーク機能がない状態での動作確認をするのにいい方法です。
そこから構築を進めて修正を加え、それぞれの機能をネットワーク対応のキャラクターに合わせていきます。
通常、ユーザー入力は他のプレイヤーのコンピュータではなく、プレイヤーが所有するインスタンスでのみ有効にするべきです。
以下で詳細を説明します。
プレハブの基本
PUNについての最初に知るべきなのは、[プレハブ]がネットワーク上でインスタンス化されるためには、[Resource]フォルダの中になければいけないということです。
Resourcesフォルダ内に[プレハブ]を置くもう一つの重要な点は、その名前に注意が必要、ということです。
AssetのResourcesパス内の[プレハブ]の名前が重複してしまうと、Unityは最初に見つけたほうを選びます。Project Asset内のResourcesフォルダパス内でプレハブの名前が重複しないよう注意してください。後ほど詳細について説明します。
Unityが無料のアセットとして提供するKyle Robotを使用します。
これは、3ds Max、Maya、cinema 4D、likesなどの3Dソフトウエアで生成されるFbxファイルとして付属しています。
これらのソフトウエアを使用したメッシュやアニメーションの生成の仕方については、このチュートリアルでは割愛します。
Robot Kyle.fbx
は、「*Assets\Photon\PhotonUnityNetworking\Demos\Shared Assets*」にあります。
プレイヤー向けにKyle Robot.fbx
の使用を開始する一つの方法:
- 「Project Browser」のどこかに、「Resources」という名前のフォルダを作成します。通常は、コンテンツを組織することを推奨しているので'PunBasics_tutorial/Resources/' というような形になります。
- "PunBasics_tutorial\Scenes" に新しい空のシーンを作成し、
Kyle Test
として保存します。
「Kyle Test」シーンの目的は、プレハブを作成し、保存することです。
一度処理が完了したら、このシーンは削除可能です。 Robot Kyle
を「Scene Hierarchy」にドラッグアンドドロップします。- Hierarchyで生成したGameObjectの名前を
My Robot Kyle
に変更します。 My Robot Kyle
を "PunBasics_tutorial\Resources" にドラッグアンドドロップします。
これでKyle Robot
Fbxアセットに基づく[プレハブ]の作成が完了し、そのインスタンスをシーンKyle Test
のHierarchyの中に作成できました。
それでは作業を開始しましょう。
CharacterController
CharacterController ComponentをHierarchyの
My Kyle Robot
インスタンスに追加しましょう。
直接[プレハブ]におこなうこともできますが、その場合は微調整をする必要が生じます。なのでここではより速い方法でおこないます。このコンポーネントは、Animatorを使った典型的なキャラクターの速い作成用にUnityから提供されるとても便利なStandard Assetです。このUnity機能を活用しましょう。My Kyle Robot
をダブルクリックしてScene Viewをズームインします。「Capsule Collider」 が足にセンタリングしていることを確認してください。
「Capsule Collider」は適切にキャラクターに合致させる必要があります。CharacterController Componentで
Center.y
プロパティを1に変更します(Height
プロパティの半分です)。「Apply」を押して変更を反映させます。
プレハブMy Kyle Robot
インスタンスの編集をしましたが、これらの変更を全てのインスタンスに加える必要があるので、これは非常に重要なステップです。このため、「Apply」を押しました。
Animatorの設定
Animator Controllerの割り当て
Kyle Robot
Fbx アセットはAnimator Graphで制御する必要があります。
このグラフについてはここでは割愛しますが、このためにコントローラーが提供されています。このコントローラーは \Assets\Photon\PhotonUnityNetworking/Demos/PunBasics-Tutorial/Animator/ 内のプロジェクトアセットにKyle Robot
という名前で用意されています。
このKyle Robot
コントローラーを[プレハブ]に割り当てるには、AnimatorコンポーネントのプロパティController
が Kyle Robot
を指すように設定するだけです。
以上のことをMy Kyle Robot
のインスタンスでおこなう場合、これらの変更を[プレハブ]自体に組み込むため、「Apply」を押す必要があります。
コントローラパラメーターと連携
Animator Controllerに関して理解すべき重要な機能はスクリプトを経由して
アニメーションを制御するAnimation Parametersです。
今回のケースではSpeed
、Direction
、Jump
、Hi
などのパラメータがあります。
Animator Componentの素晴らしい機能の一つは、アニメーションに応じてキャラクターを動かせることです。この機能はRoot Motionと呼ばれ、Animator ComponentにはApply Root Motion
というプロパティがあります。これはデフォルトでTrueになっていますので、そのまま使えます。
実際にキャラクターを歩かせるには、Speed
Animation Parameterを正の値に設定します。そうすると、キャラクターが歩き始め、前に進みます。試してみましょう。
Animator Managerスクリプト
ユーザーの入力にもとづいて、文字を制御する新しいスクリプトを作成しましょう。
PlayerAnimatorManager
という新しいc#スクリプトを作成します。[プレハブ]
My Robot Kyle
にこのスクリプトを添付します。下記のようにNameSpaceの
Com.MyCompany.MyGame
でクラスを囲みます。わかりやすいようにリージョン
MonoBehaviour CallBacks
でStart()とUpdate()を囲みます。C#
using UnityEngine; using System.Collections; namespace Com.MyCompany.MyGame { public class PlayerAnimatorManager : MonoBehaviour { #region MonoBehaviour Callbacks // Use this for initialization void Start() { } // Update is called once per frame void Update() { } #endregion } }
スクリプト
PlayerAnimatorManager
を保存します。
Animator Manager: 速度制御
始めにコーディングする必要があるのは、Animator Componentを制御するためのコードです。
スクリプト
PlayerAnimatorManager
を編集している点を確認してください。Animator
型のプライベート変数animator
を作成します。Animator ComponentをStart()メソッドでこの変数内に格納します。
C#
private Animator animator; // Use this for initializationいつもコードを書くべきです。手間ですが、長い目で見るとそのほうがずっといいです。 void Start() { animator = GetComponent<Animator>(); if (!animator) { Debug.LogError("PlayerAnimatorManager is Missing Animator Component", this); } }
Animator Componentを必須とするため、もしも取得ない場合には開発者がすぐに対処できるようエラーをログに記録します。
コードを書くときは、常にほかの誰かによって使用されることを想定すべきです。手間はかかりますが、将来的に役立ちます。[ユーザー入力]にリッスンして
Speed
Animation Parameterを制御しましょう。そして、スクリプトPlayerAnimatorManager
を保存します。C#
// Update is called once per frame void Update() { if (!animator) { return; } float h = Input.GetAxis("Horizontal"); float v = Input.GetAxis("Vertical"); if (v < 0) { v = 0; } animator.SetFloat("Speed", h * h + v * v); }
スクリプト
PlayerAnimatorManager
を保存します。
このスクリプトの役割を確認してみましょう:
このゲームで後方に進むことはできませんので、v
は0未満にしてください。ユーザーが「ダウンキー」や「S」キー(Vertical
軸のデフォルト設定)を押しても、この動作を許可せず、値は強制的に0になります。
両方の入力を二乗しているのはなぜだと思いますか?これは値を常に正の絶対値にして簡単にしておくためです。これは良い秘訣です。Mathf.Abs()
も問題なく使用出来ます。
左または右の入力をした際方向転換の時に加速できるよう、Speed
を制御するために両方の入力を追加します。
これは全て、キャラクターデザインに関することです。もちろん、ゲームロジックによって、キャラクターがその場で方向転換したり後方に進む場合などがあるので、Animation Parametersの制御は常に各ゲームに固有のものになります。
テスト、テスト、1 2 3...
ここまで行ってきたことを検証してみましょう。
Kyle Test
シーンが開かれていることを確認してください。現在、このシーンにはカメラとKyle Robot
インスタンスしかありません。ロボットが立つための地表がないので、今実行したらKyle robotは転んでしまうでしょう。
また、このシーン内では照明などの詳細が省略されています。キャラクターをテストして、スクリプトが正常に動作しているか検証をすることが目的だからです。
- シーンに「キューブ」を追加します。
キューブにはデフォルトで「Box Collider」がついているのでそのまま床で使えます。 - キューブの高さが1なので、
0,-0.5,0
に配置します。キューブの上面が床に配置されるようにします。 - キューブを
30,1,30
にスケールし、実験の余地を残します。 - カメラを選択して全体を確認するためにカメラの距離をとります。
一つのいいトリックとして、「Scene View」から好きなビューを取得し、カメラを選択してメニュー「GameObject/Align With View」に進み、カメラをシーンビューに合致させることです。 - 最後のステップとして
My Robot Kyle
のyを0.1上方向に動かします。そうしないと、衝突が抜けてしまい、キャラクターが床を通過してしまうので、シミュレーションが接触できるよう常にコライダーの間に物理的な空間を残してください。 - シーンを再生して「上矢印キー」もしくは「a」キーを押すと、キャラクターが歩きます!
すべてのキーをテストして確認してください。
だいぶ進みましたが、まだ作業が必要です。まだカメラを追跡させる必要がありますし、方向転換ができません。
すぐにカメラの設定をしたい場合は、カメラの専用セクションに進んでください。このページではAnimator制御と回転の実装について説明します。
Animator Manager スクリプト: 方向制御
回転の制御は少し複雑です。左右のキーを押した際に、キャラクターが突然回転せず、滑らかに方向転換するのが目標です。ダンピングを使ってAnimation Parameterの設定が可能です。
スクリプト
PlayerAnimatorManager
を編集していることを確認してください。新しいリージョン「Private Fields」リージョン内に浮動小数変数
directionDampTime
を作成します。C#
#region Private Fields [SerializeField] private float directionDampTime = 0.25f; #endregion
Update()機能の最後に、以下を追加します:
C#
animator.SetFloat("Direction", h, directionDampTime, Time.deltaTime);
animator.SetFloat() は異なる署名があります。
Speed
制御のために使用している署名は簡単ですが、2つのパラメータを追加する必要があります。一つはdamping time、もう一つはdeltaTimeです。
damping timeは希望する値に達するまでの時間なので、整合性があります。では、[delta time]とは何でしょう?
delta timeはフレームレートから独立したコーディングを可能にします。Update()がフレームレートに依存するので、deltaTimeを使います。
このトピックについてWebなどで調べてできるだけ理解を深めてください。
このコンセプトを理解すると、アニメーションや制御機能最大限に活用できます。スクリプト
PlayerAnimatorManager
を保存します。シーンを実行し、全ての矢印キーを押してキャラクターが歩き、方向転換することを確認します。
directionDampTime
の効果をテストします。例として1を入力し、その後に5にして最大の回転能力に到達するまでの時間を確認します。
回転の半径がdirectionDampTime
に応じて増加しています。
Animator Manager スクリプト: ジャンプ
ジャンプには、2つの理由からもう少し作業が必要です。1つは、プレイヤーが走っていないときにはジャンプさせないこと、そして2つめはジャンプをループさせないことです。
スクリプト
PlayerAnimatorManager
を編集していることを確認してください。Update()メソッドで、ユーザー入力を取得する前に以下を入力してください。
C#
// deal with Jumping AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0); // only allow jumping if we are running. if (stateInfo.IsName("Base Layer.Run")) { // When using trigger parameter if (Input.GetButtonDown("Fire2")) { animator.SetTrigger("Jump"); } }
スクリプト
PlayerAnimatorManager
を保存します。テストします。実行を開始し、「alt」キーを押すか、右クリックでKyleがジャンプします。
最初に理解するべきことはアニメーターが走っているかどうかをどう確認するかということです。これにはstateInfo.IsName("Base Layer.Run")
を使用します。
Animatorの現在のアクティブ状態がRun
かどうかを確認します。Run
状態はBase Layer
にあるので、Base Layer
を追加する必要があります。
Run状態にある場合、Fire2
[入力]をリッスンして、必要に応じて Jump
トリガーを上げます。
以下は、これまでのところのPlayerAnimatorManager
スクリプトの全容です。
C#
using UnityEngine;
using System.Collections;
namespace Com.MyCompany.MyGame
{
public class PlayerAnimatorManager : MonoBehaviour
{
#region Private Fields
[SerializeField]
private float directionDampTime = .25f;
private Animator animator;
#endregion
#region MonoBehaviour CallBacks
// Use this for initialization
void Start()
{
animator = GetComponent<Animator>();
if (!animator)
{
Debug.LogError("PlayerAnimatorManager is Missing Animator Component", this);
}
}
// Update is called once per frame
void Update()
{
if (!animator)
{
return;
}
// deal with Jumping
AnimatorStateInfo stateInfo = animator.GetCurrentAnimatorStateInfo(0);
// only allow jumping if we are running.
if (stateInfo.IsName("Base Layer.Run"))
{
// When using trigger parameter
if (Input.GetButtonDown("Fire2"))
{
animator.SetTrigger("Jump");
}
}
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
if (v < 0)
{
v = 0;
}
animator.SetFloat("Speed", h * h + v * v);
animator.SetFloat("Direction", h, directionDampTime, Time.deltaTime);
}
#endregion
}
}
この数行のコードにより、シーンで多くのことを実現できます。
次に、カメラが追跡できるようにするためにカメラの設定を行いましょう。
カメラ設定
この設定では、CameraWork
スクリプトを使用します。
スクラッチからCameraWork
を書く場合は、次のセクションで確認して、完了次第ここに戻ってきてください。
My Kyle Robot
プレハブにコンポーネントCameraWork
を追加します。- プロパティ
Follow on Start
をオンするとカメラが瞬時にキャラクターを追跡するようになります。
ネットワーク実装を開始する際に、オフにします。 - プロパティ
Center Offset
を0,4,0
に設定して、カメラを高くします。こうすれば全体を映すことができ、カメラがプレイヤーをまっすぐ映した時のような無意味な地面の映しすぎを回避できます。 - シーン
Kyle Test
を再生し、キャラクターを動かしてカメラが適切に追従しているか検証します。
PhotonViewコンポーネント
プレハブにPhotonViewコンポーネントをアタッチさせる必要があります。
A PhotonViewとは、各コンピュータの様々なインスタンスをつなぎ、度のコンポーネントを監視するか、またどのように監視するかを定義するものです。
My Robot Kyle
にPhotonViewコンポーネントを追加します。Unreliable On Change
にObserve Option
を設定します。- PhotonViewは、効果を表すには監視対象を設定する必要があると警告を表示します。
チュートリアルの後半で監視コンポーネントの設定を行うので、今の時点では無視しておいても問題ありません。
ビーム設定
まだロボットキャラクターは武器を持っていません。目から発射するレーザービームを作成してみましょう。
ビームモデルの追加
簡単にするために、シンプルなキューブ四角形を使用して、かなり細長い形にスケールします。
これを手早く作成するテクニックを紹介します。Headの子として直接キューブを追加しないで、作成、移動、スケールアップしてから頭に添付します。目にビームを合わせるため、適切な回転値を推測することを防ぎます。
もう1つの重要なテクニックは、両方のビームで1つだけコライダーを使用することです。
これは物理エンジンの動作のためです。細長いコライダーは信頼性がなく、決して扱いやすいものではありません。なので、大きなボックスコライダーを作成し確実にターゲットを攻撃できるようにします。
Kyle test
シーンを開きます。- シーンにキューブを追加し、
Beam Left
と名付けます。 - 長いビームに見えるように修正し、左目に対して適切に配置します。
My Kyle Robot
インスタンスをHierarchyから選択します。- Headの子を確認します。
注:レーザービームは、自身を傷つけないようキャラクターのコライダーの外側に設定する必要があります。
これで以下のようになっているはずです。
ユーザー入力でビームを制御
これでビームがで完成しました。次に、それをトリガーするためのFire1
入力をつなぎましょう。
新しいC#スクリプトを作成し、PlayerManager
と呼びます。
以下はビームを作動させるためのの完全なコードです。
C#
using UnityEngine;
using UnityEngine.EventSystems;
using Photon.Pun;
using System.Collections;
namespace Com.MyCompany.MyGame
{
/// <summary>
/// Player manager.
/// Handles fire Input and Beams.
/// </summary>
public class PlayerManager : MonoBehaviourPunCallbacks
{
#region Private Fields
[Tooltip("The Beams GameObject to control")]
[SerializeField]
private GameObject beams;
//True, when the user is firing
bool IsFiring;
#endregion
#region MonoBehaviour CallBacks
/// <summary>
/// MonoBehaviour method called on GameObject by Unity during early initialization phase.
/// </summary>
void Awake()
{
if (beams == null)
{
Debug.LogError("<Color=Red><a>Missing</a></Color> Beams Reference.", this);
}
else
{
beams.SetActive(false);
}
}
/// <summary>
/// MonoBehaviour method called on GameObject by Unity on every frame.
/// </summary>
void Update()
{
ProcessInputs();
// trigger Beams active state
if (beams != null && IsFiring != beams.activeInHierarchy)
{
beams.SetActive(IsFiring);
}
}
#endregion
#region Custom
/// <summary>
/// Processes the inputs. Maintain a flag representing when the user is pressing Fire.
/// </summary>
void ProcessInputs()
{
if (Input.GetButtonDown("Fire1"))
{
if (!IsFiring)
{
IsFiring = true;
}
}
if (Input.GetButtonUp("Fire1"))
{
if (IsFiring)
{
IsFiring = false;
}
}
}
#endregion
}
}
この段階での当スクリプトの主なポイントはビームを有効化・無効化することです。
有効化した場合は、他のモデルで衝突が発生したときにビームが効果的にトリガーします。このため、各キャラクターの体力に影響を与える目的で、後にこれらのトリガーを取得します。
My Kyle RobotプレハブのHierarchy内でGameObjectを正確に参照できるパブリックプロパティBeams
を公開しました。
では、Beams
の接続方法を説明します。Assetsブラウザではプレハブは最初の子しか表示せず、サブチャイルドを表示しないのでBeamsがプレハブ階層で埋もれてしまいます。そのため、シーンのインスタンスから行い、プレハブ自体に適用して戻す必要があります。
Kyle test
シーンを開きます。- シーンHierarchy内で
My Kyle Robot
を選択します。 My Kyle Robot
にPlayerManager
コンポーネントを追加します。- インスペクター内の
PlayerManager
Beams
プロパティにMy Kyle Robot/Root/Ribs/Neck/Head/Beams
をドラッグアンドドロップします。 - プレハブに戻ってインスタンスからの変更を適用します。
これでPlayを選択しFire1
Inputを押す(デフォルトで左クリックか左のctrlキーです)と、ビームが現れ、手を離すとすぐに消えます。
体力の設定
ビームがプレイヤーに当たったときに体力が減少する非常に単純なシステムを実装してみましょう。
弾丸ではなく、一定のエネルギーの流れなので、二方向で体力への影響を考える必要があります。ビームが当たった時と、ビームが当たっている間のダメージです。
PlayerManager
スクリプトを開きます。Public Fields
リージョン内にパブリックHealth
プロパティを追加します。C#
[Tooltip("The current Health of our player")] public float Health = 1f;
以下の2つのメソッドを
MonoBehaviour Callbacks
リージョンに追加します。そのあとでPlayerManager
スクリプトを保存します。C#
/// <summary> /// MonoBehaviour method called when the Collider 'other' enters the trigger. /// Affect Health of the Player if the collider is a beam /// Note: when jumping and firing at the same, you'll find that the player's own beam intersects with itself /// One could move the collider further away to prevent this or check if the beam belongs to the player. /// </summary> void OnTriggerEnter(Collider other) { if (!photonView.IsMine) { return; } // We are only interested in Beamers // we should be using tags but for the sake of distribution, let's simply check by name. if (!other.name.Contains("Beam")) { return; } Health -= 0.1f; } /// <summary> /// MonoBehaviour method called once per frame for every Collider 'other' that is touching the trigger. /// We're going to affect health while the beams are touching the player /// </summary> /// <param name="other">Other.</param> void OnTriggerStay(Collider other) { // we dont' do anything if we are not the local player. if (! photonView.IsMine) { return; } // We are only interested in Beamers // we should be using tags but for the sake of distribution, let's simply check by name. if (!other.name.Contains("Beam")) { return; } // we slowly affect health when beam is constantly hitting us, so player has to move to prevent death. Health -= 0.1f*Time.deltaTime; }
PlayerManager
はMonoBehaviourPunCallbacks
を拡張します。
MonoBehaviourPunCallbacks
はMonoBehaviourPun
を拡張します。
MonoBehaviourPun
にはphotonView
プロパティがあり、'lazy initialization' が設定されています。
このようにして、photonView
はPlayerManager
に含まれます。PlayerManager
スクリプトを保存します。
2つのメソッドはほとんど同じですが、TriggerStayの場合はフレームレートに減少速度を依存させないよう、Time.deltaTime
を使用して体力を減少させます。
これは通常アニメーションに適用する概念ですが、この場合はすべての端末で予測可能な方法で体力を減少させるために使用します。速いコンピューター上では体力の減少も速くなってしまったら公平ではないからです。
Time.deltaTime
は一貫性を保証するためにあります。
Time.deltaTime
に関するご質問がありましたらお問い合わせください。また、概念を理解するためにUnityコミュニティを検索することをお勧めします。
理解するべきである二つ目の重要な側面は、ローカルプレイヤーの体力にのみ影響を与えるべきだということです。そのため、メソッドから早く出ます。PhotonViewはMine
ではありません。
最後に、プレイヤーに当たるObjectがビームである場合のみ体力に影響を与えるようにします。
デバッグを容易におこなうため、Health浮動小数はパブリック浮動小数となっています。これは、UIが構築されるのを待つ間に簡単に値を確認できるようにするためです。
だいぶ完成に近づきましたが、プレイヤーの体力がゼロになりゲームオーバー状態になるまで体力システムは完全ではありません。 この設定をおこないましょう。
ゲームオーバーのための体力確認
単純化するため、プレイヤーの体力が0に達したときはルームから退室します。既にGameManagerスクリプトでルームを退出するメソッドを作成しています。
同じ機能のために再びコーディングをするのではなく、同じメソッドを利用できるのが理想的です。どのような場合も、同じ結果を生じるコードの重複は避けるべきです。
ここで、「Singleton」という便利なプログラミングの概念を紹介します。
「Singleton」そのものは全て説明すると長くなるので、今は最小限の実装のみ行います。
SingletonのUnityコンテキスト内での変化や、有効な機能を作成する方法などを理解すると非常に便利です。
このチュートリアルだけでなく、Singletonについて勉強することをお勧めします。
GameManager
スクリプトを開きます。この変数をPublic Fieldリージョンに追加します。
C#
public static GameManager Instance;
Start()メソッドを下記のように追加します。
C#
void Start() { Instance = this; }
GameManager
スクリプトを保存します。
Instance変数に[静的な]キーワードを実装した点に留意してください。つまり、GameManagerのインスタンスにポインタを保持しなくても、この変数を使用することができます。
結果としてコード内のどこからでもGameManager.instance.xxx()
を実行でき、
非常に効率的です。
上記が、このゲームのロジック管理の点にどのように合致するかをみてみましょう。
PlayerManager
スクリプトを開きます。- Update()メソッド内でProcessInputsを行った後、以下を追加して
PlayerManager
スクリプトを保存します。C#
if (photonView.IsMine) { ProcessInputs(); if (Health <= 0f) { GameManager.Instance.LeaveRoom(); } }
PlayerManager
スクリプトを保存します。
*注:レーザービームによるダメージの強度は一定ではないので、体力が負の数値になり得ることを考慮します
*注:実際にコンポーネントなどを入手することなく、GameManagerインスタンスのLeaveRoom()
パブリックメソッドに到達します。
現在のシーンで、GameObjectにGameManagerコンポーネントが存在するという仮定にもとづいています。
OK, now we are diving into networking!
Back to top