プログラムでプレイヤーを動かす¶
いよいよコーディングです。前編で作成した入力アクションを使って、キャラクターを動かしていきます。
新しいスクリプトを追加するために、Playerノードを右クリックし、スクリプトをアタッチ(Attach Script)を選択します。ポップアップで、テンプレート(Template)をEmptyにセットしてから *作成*(Create)ボタンを押してください。
まず、このクラスのプロパティから始めましょう。移動速度(speed)、重力を表す落下加速度(gravity)、そしてキャラクターを移動させるのに使う速度(velocity)を定義していきます。
extends KinematicBody
# How fast the player moves in meters per second.
export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
export var fall_acceleration = 75
var velocity = Vector3.ZERO
public class Player : KinematicBody
{
// Don't forget to rebuild the project so the editor knows about the new export variable.
// How fast the player moves in meters per second.
[Export]
public int Speed = 14;
// The downward acceleration when in the air, in meters per second squared.
[Export]
public int FallAcceleration = 75;
private Vector3 _velocity = Vector3.Zero;
}
これらは移動体に共通するプロパティです。velocity
は、速度と方向を組み合わせた3Dベクトルです。ここでは、フレーム間で値を更新して再利用したいので、プロパティとして定義しています。
注釈
距離はメートル単位なので、2D コードとは値がかなり異なります。2D では、1000単位(ピクセル)は画面の幅の半分にしか相当しませんが、3D では 1km になります。
それでは、動きをコーディングしてみましょう。まず、グローバルなInput
オブジェクトを使って、_physics_process()
で入力方向ベクトルを計算するところから始めます。
func _physics_process(delta):
# We create a local variable to store the input direction.
var direction = Vector3.ZERO
# We check for each move input and update the direction accordingly.
if Input.is_action_pressed("move_right"):
direction.x += 1
if Input.is_action_pressed("move_left"):
direction.x -= 1
if Input.is_action_pressed("move_back"):
# Notice how we are working with the vector's x and z axes.
# In 3D, the XZ plane is the ground plane.
direction.z += 1
if Input.is_action_pressed("move_forward"):
direction.z -= 1
public override void _PhysicsProcess(float delta)
{
// We create a local variable to store the input direction.
var direction = Vector3.Zero;
// We check for each move input and update the direction accordingly
if (Input.IsActionPressed("move_right"))
{
direction.x += 1f;
}
if (Input.IsActionPressed("move_left"))
{
direction.x -= 1f;
}
if (Input.IsActionPressed("move_back"))
{
// Notice how we are working with the vector's x and z axes.
// In 3D, the XZ plane is the ground plane.
direction.z += 1f;
}
if (Input.IsActionPressed("move_forward"))
{
direction.z -= 1f;
}
}
ここでは、_physics_process()
仮想関数を使用してすべての計算を行います。_process()
と同様に、フレームごとにノードを更新することができますが、運動体や剛体の移動などの物理関連のコードに特化した設計になっています。
参考
_process()
と_physics_process()
の違いについては、 アイドル処理と物理処理 を参照してください。
まず、direction
変数を Vector3.ZERO
に初期化することからはじめます。次に、プレイヤーがmove_*
の入力を一つ以上押しているかどうかを確認し、ベクトルのx
、z
成分を適宜に更新します。これらの成分は地平面の軸に相当します。
この4つの条件は8つの可能性そして8つの可能な方向を与えます。
たとえば、プレイヤーが W と D の両方を同時に押した場合、ベクトルの長さは1.4
程度になります。しかし、1 つのキーを押した場合は、1
の長さになります。ベクトルの長さが一定であるようにしたいので、そのためにnormalize()
メソッドを呼び出します。
#func _physics_process(delta):
#...
if direction != Vector3.ZERO:
direction = direction.normalized()
$Pivot.look_at(translation + direction, Vector3.UP)
public override void _PhysicsProcess(float delta)
{
// ...
if (direction != Vector3.Zero)
{
direction = direction.Normalized();
GetNode<Spatial>("Pivot").LookAt(Translation + direction, Vector3.Up);
}
}
ここでは、方向が0より大きい長さを持つ場合、つまりプレイヤーが方向キーを押している場合にのみ、このベクトルを正規化(normalize)します。
この場合、Pivotノードも取得し、そのlook_at()
メソッドを呼び出します。このメソッドは、空間上で「向く」(look at)先のグローバル座標と、上にあたる方向を引数に取ります。この場合、Vector3.UP
という定数を使うことができます。
注釈
ノードのローカル座標は、 translation
のように、親からの相対的な座標です。グローバル座標は、ビューポートで見ることができる、ワールドのメイン軸からの相対座標です。
3D では、ノードの位置を含むプロパティはtranslation
となります。これにdirection
を加えると、Playerは1メートル離れた位置を見ることができます。
次に、速度(velocity)を更新します。地面での速度と落下速度を別々に計算する必要があります。行は字下げを1タブ戻し、_physics_process()
関数の中にありつつ、今書いた条件文の外にあるようにしてください。
func _physics_process(delta):
#...
if direction != Vector3.ZERO:
#...
# Ground velocity
velocity.x = direction.x * speed
velocity.z = direction.z * speed
# Vertical velocity
velocity.y -= fall_acceleration * delta
# Moving the character
velocity = move_and_slide(velocity, Vector3.UP)
public override void _PhysicsProcess(float delta)
{
// ...
// Ground velocity
_velocity.x = direction.x * Speed;
_velocity.z = direction.z * Speed;
// Vertical velocity
_velocity.y -= FallAcceleration * delta;
// Moving the character
_velocity = MoveAndSlide(_velocity, Vector3.Up);
}
垂直方向の速度については、落下加速度に毎フレームのデルタタイムを掛けたものを差し引きます。-=
という演算子が使われていることに注目してください。これは variable = variable - ...
の省略形になっています。
この行によってキャラクターは全てのフレームで落下します。キャラクターがすでに床の上にいる場合は、これは奇妙に思えるかもしれません。しかし、キャラクターを全てのフレームで地面に衝突させるためには、このようにしなければなりません。
物理エンジンは、移動および衝突が起こった場合にのみ、特定のフレームにおける壁、床、または他の物体との相互作用を検出することができます。 後でこのプロパティを使ってジャンプをコーディングします。
最後の行で、KinematicBody.move_and_slide()
を呼び出しています。これは、KinematicBody
クラスの強力なメソッドで、キャラクターをスムーズに移動させることを可能にします。動作の途中で壁にぶつかっても、エンジンがスムーズな動きにしようとします。
この関数は2つのパラメータを受け取ります: 速度と上を指す方向です。関数はキャラクターを移動させ、衝突を適用した後に残った速度を返します。床や壁にぶつかると、その方向への速度が減少またはリセットされます。この場合、関数の戻り値を保存しておくことで、キャラクターが垂直方向の勢いを蓄積し、床を貫通してしまうのを防ぎます。
そして、これが床の上でキャラクターを動かすために必要なコードのすべてです。
参考までに、Player.gd
の完全なコードを以下に示します。
extends KinematicBody
# How fast the player moves in meters per second.
export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
export var fall_acceleration = 75
var velocity = Vector3.ZERO
func _physics_process(delta):
var direction = Vector3.ZERO
if Input.is_action_pressed("move_right"):
direction.x += 1
if Input.is_action_pressed("move_left"):
direction.x -= 1
if Input.is_action_pressed("move_back"):
direction.z += 1
if Input.is_action_pressed("move_forward"):
direction.z -= 1
if direction != Vector3.ZERO:
direction = direction.normalized()
$Pivot.look_at(translation + direction, Vector3.UP)
velocity.x = direction.x * speed
velocity.z = direction.z * speed
velocity.y -= fall_acceleration * delta
velocity = move_and_slide(velocity, Vector3.UP)
public class Player : KinematicBody
{
// How fast the player moves in meters per second.
[Export]
public int Speed = 14;
// The downward acceleration when in the air, in meters per second squared.
[Export]
public int FallAcceleration = 75;
private Vector3 _velocity = Vector3.Zero;
public override void _PhysicsProcess(float delta)
{
// We create a local variable to store the input direction.
var direction = Vector3.Zero;
// We check for each move input and update the direction accordingly
if (Input.IsActionPressed("move_right"))
{
direction.x += 1f;
}
if (Input.IsActionPressed("move_left"))
{
direction.x -= 1f;
}
if (Input.IsActionPressed("move_back"))
{
// Notice how we are working with the vector's x and z axes.
// In 3D, the XZ plane is the ground plane.
direction.z += 1f;
}
if (Input.IsActionPressed("move_forward"))
{
direction.z -= 1f;
}
if (direction != Vector3.Zero)
{
direction = direction.Normalized();
GetNode<Spatial>("Pivot").LookAt(Translation + direction, Vector3.Up);
}
// Ground velocity
_velocity.x = direction.x * Speed;
_velocity.z = direction.z * Speed;
// Vertical velocity
_velocity.y -= FallAcceleration * delta;
// Moving the character
_velocity = MoveAndSlide(_velocity, Vector3.Up);
}
}
プレイヤーの動きをテストする¶
これから、Mainのシーンにプレーヤーを置いて、テストしてみます。そのためには、プレーヤーをインスタンス化し、カメラを追加する必要があります。2D とは異なり、3D では、ビューポートにカメラがない場合は何も見えません。
Playerのシーンを保存し、Mainのシーンを開いてみてください。エディタ上部のMainタブをクリックすると開けます。
シーンを閉じていた場合は、ファイルシステム(FileSystem)ドックで、Main.tscn
をダブルクリックして再び開いてください。
Playerをインスタンス化するには、Mainノードを右クリックして、子シーンをインスタンス化(Instantiate Child Scene)を選択してください。
ポップアップで、Player.tscnをダブルクリックします。キャラクターがビューポートの中央に表示されるはずです。
カメラを追加する¶
次にカメラを追加しましょう。PlayerのPivotで行ったように、基本的なリグを作成することにします。Mainノードをもう一度右クリックし、今度は子ノードを追加(Add Child Node)を選択します。新しい Position3Dを作成し、それを CameraPivot と名づけ、Cameraノードをその子として追加してください。シーン ツリーは、次のようになります。
Cameraを選択しているとき左上に表示される プレビュー(Preview)チェック ボックスに注目してください。これをクリックすると、ゲーム内のカメラの投影をプレビューすることができます。
ここでは、Pivotを使用して、クレーンに乗っているかのようにカメラを回転させることにします。まず、3D ビューを分割して、シーンを自由に移動できるようにし、カメラが見ているものを確認できるようにしましょう。
ビューポートのすぐ上のツールバーで、ビュー(View)、そして2 ビューポート(2 Viewports)をクリックしてください。また、Ctrl + 2 (macOSは Cmd + 2 )を押すことも可能です。
下のビューでCameraを選択し、プレビューのチェックボックスをクリックしてカメラプレビューをオンにします。
上のビューで、カメラをZ軸(青い方)に19
ユニットほど移動させます。
Here's where the magic happens. Select the CameraPivot and rotate it -45
degrees around the X axis (using the red circle). You'll see the camera move as
if it was attached to a crane.
:kbd:`F6`を押してシーンを実行し、矢印キーを押してキャラクターを移動することができます。
透視投影(Perspective)を使用しているため、キャラクターの周囲の何もない空間が見えています。このゲームでは、ゲームプレイエリアをより適切に枠に収め、プレイヤーが距離を読みやすくするために、代わりに平行投影(Orthographic)を使用することにします。
再びCameraを選択し、インスペクター(Inspector)で、Projectionを Orthogonalに、Sizeを19
に設定します。これで、キャラクターはより平坦に見え、地面が背景を埋めるようになるはずです。
これで、プレイヤーの動きとビューの両方が整いました。次に、モンスターを作成していきます。