Godotの3D移動を6分の動画で学ぶ

3D Movement in Godot in Only 6 Minutes

YouTube GDQuest 2021年10月31日より。

ハイライト:

  • 前後左右に動きジャンプするキャラクターと、モデルの動きに追随するカメラの設定方法

3D Movement in Godot in Only 6 Minutes


00:00 Making your 1st 3D game can be daunting. 初めての3Dゲームを作るのは大変なことです。
00:02 With the additional third axis, just trying to rotate your character can be a nightmare. 第3の軸が追加されたことで、キャラクターを回転させるだけでも悪夢のような作業になってしまいます。
00:07 In this video, you'll see how to create a third person character that moves relative to the camera and jumps. この動画では、カメラに対して相対的に動き、ジャンプする三人称視点のキャラクターを作成する方法を説明します。
00:13 We'll also take a look at rotating the character model towards the direction of movement. また、キャラクターモデルを移動方向に向けて回転させる方法も見てみましょう。
00:17 There are two physics bodies we use to move objects in 3D. 3Dでオブジェクトを動かすためには、2つの物理ボディがあります。
00:21 Rigid body and Kinematic body. リジッド・ボディとキネマティック・ボディです。
00:23 Rigid bodies have realistic physics. リジッド・ボディはリアルな物理現象です。
00:25 They're quick to set up, but hard to master. 設定はすぐにできますが、使いこなすのは大変です。
00:27 Kinematic bodies give you more control, and they come with handy functions ideal for third person movement. キネマティック・ボディは、よりコントロールしやすく、三人称の動きに最適な便利な機能を備えています。
00:32 In a new scene, we create a Kinematic Body with the collision shape. 新しいシーンでコリジョン形状を備えたキネマティック・ボディを作成してみましょう。
00:36 We've added our own animated character to the scene, but you can use any model or mesh instance instead. ここでは独自のアニメーション・キャラクターをシーンに追加しましたが、モデルやメッシュ・インスタンスを使ってもいいです。
00:41 Two common collision shapes for characters in 3D are capsules and cylinders. 3Dのキャラクターによく使われるコリジョン形状には、カプセルと円柱があります。
00:45 Capsules have no hard edges, making your character move smoothly on slopes and corners. カプセルには硬いエッジがないので、キャラクターは坂道や角でスムーズに動けます。
00:49 A character with a capsule can run up small stairs, but it also slides off platforms when standing on edges. カプセルを持つキャラクターは、小さな階段を駆け上がることができますが、端に立つとプラットフォームから滑り落ちてしまいます。
00:55 Cylinders can stay on edges, but their movement won't be nearly as smooth on uneven terrain and stairs. 円柱は端に立つことができますが、凹凸のある地形や階段では動きがスムーズになりません。
01:01 I recommend a capsule to get started. 最初はカプセルをお勧めします。
01:03 They're generally easier to use. 一般的にはカプセルの方が使いやすいのです。
01:05 With the collision shape selected, we add a capsule shape and rotate it to fit our character. コリジョン形状を選択した状態で、カプセル形状を追加し、キャラクターに合わせて回転させます。
01:10 We also reduced the shape radius to zero five to match our character better. また、キャラクターに合わせてシェイプの半径を0.5にしました。
01:15 In 3D, you need the camera to render the game world. 3Dでは、ゲームの世界をレンダリングするためにカメラが必要です。
01:18 To make the camera follow and orbit the character, we add a Spring Arm node to the player with a camera child. カメラがキャラクターに追従して軌道を回るようにするために、カメラを子ノードとして持つスプリング・アームというノードをプレイヤーに追加します。
01:23 The Spring Arm constrains the camera to the player at a distance. スプリング・アームは、カメラをプレイヤーから離れた位置に固定します。
01:27 It also makes the camera collide with the world, preventing it from getting inside walls. また、カメラをワールドに衝突させて、壁の中に入らないようにします。
01:30 We'll get back to setting up the Spring Arm later, when we rotate the camera. スプリング・アームの設定については、後ほどカメラを回転させるときに説明しますね。
01:34 The next step is coding the character movement. 次のステップは、キャラクターの動きをコードで書きあらわすことです。
01:37 We add a script to the Kinematic Body node. キネマティック・ボディノードにスクリプトを追加しましょう。
01:39 Kinematic Body gives us the move and slide with Snap function. キネマティック・ボディは、スナップ機能による移動とスライドを提供してくれます。
01:42 It handles movement on slopes for us, keeping the character grounded while running. 斜面での動きも処理してくれるので、キャラクターが走っている間も安定しています。
01:46 We start with variables for movement, speed, jump strength, and gravity. まず、動き、スピード、ジャンプの強さ、そして重力の変数を用意します。
01:51 The move and slide with snap function expects a velocity and a snap vector, so we define these two variables next. move_and_slide_with_snap()は、速度とスナップのベクトルを必要とするので、次にこの2つの変数を定義します。
01:57 The snap vector points to the ground to ground our character. スナップ・ベクトルは地面を指し、キャラクターを接地させます。
02:00 When jumping or falling, we turn off snapping by setting it to zero. ジャンプしたり落下したりするときは、スナップをゼロにしてオフにします。
02:04 We cache the player model and the Spring Arm into on ready variables. プレイヤーモデルとスプリング・アームをオンレディ変数にキャッシュします。
02:09 In physics process, We start by calculating the player's desired direction. 物理処理では、まずプレイヤーが行きたい方向を計算します。
02:13 To get precise inputs on controllers, while supporting keyboards, we use Input.get_action_strength(). キーボードをサポートしつつ、コントローラの正確な入力を得るために、Input.get_action_strength()を使います。
02:18 The left, forward, back, right, and jump input actions are defined in the project's input map. 左、前、後ろ、右、ジャンプの入力アクションは、プロジェクト設定の入力マップで定義されています。
02:25 Now, because the camera can orbit around the character, we also need to rotate the input direction to match the camera's look direction. さて、カメラはキャラクターの周りを回ることができるので、カメラの視線方向に合わせて入力方向を回転させる必要があります。
02:32 That way, the character always moves forward, back or sideways. そうすれば、キャラクターは常に前にも後ろにも横にも動くことになります。
02:36 We rotate the move direction by the Spring Arm's Y rotation. ここでは、スプリング・アームのY方向の回転によって、移動方向を回転させましょう。
02:39 This makes the move direction relative to the camera. これにより、移動方向はカメラに対して相対的になります。
02:42 We normalize the move direction, so going diagonally doesn't make us go faster. 移動方向を正規化して、斜めに移動しても速くならないようにします。
02:47 We use the direction to set the velocity variable on the ground. この方向性を利用して、地面での速度変数を設定します。
02:50 Every frame we also want to pull the velocity down using gravity. また、毎フレーム、重力を利用して速度を下げます。
02:54 Applying gravity lets us detect when the character is on the floor, using the is_on_floor() function. 重力を利用すると、is_on_floor()関数を使って、キャラクターが床にいることを検出できます。
02:59 We can use the Snap Vector and is_on_floor() to check if we just landed. スナップベクターとis_on_floor()を使って、着地したばかりかどうかを確認できます。
03:03 If the character is on the floor and the player presses jump, then they're jumping. キャラクターが床の上にいて、プレイヤーがjumpを押した場合は、ジャンプしていることになります。
03:07 Jumping should have priority, so we set the Y-velocity to jump_strength. ジャンプが優先されるべきなので、Y-velocityをjump_strengthに設定します。
03:11 And we zero out the snap vector to turn off snapping. そして、スナップベクターをゼロにして、スナップをオフにします。
03:14 When landing, we point the snap vector down to snap the character to the ground. 着地時には、スナップベクターを下に向けて、キャラクターを地面に固定します。
03:19 Next, we use the velocity and Snap Vector in a call to move and slide with Snap. 次に、速度とスナップベクターを使って、スナップを使った移動とスライドの呼び出しを行います。
03:24 This function needs our velocity snap vector and the up direction. この関数には、速度のスナップベクターと、上方向が必要です。
03:27 We also tell it to stop on slopes without sliding down. また、坂道では滑らずに止まるように指示します。
03:31 The function returns an updated velocity that takes collisions into account; この関数は、衝突を考慮して更新された速度を返します。
03:35 resetting the Y-velocity to zero when landing. 着地時には Y-velocity を 0 にリセットします。
03:38 We store this value in our velocity variable. この値をvelocity変数に格納します。
03:41 Before moving on to the camera script, in process, we update the Spring Arm to move with the character every frame. カメラスクリプトに移る前に、処理として、スプリング・アームを更新して、フレームごとにキャラクターと一緒に動くようにしています。
03:47 You'll see why shortly. 理由は後ほど説明します。
03:48 Now we've got code to move relative to the camera, but the camera doesn't rotate yet. さて、カメラに対して相対的に移動するコードができましたが、カメラはまだ回転しません。
03:53 First, we set the Spring Arm's properties. まず、スプリング・アームのプロパティを設定します。
03:55 The Spring Length controls how far the camera moves away from the character. スプリングの長さは、カメラがキャラクターからどれだけ離れるかを制御します。
03:59 You also want to increase the margin to zero two or larger to give the camera more room to collide. また、カメラが衝突する際の余裕を持たせるために、マージンを0.2以上にします。
04:05 The point o one default tends to clip inside walls. デフォルトの0.01では、壁の内側でクリップしてしまう傾向があります。
04:08 Next, we attach a new script to the Spring Arm node to control the rotation. 次に、回転を制御するための新しいスクリプトをスプリング・アーム ノードにアタッチします。
04:12 we add an exported variable for rotation speed. ここでは、回転速度を表すエクスポートされた変数を追加します。
04:15 By calling set as top level True in the ready function, the camera can move independently from the character. ready関数の中でsetをトップレベルのTrueにすることで、カメラはキャラクターから独立して動くことができるようになります。
04:21 It won't inherit its position, rotation, or scale. カメラはキャラクターの位置、回転、スケールを継承しません。
04:24 That's why we added a line in the character script to reposition the camera every frame. そのため、キャラクターのスクリプトの中に、1フレームごとにカメラの位置を変更する行を追加しました。
04:28 We capture the mouse cursor inside the window by calling Input.set_mouse_mode(). Input.set_mouse_mode()を呼び出して、マウスカーソルをウィンドウ内に取り込みます。
04:33 This hides and locks the cursor to the game window. これにより、カーソルはゲームウィンドウに隠され、固定されます。
04:35 You can release the mouse by passing a different mode instead. 代わりに別のモードを渡すことで、マウスを解放することができます。
04:39 In unhandled input, we check the event is mouse movement before continuing; ハンドリングされていない入力では、イベントがマウスの動きであることを確認してから続行します。
04:43 moving the mouse up and down on the screen pitches the camera up and down, left and right mouse movement rotates the camera around the Y axis. 画面上でマウスを上下に動かすとカメラが上下に動き、マウスを左右に動かすとY軸を中心にカメラが回転します。
04:51 The clamp() function prevents the camera from rolling over. clamp()関数は、カメラが横転するのを防ぎます。
04:54 You can change the degree amounts here to limit the camera's pitch. ここで度量を変更することで、カメラのピッチを制限することができます。
04:58 Meanwhile, the wrap F function makes the angle wrap between zero and 360 degrees. 一方、wrap F関数は、角度を0度から360度の間で折り返します。
05:03 So the rotation doesn't accumulate. そのため、回転が蓄積されないようになっています。
05:05 Finally, we want to rotate the character model to match its movement. 最後に、キャラクターモデルの動きに合わせて回転させたいと思います。
05:09 Back in the character script, We check the velocity length first so we don't face forward when we stop moving. キャラクタースクリプトに戻って、動きを止めたときに前を向かないように、まずverocity lengthをチェックします。
05:15 Then we store the velocity Z and X in a Vector2 as our look direction. そして、ベロシティのZとXをVector2に格納して、見る方向としています。
05:20 I know it looks backwards. 後ろ向きになるみたいですよね。
05:22 Trust me, we need the opposite of what it normally returns here. 信じてください、ここでは通常の戻り値とは逆の値が必要なのです。
05:26 Finally, we set the model's Y rotation to the look direction angle. 最後に、モデルのY回転を見る方向の角度に設定します。
05:29 The angle function turns a Vector2 into radiance. 角度関数は、Vector2をラジアンに変換します。
05:33 We assign this to the rotation Y property so it faces the angle of movement. これをY回転プロパティに割り当てて、動きの角度に向くようにします。
05:37 And there you have it. これで完成しました。
05:39 A 3D platformer character with a rotating player model and camera. プレイヤーモデルとカメラが回転する、3Dプラットフォーマーのキャラクターです。
05:43 We're making a course to teach you and people you love how to make games from absolutely zero with Godot. 私たちは、Godotを使ってまったくゼロからゲームを作る方法を、あなたや好きな人たちに教えるコースを作っています。
05:49 Along with the course, we're developing a free and open source app that will help everyone practice code right in their browser. また、このコースと並行して、誰もがブラウザ上でコードを練習できる無料のオープンソース・アプリも開発しています。
05:55 We're on Kickstarter to make this project a reality. このプロジェクトを実現するために、私たちはKickstarterに参加しています。
05:58 You can get the course at halfway, the final price, but be quick because the campaign ends on October 31. このキャンペーンは10月31日までですので、お早めにどうぞ。
06:07 Check out the link in the description to get all the details. 詳細は説明文にあるリンクをご覧ください。

スクリプト

KinematicBodyにアタッチするコード

extends KinematicBody

export var speed := 7.0
export var jump_strength := 20.0
export var gravity := 50.0

var _velocity := Vector3.ZERO
var _snap_vector := Vector3.DOWN

onready var _spring_arm: SpringArm = $SpringArm
onready var _model: Spatial = $mirai

func _physics_process(delta: float) -> void:
	var move_direction := Vector3.ZERO
	move_direction.x = Input.get_action_strength("right") - Input.get_action_strength("left")
	move_direction.z = Input.get_action_strength("back") - Input.get_action_strength("forward")
	move_direction = move_direction.rotated(Vector3.UP, _spring_arm.rotation.y).normalized()
	
	_velocity.x = move_direction.x * speed
	_velocity.z = move_direction.z * speed
	_velocity.y -= gravity * delta
	
	var just_landed := is_on_floor() and _snap_vector == Vector3.ZERO
	var is_jumping := is_on_floor() and Input.is_action_just_pressed("jump")
	if is_jumping:
		_velocity.y = jump_strength
		_snap_vector = Vector3.ZERO
	elif just_landed:
		_snap_vector = Vector3.DOWN
	_velocity = move_and_slide_with_snap(_velocity, _snap_vector, Vector3.UP, true)

	if _velocity.length() > 0.2:
		var look_direction = Vector2(_velocity.z, _velocity.x)
		_model.rotation.y = look_direction.angle()


func _process(_delta: float) -> void:
	_spring_arm.translation = translation

SpringArmにアタッチするコード

# extends SpringArm
extends Spatial

export var mouse_sensitivity := 0.05


func _ready()  -> void:
	set_as_toplevel(true)
	#Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)	# カーソルが見えないと不便なので隠す設定はしない


func _unhandled_input(event: InputEvent) -> void:
	if event is InputEventMouseMotion:
		rotation_degrees.x -= event.relative.y * mouse_sensitivity
		rotation_degrees.x = clamp(rotation_degrees.x, -90.0, 30.0)
		
		rotation_degrees.y -= event.relative.x * mouse_sensitivity
		rotation_degrees.y = wrapf(rotation_degrees.y, 0.0, 360.0)


関連記事