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 → 走る、待機などのアニメーション制御
PlayerSensorRayCast3Dでプレイヤーを検知
WaitingTimer → パトロール時の待機時間を管理
FootsSound → 足音の再生
FindPlayerAreaArea3Dでプレイヤーを探知
DeadAreaArea3Dでプレイヤーが捕まる処理



ちょっとしたポイント!
FindPlayerAreaArea3Dでプレイヤーを探知しますが、もしプレイヤーが壁越しのいても探知して追いかけてします。それだと見つかっていないのに知らないところから襲われてゲームオーバーになってしまいます。 そこでPlayerSensorRayCast3D)を使って、もしプレイヤーが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, ...)。
それらをEnemyrandomPathsに設定。



ステップ4: ゲームへの組み込み

main.tscnenemy.tscn を配置。
NavigationRegion3D の範囲内に敵が移動できることを確認。
プレイヤーが敵に近づくと追跡されるかをテスト。



まとめ

このパートでは、敵のAIを実装し、パトロールやプレイヤーの追跡が可能になりました!
最終更新日: 2025/01/30 06:21

コメント