5-プレイヤーの構築
このセクションでは、このチュートリアルで使用するPlayer[プレハブ] を一から作成し、作成プロセスのすべてのステップを説明します。
PUNに接続していなくても動作するPlayer [プレハブ] の作成を試みるのは良いことです。
そうすれば、テストやデバッグを簡単におこなえて、ネットワーク機能なしで動作することができます。 そこから構築を進めて修正を加え、それぞれの機能をネットワーク対応のキャラクターに合わせていきます。
通常、ユーザ入力は他のプレイヤーのコンピュータではなく、プレイヤーが所有するインスタンスでのみ有効にするべきです。
以下で詳細を説明します。
プレハブの基本
PUNについて最初に知るべきなのは、[プレハブ]がネットワーク上でインスタンス化されるためには、プレハブがResourcesフォルダ内に存在する必要があるということです。
Resources内に[プレハブ] を置く場合のもう1つの重要な点は、その名前に注意が必要ということです。
AssetsのResourcesパス内の[プレハブ]の名前が重複してしまうと、Unityは最初に見つけた方を選びます。Project AssetsのResourcesフォルダパス内でPrefabの名前が重複しないよう注意してください。後ほど詳細について説明します。
Unityが無料のアセットとして提供するKyle Robotを使用します。
これは、3ds Max、Maya、cinema4dなどの3Dソフトウェアによって生成されるFbxファイルとして付属しています。
このチュートリアルではこれらのツールでおこなうメッシュやアニメーションの作成については説明しませんが、自身のキャラクターやアニメーションを作成するためには必須の作業です。
この「Robot Kyle.fbx」は 「/Assets/Photon Unity Networking/Demos/Shared Assets/」にあります。
プレイヤー向けにKyle Robot.fbx
の使用を開始する1つの方法:
1.「Project Browser」のどこかに、「Resources」という名前のフォルダを作成します。通常は、コンテンツを組織することを推奨しているので'DemoAnimator_tutorial/Resources/'というような形になります。
2. /PunBasics_tutorial/Scenes/ に新しい空のシーンを作成し、Kyle Test
として保存します。
「Kyle Test」シーンの目的は、プレハブを作成して設定することです。
これが完了すると、このシーンを削除することができます。
3.Robot Kyle
を「Scene Hierarchy」にドラッグアンドドロップします。
4. Hierarchyで作成したGameObjectの名前をMy Robot Kyle
に変更します。
5. My Robot Kyle
を*/PunBasics_tutorial/Resources/* にドラッグアンドドロップします。
これで、Kyle Robot
FbxアセットにもとづいたPrefabの作成が完了し、そのインスタンスがKyle Test
シーンのHierarchyに作成できました。
それでは、作業を開始しましょう。
CharacterController
1.[プレハブ] に直接おこなうこともできますが、その場合は微調整をする必要が生じます。
このため、ここではより速い方法でおこないます。
このコンポーネントはAnimatorを使用してより速い動きのキャラクターを作成する、Unityが提供するStandard Assetです。この便利な機能を使用してみましょう。
2.My Kyle Robot
をダブルクリックして、 Scene Viewをズームインします。"Capsule Collider"が足にセンタリングしていることを確認してください。
"Capsule Collider"を適切にキャラクターに合致させる必要があります。
3.CharacterController ComponentのCenter.y
プロパティを1に変更します (Height
プロパティの半分)。
加えた変更を反映させるため、「Apply」を押します。
My Kyle Robot
プレハブのインスタンスを編集しましたが、これらの変更はすべてのインスタンスに加える必要があるので、これは非常に重要なステップです。このため、「Apply」を押しました。
Animatorの設定
Animator Controllerの割り当て
Kyle Robot
Fbxアセットは、Animator Graphによって制御する必要があります。
このチュートリアルではこのグラフの作成については説明しないため、このコントローラーが提供されています。これは、 Photon Unity Networking/Demos/PunBasics/Animator/ の中にKyle Robotという名前で用意されています。
[プレハブ]にこの Kyle Robot
コントローラーを割り当てるには、Animator Componentのプロパティ コントローラー
がKyle Robot
を指すようにプロパティのコントローラーを設定します。
この設定をMy Kyle Robot
のインスタンスでおこなう場合には、これらの変更を[プレハブ]自体に組み込むため、"Apply"を押す必要があります。
コントローラーパラメータと連携
Animator Controller に関して理解するすべき重要な機能は、スクリプト経由でアニメーションを制御する[Animator Parameters]です。
今回のケースではSpeed
、Direction
、Jump
、Hi
などのパラメータがあります。
[Animatorコンポーネント]の素晴らしい特徴の一つは、アニメーションにもとづいて実際にキャラクターを動かせることです。この機能はRoot Motionと呼ばれ、Animator ComponentにはApply Root Motion
というプロパティがあります。これはデフォルトでtrueになっているので、そのまま利用できます。
つまり実際にキャラクターを歩かせるには、Speed
Animation Parameterを正の値に設します。キャラクターが歩き始め、前に進みます。試してみましょう!
Animator Managerスクリプト
ユーザーの入力にもとづいて、文字を制御する新しいスクリプトを作成してみましょう。
DemoAnimator/tutorial/Scripts/
にPlayerAnimatorManager
という新しいC#スクリプトを作成[プレハブ]
My Robot Kyle
にこのスクリプトを添付以下のように、 NameSpaceの
Com.MyCompany.MyGame
でクラスを囲みます。明確にするため、リージョン
MONOBEHAVIOUR MESSAGES
でStart()とUpdate() を囲みます。C#
using UnityEngine; using System.Collections; namespace Com.MyCompany.MyGame { public class PlayerAnimatorManager : MonoBehaviour { #region MONOBEHAVIOUR MESSAGES // 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'キーを押しても、それを許可せず、値は強制的に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
にスケールし、実験の余地を残します。 - カメラを選択して、全体を確認するためにカメラの距離をとります。
1つの良いトリックは、「Scene View」で好きなビューを取得し、カメラを選択して「GameObject/Align With View」メニューに進み、カメラをシーンビューに一致させることです。 - 最後のステップとして、
My Robot Kyle
を上に0.1移動します。そうしないと、衝突が抜けてしまい、キャラクターが床を通過してしまうので、シミュレーションが接触できるよう常にコライダーの間に物理的な空間を残してください。 - シーンを実行して、上矢印キーまたは 'a'を押すとキャラクターが歩きます!
すべてのキーをテストして確認してください。
だいぶ進みましたが、まだ作業が必要です。まだカメラを追跡させる必要がありますし、方向転換ができません。
すぐにカメラの設定をしたい場合は、カメラの専用セクションを確認してください。このページでは、Animatorの制御や回転の実装について説明します。
Animator Managerスクリプト: 方向制御
回転の制御は少し複雑です。左右のキーを押した際にキャラクターが突然回転せず、滑らかに方向転換することが目標です。
ダンピングを使用してAnimation Parameterを設定することができます。
- スクリプト
PlayerAnimatorManager
を編集していることを確認してください。 - 「PRIVATE PROPERTIES」リーションのすぐ上のスクリプトの、新しいリージョン「PUBLIC PROPERTIES」内に浮動小数変数
DirectionDampTime
を作成します。
C#
//#region PUBLIC PROPERTIES
public float DirectionDampTime = .25f;
//#endregion
Update()関数の最後に、以下を追加します:
C#
animator.SetFloat("Direction", h, DirectionDampTime, Time.deltaTime);
animator.SetFloat() は異なる署名が存在します。
Speed
を制御する署名は簡単ですが、2つのパラメータを追加する必要があります。1つはdamping time、もう片方はdeltaTimeです。
Damping timeは希望する値に到達するまでの時間です。 deltaTimeはフレームレートから独立してコードを書くことを可能にします。
Update()はフレームレートに依存するのでdeltaTimeを使用します。
このトピックについて、webなどで調べて理解を深めてください。
コンセプトを理解するとUnityのアニメーションや制御機能を最大限に活用できます。スクリプト
PlayerAnimatorManager
を保存します。シーンを実行して、すべての矢印を使用してキャラクターが歩き、方向転換することを確認してください。
DirectionDampTime
の効果を試してください: たとえば1の後に5を試し、最大の回転能力に到達するまでの時間を確認します。
回転の半径がDirectionDampTimeと共に大きくなることがわかります。
Animator Managerスクリプト:ジャンプ
ジャンプは、2つの理由からもう少し作業が必要です。
1つは、プレイヤーが走っていないときはジャンプするべきでないからです。もう1つは、ジャンプをループさせないためです。
スクリプト
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がジャンプします。
Animatorが実行しているかどうか確認するための方法について理解するには、まずstateInfo.IsName("Base Layer.Run")
を使用します。
Animatorの現在のアクティブ状態がRun
かどうかを確認します。Run
状態は'Base Layer'にあるので、'Base Layer'を追加する必要があります。
Run状態にいる場合、 Fire2
Inputにリッスンして、必要に応じて Jump
トリガーを発生させます。
以下が、ここまでの PlayerAnimatorManager
スクリプトです:
C#
using UnityEngine;
using System.Collections;
namespace Com.MyCompany.MyGame
{
public class PlayerAnimatorManager : MonoBehaviour
{
#region PRIVATE PROPERTIES
public float DirectionDampTime = .25f;
#endregion
#region PRIVATE PROPERTIES
private Animator animator;
#endregion
#region MONOBEHAVIOUR MESSAGES
// 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
}
}
この数行のコードにより、シーンで多くのことを実現できます。
次に、カメラが追跡できるようにするためにカメラの設定を行いましょう。
カメラ設定
このセクションでは、作成プロセス中に「Player」プレハブに焦点を当て続けるため、CameraWork
スクリプトをそのままの状態で使用します。
一からCameraWork
を書きたい場合は、 次のセクション に進んでからこのセクションに戻ってください。
My Kyle Robot
プレハブにコンポーネントCameraWork
を追加します。- プロパティ
Follow on Start
をオンにすると、カメラが瞬時にキャラクターを追跡するようになります。
ネットワーク実装を開始する際に、オフにします。 - プロパティ
Center Offset
を0,4,0
に設定して、カメラを高くします。こうすれば全体を映すことができ、カメラがプレイヤーをまっすぐ映した時のような無意味な地面の映しすぎを回避できます。 Kyle Test
シーンを再生し、カメラが適切にキャラクターを追跡することを確認するため、キャラクターを動かします。
PhotonView コンポーネント
プレハブにPhotonViewコンポーネントが必要です。
PhotonViewは、各コンピューター上のさまざまなインスタンスを接続し、監視するコンポーネントとこれらのコンポーネントの監視方法を定義するものです。
My Robot Kyle
にPhotonViewコンポーネントを追加します。Observe Option
をUnreliable On Change
に設定します。- PhotonViewは、これを有効にするには何かを観察する必要があることを警告します。
これらの観察されたコンポーネントはチュートリアルの後半で設定されるため、今は無視します。
ビームの設定
私達のロボットキャラクターには、まだ武器がありません。目から発射されるレーザービームを作成してみましょう。
ビームモデルの追加
簡素化するため、シンプルなキューブを使い、非常に細く長くなるようにスケールします。
これを速やかに行うトリックがあります:頭の子として直接キューブを追加するのではなく、作成、移動、スケールアップしてから頭に添付します。目にビームを合わせるため、適切な回転値を推測することを防ぎます。
もう1つの重要なトリックは、両方のビームで1つだけコライダーを使用することです。
これによって、物理エンジンがより効率的に機能します。薄いコライダーは信頼性が低く好ましくないので、確実にターゲットに当たるように大きなボックスコライダーを作ります。
Kyle test
シーンを開きます。- シーンにキューブを追加し、
Beam Left
と名付けます。 - 長いビームに見えるように変更し、左目に適切に配置します。
- Hierarchy内で
My Kyle Robot
インスタンスを選択します。 - Headの子を確認します。
注:レーザービームは、自身を傷つけないようキャラクターのコライダーの外側に設定する必要があります。
以下のようになります:
ユーザー入力でビームを制御
これでビームがで完成しました。次に、それをトリガーするためのFire入力をつなぎましょう。
次に、PlayerManagerと呼ばれる新しいC#のスクリプトを作成します。
以下はビームを使えるようにするための完全なコードです:
C#
using UnityEngine;
using UnityEngine.EventSystems;
using System.Collections;
namespace Com.MyCompany.MyGame
{
/// <summary>
/// Player manager.
/// Handles fire Input and Beams.
/// </summary>
public class PlayerManager : MonoBehaviour
{
#region Public Variables
[Tooltip("The Beams GameObject to control")]
public GameObject Beams;
#endregion
#region Private Variables
//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
プレハブの階層内でGameObjectを正確に参照できるパブリックプロパティBeams
を公開しました。
では、Beamsの接続方法を説明します。Assetsブラウザではプレハブは最初の子しか表示せず、サブチャイルドを表示しないのでBeamsがプレハブ階層で埋もれてしまいます。そのため、シーンのインスタンスから行い、プレハブ自体に適用して戻す必要があります。
Kyle test
シーンを開きます- シーンHierarchy内で
My Kyle Robot
を選択します。 PlayerManager
コンポーネントをMy Kyle Robot
に追加します。My Kyle Robot/Root/Ribs/Neck/Head/Beams
をインスペクター内のPlayerManager
Beams
プロパティにドラッグアンドドロップします。- インスタンスの変更をプレハブに適用します。
Playを押して Fire1
Input を押すと(デフォルトではマウスの左クリックまたはCtrlキー)ビームが表示され、キーを離すとすぐに非表示になります。
体力の設定
ビームがプレイヤーに当たったときに体力が減少する非常に単純なシステムを実装してみましょう。
弾丸ではなく、一定のエネルギーの流れなので、二方向で体力への影響を考える必要があります。ビームが当たった時と、ビームが当たっている間のダメージです。
PlayerManager
スクリプトを開きます。PlayerManager
をPhoton.PunBehaviourに変換し、PhotonViewコンポーネントを公開します。
C#
[Tooltip("The current Health of our player")]
public float Health = 1f;
- パブリック
Health
プロパティをPublic Variables
リージョンに追加します。
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
スクリプトを保存する。
2つのメソッドはほとんど同じですが、TriggerStayの場合はフレームレートに減少速度を依存させないよう、Deltatimeを使用して体力を減少させます。
これは通常アニメーションに適用する概念ですが、この場合はすべての端末で予測可能な方法で体力を減少させるために使用します。速いコンピュータで体力が速く減少してしまうのは公平ではないためです。
Deltatimeは一貫性を保証します。
DeltaTimeに関するご質問がありましたらお問い合わせください。また、概念を理解するためにUnityコミュニティを検索することをお勧めします。
理解するべきである二つ目の重要な側面は、ローカルプレイヤーの体力にのみ影響を与えるべきだということです。そのため、メソッドから早く出ます。PhotonViewはMine
ではありません。
最後に、プレイヤーに当たるObjectがビームである場合のみ体力に影響を与えるので、「beam」タグを使用して確認します。GameObjectsビームはそのようにタグしました。
デバッグを容易におこなうため、Health浮動小数はパブリック浮動小数となっています。これは、UIが構築されるのを待つ間に簡単に値を確認できるようにするためです。
だいぶ完成に近づきましたが、プレイヤーの体力がゼロになりゲームオーバー状態になるまで体力システムは完全ではありません。この設定をおこないましょう。
ゲームオーバーのための体力確認
単純化するため、プレイヤーの体力が0に達したときはルームから退室します。既にGameManagerスクリプトでルームを退出するメソッドを作成しています。
同じ機能のために再びコーディングをするのではなく、同じメソッドを利用できるのが理想的です。どのような場合も、同じ結果を生じるコードの重複は避けるべきです。
ここで、「Singleton」という便利なプログラミングの概念を紹介します。
詳細を説明すると長くなるので今は「Singleton」の最小限の実装をおこないます。
SingletonのUnityコンテキスト内での変化や、有効な機能を作成する方法などを理解すると非常に便利です。
このチュートリアルだけでなく、Singletonについて勉強することをお勧めします。
GameManager
スクリプトを開きますPublicプロパティリージョンに、この変数を追加します。
C#
static public GameManager Instance;
このコードをStart()メソッドに追加します。
C#
Instance = this;
GameManager
スクリプトを保存します。
Instance変数に[静的な]キーワードを実装した点に留意してください。
つまり、GameManagerのインスタンスにポインタを保持しなくても、この変数を使用することができます。
結果としてコード内のどこからでも GameManager.instance.xxx()
を実行でき、非常に効率的です。
上記が、このゲームのロジック管理の点にどのように合致するかをみてみましょう。
PlayerManager
スクリプトを開く。- Update()メソッドで入力を処理した後、これを追加して
PlayerManager
スクリプトを保存しますC#
if (photonView.isMine) { ProcessInputs(); if (Health <= 0f) { GameManager.Instance.LeaveRoom(); } }
PlayerManager
スクリプトを保存します
- レーザービームによるダメージの強度は一定ではないので、体力が負の数値になり得ることを考慮します。
- 実際にコンポーネントなどを入手することなく、GameManagerインスタンスの
LeaveRoom()
パブリックメソッドに到達します。
現在のシーンで、GameObjectにGameManagerコンポーネントが存在するという仮定にもとづいています。
次に、ネットワークに進みましょう!
Back to top