Work in progress
The content of this page was not yet updated for Godot
4.2
and may be outdated. If you know how to improve this page or you can confirm
that it's up to date, feel free to open a pull request.
プログラムでプレイヤーを動かす¶
いよいよコーディングです。前編で作成した入力アクションを使って、キャラクターを動かしていきます。
Right-click the Player
node and select Attach Script to add a new script to
it. In the popup, set the Template to Empty before pressing the Create
button.
まず、このクラスのプロパティから始めましょう。移動速度(speed)、重力を表す落下加速度(gravity)、そしてキャラクターを移動させるのに使う速度(velocity)を定義していきます。
extends CharacterBody3D
# 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 target_velocity = Vector3.ZERO
using Godot;
public partial class Player : CharacterBody3D
{
// 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 { get; set; } = 14;
// The downward acceleration when in the air, in meters per second squared.
[Export]
public int FallAcceleration { get; set; } = 75;
private Vector3 _targetVelocity = Vector3.Zero;
}
These are common properties for a moving body. The target_velocity
is a 3D vector
combining a speed with a direction. Here, we define it as a property because
we want to update and reuse its value across frames.
注釈
距離はメートル単位なので、2D コードとは値がかなり異なります。2D では、1000単位(ピクセル)は画面の幅の半分にしか相当しませんが、3D では 1km になります。
Let's code the movement. We start by calculating the input direction vector
using the global Input
object, in _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(double 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 += 1.0f;
}
if (Input.IsActionPressed("move_left"))
{
direction.X -= 1.0f;
}
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 += 1.0f;
}
if (Input.IsActionPressed("move_forward"))
{
direction.Z -= 1.0f;
}
}
ここでは、_physics_process()
仮想関数を使用してすべての計算を行います。_process()
と同様に、フレームごとにノードを更新することができますが、運動体や剛体の移動などの物理関連のコードに特化した設計になっています。
参考
_process()
と_physics_process()
の違いについては、 アイドル処理と物理処理 を参照してください。
まず、direction
変数を Vector3.ZERO
に初期化することからはじめます。次に、プレイヤーがmove_*
の入力を一つ以上押しているかどうかを確認し、ベクトルのx
、z
成分を適宜に更新します。これらの成分は地平面の軸に相当します。
この4つの条件は8つの可能性そして8つの可能な方向を与えます。
In case the player presses, say, both W and D simultaneously, the vector will
have a length of about 1.4
. But if they press a single key, it will have a
length of 1
. We want the vector's length to be consistent, and not move faster diagonally. To do so, we can
call its normalized()
method.
#func _physics_process(delta):
#...
if direction != Vector3.ZERO:
direction = direction.normalized()
$Pivot.look_at(position + direction, Vector3.UP)
public override void _PhysicsProcess(double delta)
{
// ...
if (direction != Vector3.Zero)
{
direction = direction.Normalized();
GetNode<Node3D>("Pivot").LookAt(Position + direction, Vector3.Up);
}
}
ここでは、方向が0より大きい長さを持つ場合、つまりプレイヤーが方向キーを押している場合にのみ、このベクトルを正規化(normalize)します。
In this case, we also get the Pivot
node and call its look_at()
method.
This method takes a position in space to look at in global coordinates and the
up direction. In this case, we can use the Vector3.UP
constant.
注釈
A node's local coordinates, like position
, are relative to their
parent. Global coordinates, like global_position
are relative to the world's main axes you can see
in the viewport instead.
In 3D, the property that contains a node's position is position
. By
adding the direction
to it, we get a position to look at that's one meter
away from the Player
.
Then, we update the velocity. We have to calculate the ground velocity and the
fall speed separately. Be sure to go back one tab so the lines are inside the
_physics_process()
function but outside the condition we just wrote above.
func _physics_process(delta):
#...
if direction != Vector3.ZERO:
#...
# Ground Velocity
target_velocity.x = direction.x * speed
target_velocity.z = direction.z * speed
# Vertical Velocity
if not is_on_floor(): # If in the air, fall towards the floor. Literally gravity
target_velocity.y = target_velocity.y - (fall_acceleration * delta)
# Moving the Character
velocity = target_velocity
move_and_slide()
public override void _PhysicsProcess(double delta)
{
// ...
if (direction != Vector3.Zero)
{
// ...
}
// Ground velocity
_targetVelocity.X = direction.X * Speed;
_targetVelocity.Z = direction.Z * Speed;
// Vertical velocity
if (!IsOnFloor()) // If in the air, fall towards the floor. Literally gravity
{
_targetVelocity.Y -= FallAcceleration * (float)delta;
}
// Moving the character
Velocity = _targetVelocity;
MoveAndSlide();
}
The CharacterBody3D.is_on_floor()
function returns true
if the body collided with the floor in this frame. That's why
we apply gravity to the Player
only while it is in the air.
For the vertical velocity, we subtract the fall acceleration multiplied by the delta time every frame. This line of code will cause our character to fall in every frame, as long as it is not on or colliding with the floor.
物理エンジンは、移動および衝突が起こった場合にのみ、特定のフレームにおける壁、床、または他の物体との相互作用を検出することができます。 後でこのプロパティを使ってジャンプをコーディングします。
On the last line, we call CharacterBody3D.move_and_slide()
which is a powerful
method of the CharacterBody3D
class that allows you to move a character
smoothly. If it hits a wall midway through a motion, the engine will try to
smooth it out for you. It uses the velocity value native to the CharacterBody3D
そして、これが床の上でキャラクターを動かすために必要なコードのすべてです。
参考までに、Player.gd
の完全なコードを以下に示します。
extends CharacterBody3D
# 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 target_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(position + direction, Vector3.UP)
# Ground Velocity
target_velocity.x = direction.x * speed
target_velocity.z = direction.z * speed
# Vertical Velocity
if not is_on_floor(): # If in the air, fall towards the floor. Literally gravity
target_velocity.y = target_velocity.y - (fall_acceleration * delta)
# Moving the Character
velocity = target_velocity
move_and_slide()
using Godot;
public partial class Player : CharacterBody3D
{
// How fast the player moves in meters per second.
[Export]
public int Speed { get; set; } = 14;
// The downward acceleration when in the air, in meters per second squared.
[Export]
public int FallAcceleration { get; set; } = 75;
private Vector3 _targetVelocity = Vector3.Zero;
public override void _PhysicsProcess(double delta)
{
var direction = Vector3.Zero;
if (Input.IsActionPressed("move_right"))
{
direction.X += 1.0f;
}
if (Input.IsActionPressed("move_left"))
{
direction.X -= 1.0f;
}
if (Input.IsActionPressed("move_back"))
{
direction.Z += 1.0f;
}
if (Input.IsActionPressed("move_forward"))
{
direction.Z -= 1.0f;
}
if (direction != Vector3.Zero)
{
direction = direction.Normalized();
GetNode<Node3D>("Pivot").LookAt(Position + direction, Vector3.Up);
}
// Ground velocity
_targetVelocity.X = direction.X * Speed;
_targetVelocity.Z = direction.Z * Speed;
// Vertical velocity
if (!IsOnFloor()) // If in the air, fall towards the floor. Literally gravity
{
_targetVelocity.Y -= FallAcceleration * (float)delta;
}
// Moving the character
Velocity = _targetVelocity;
MoveAndSlide();
}
}
プレイヤーの動きをテストする¶
We're going to put our player in the Main
scene to test it. To do so, we need
to instantiate the player and then add a camera. Unlike in 2D, in 3D, you won't
see anything if your viewport doesn't have a camera pointing at something.
Save your Player
scene and open the Main
scene. You can click on the Main
tab at the top of the editor to do so.
If you closed the scene before, head to the FileSystem dock and double-click
main.tscn
to re-open it.
To instantiate the Player
, right-click on the Main
node and select Instantiate
Child Scene.
In the popup, double-click player.tscn
. The character should appear in the
center of the viewport.
カメラを追加する¶
Let's add the camera next. Like we did with our Player's Pivot, we're
going to create a basic rig. Right-click on the Main
node again and select
Add Child Node. Create a new Marker3D, and name it CameraPivot
. Select CameraPivot
and add a child node Camera3D to it. Your scene tree should look like this.
Cameraを選択しているとき左上に表示される プレビュー(Preview)チェック ボックスに注目してください。これをクリックすると、ゲーム内のカメラの投影をプレビューすることができます。
ここでは、Pivotを使用して、クレーンに乗っているかのようにカメラを回転させることにします。まず、3D ビューを分割して、シーンを自由に移動できるようにし、カメラが見ているものを確認できるようにしましょう。
ビューポートのすぐ上のツールバーで、ビュー(View)、そして2 ビューポート(2 Viewports)をクリックしてください。また、Ctrl + 2 (macOSは Cmd + 2 )を押すことも可能です。
On the bottom view, select your Camera3D and turn on camera Preview by clicking the checkbox.
上のビューで、カメラを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
に設定します。これで、キャラクターはより平坦に見え、地面が背景を埋めるようになるはずです。
注釈
When using an orthogonal camera in Godot 4, directional shadow quality is dependent on the camera's Far value. The higher the Far value, the further away the camera will be able to see. However, higher Far values also decrease shadow quality as the shadow rendering has to cover a greater distance.
If directional shadows look too blurry after switching to an orthogonal
camera, decrease the camera's Far property to a lower value such as
100
. Don't decrease this Far property too much, or objects in the
distance will start disappearing.
Test your scene and you should be able to move in all 8 directions and not glitch through the floor!
Ultimately, we have both player movement and the view in place. Next, we will work on the monsters.