Godotでホラーゲーム開発チュートリアル Part 5: 敵キャラクターの作成とスクリプトの実装
Godot Engine バージョン4.3
このパートでは、ホラーゲームの敵キャラクターを作成し、プレイヤーを追跡するAIを実装します。NavigationAgent3Dを活用して、マップ内をパトロールしつつ、プレイヤーを発見すると追跡するように設定します。また、足音やアニメーションを組み込み、よりリアルな動きを実現します。
ステップ1: 敵キャラクターのモデルを用意
まずは敵の3Dモデルを準備し、
enemy.tscn
というシーンを作成します。1.1 敵のノード構成
以下のような構成でノードを追加します。
Diablo (CharacterBody3D)├── Scene (準備した敵の3Dモデル)├── CollisionShape3D (当たり判定)├── NavigationAgent3D (ナビゲーション制御)├── AnimationPlayer (アニメーション管理)├── PlayerSensor (RayCast3D, プレイヤー検知)├── WaitingTimer (Timer, パトロール用)├── FootsSound (AudioStreamPlayer3D, 足音再生)├── FindPlayer (Area3D, プレイヤー検知範囲)│ ├── CollisionShape3D (範囲設定)├── DeadArea (Area3D, プレイヤーが捕まるエリア)│ ├── CollisionShape3D (範囲設定)
Scene
→ 敵の3Dモデルを配置CollisionShape3D
→ 当たり判定を設定NavigationAgent3D
→ マップ内を移動するために使用AnimationPlayer
→ 走る、待機などのアニメーション制御PlayerSensor
→ RayCast3D
でプレイヤーを検知WaitingTimer
→ パトロール時の待機時間を管理FootsSound
→ 足音の再生FindPlayerArea
→ Area3D
でプレイヤーを探知DeadArea
→ Area3D
でプレイヤーが捕まる処理
ちょっとしたポイント!
FindPlayerArea
はArea3D
でプレイヤーを探知しますが、もしプレイヤーが壁越しのいても探知して追いかけてします。それだと見つかっていないのに知らないところから襲われてゲームオーバーになってしまいます。
そこでPlayerSensor
(RayCast3D
)を使って、もしプレイヤーがFindPlayerArea
に入った時にPlayerSensor
が障害物に当たらなかったらプレイヤーを追っかけます!なのでプレイヤーが敵の足音がしたら安心して障害物に隠れたりしてドキドキ感を出せる仕組みになっています!
ステップ2: スクリプトを追加
2.1 enemy.gd
を設定
敵のAIを制御するスクリプトを
enemy.gd
に追加します。extends CharacterBody3D
# プレイヤーへの参照var player = null
# 敵の移動速度@export var speed = 5.0# 敵が移動するランダムな経路ポイントのリスト@export var randomPaths:Array[Node3D]# プレイヤーが死亡したときに表示されるシーン@export var dead_player:PackedScene
# ナビゲーション用のエージェントノード@onready var nav_agent:NavigationAgent3D = $NavigationAgent3D# 敵が待機する時間を管理するタイマー@onready var waiting_timer = $WaitingTimer# アニメーションの再生を管理@onready var animation_player = $AnimationPlayer# 足音の効果音を再生@onready var foots_sound = $FootsSound# プレイヤーを検知するためのセンサー@onready var player_sensor = $PlayerSensor
# 次の移動先var next_path# 敵が注視する座標var lookAtVar = Vector3.ZERO# プレイヤーを発見したかどうかvar is_player_found = false# 足音の間隔を測る変数var distanceFootstep := 0.0# プレイヤーの現在位置var player_pos
func _ready(): # ランダムな移動ポイントを選択し、次の移動先として設定 if randomPaths.size() > 0: next_path = randomPaths[randi_range(0, randomPaths.size() - 1)]
func _process(delta): # プレイヤーセンサーがプレイヤーを注視 if player_pos: player_sensor.look_at(player_pos.global_transform.origin, Vector3.UP)
# センサーが衝突した場合の処理 if player_sensor.is_colliding(): var detected = player_sensor.get_collider() if detected is PlayerInteractable: # プレイヤーを検出した場合、追跡を開始 waiting_timer.stop() is_player_found = true next_path = detected nav_agent.set_target_position(next_path.global_transform.origin) # ナビゲーションエージェントがターゲットに到達していない場合 if nav_agent.is_target_reachable() and not nav_agent.is_target_reached() and next_path: # 移動アニメーションを再生 animation_player.play("run/Armature") velocity = Vector3.ZERO nav_agent.set_target_position(next_path.global_transform.origin) var next_nav_point = nav_agent.get_next_path_position() # 次の経路ポイントに向けて移動 velocity = (next_nav_point - global_transform.origin).normalized() * speed # 足音の処理 processGroundSounds()
# 次のポイントを向く look_at(Vector3(next_nav_point.x, next_nav_point.y, next_nav_point.z), Vector3.UP) # 実際に移動を行う move_and_slide() else: # 到達した場合、待機アニメーションを再生 animation_player.play("Armature") if waiting_timer.is_stopped(): # 待機時間をランダムに設定してタイマーを開始 var random_time = randi_range(1, 3) waiting_timer.wait_time = random_time waiting_timer.start() func processGroundSounds(): # 敵の移動時に足音を再生する if (int(velocity.x) != 0) || int(velocity.z) != 0: distanceFootstep += .1
if distanceFootstep > 3.5: # ランダムなピッチで足音を再生 foots_sound.pitch_scale = randf_range(.8, 1.2) foots_sound.play() distanceFootstep = 0.0
func _on_waiting_timer_timeout(): # タイマーが終了したときに次の移動先をランダムに設定 if !is_player_found: var random_pos = randi_range(0, randomPaths.size() - 1) next_path = randomPaths[random_pos] nav_agent.set_target_position(next_path.global_transform.origin)
func _on_find_player_body_entered(body): # プレイヤーを検知した場合の処理 if body.name == "Player": player_pos = body
func _on_dead_area_body_entered(body): # プレイヤーが死亡エリアに入った場合、ゲームオーバーシーンに切り替え if body.name == "Player": get_tree().change_scene_to_packed(dead_player)
func _on_find_player_body_exited(body): # プレイヤーが検知範囲から外れた場合の処理 if body.name == "Player": player_pos = null player_sensor.rotation = Vector3.ZERO
ステップ3: 敵のパトロール設定
3.1 パトロールポイントを設置
Node3D
を複数配置し、敵の移動ポイントを設定(例: PatrolPoint1, PatrolPoint2, ...
)。それらを
Enemy
のrandomPaths
に設定。
ステップ4: ゲームへの組み込み
main.tscn
に enemy.tscn
を配置。NavigationRegion3D
の範囲内に敵が移動できることを確認。プレイヤーが敵に近づくと追跡されるかをテスト。
まとめ
このパートでは、敵のAIを実装し、パトロールやプレイヤーの追跡が可能になりました!
1
100%