「ゲームに追いかけてくる敵を出したい」「敵が壁にぶつかって止まってしまう」
ロブロックス(Roblox)で追いかけてくる敵の作り方を、実際の画像付きで誰でも簡単に分かるように解説します。ここでは、いちばん近いプレイヤーを追いかけて、触れると体力が減る敵を作ります。後半では、壁があると敵が止まってしまうことを確かめてから、壁を回り込んで追ってくるように書きかえていきます。
中級ステップ10/28 前の記事:ロブロックス(Roblox)スタジオでNPCを作る方法 全体の順番を見る
使うもの:Roblox Studioが起動するパソコン、マウス、キーボード。NPCの置き方はNPCを作る方法で画像付きで解説しています。
今回の完成状態:下の画像のように、敵(画像の2)に追いつかれると、右上の体力バー(画像の1)が減れば成功です。

1. 敵の体を置く
新しいBaseplateを開き、3D画面上部メニューの「ホーム」タブの「キャラクター」ボタンを押します。
「リグを生成」の窓が開きます。リグタイプの「R15」を選んだまま、「ブロック型アバター(2012年)」を押します。3D画面に灰色のキャラクターが置かれ、エクスプローラのWorkspaceの中に「Rig」ができます。窓の画像はNPCを作る方法の1で見られます。
エクスプローラで「Rig」を選んだ状態でF2キーを押し、名前を「Enemy」に変えます(赤い枠)。

エクスプローラでEnemyの左の三角を押して開き、中の「Humanoid」を選んだ状態で、プロパティの検索欄に「DisplayName」と入力し、DisplayNameを「敵」にします(赤い枠)。

エクスプローラでEnemyを選び直し、プロパティの検索欄に「Origin」と入力して、Originの下のPositionを「0, 3, -40」にしてEnterキーを押します(赤い枠)。スポーン地点から40スタッド奥に動きます。画面では最初の数が「-0」と出ることがありますが、0と同じです。

敵を歩かせるので、HumanoidRootPartのAnchoredにチェックが入っていないことを確かめます。エクスプローラでEnemyの中の「HumanoidRootPart」を選んだ状態で、プロパティの検索欄に「Anchored」と入力します。最初からチェックは入っていません(画像の2)。3D画面の奥に敵が立っています(画像の1)。

2. 追いかけて、触れると体力が減るスクリプトを入れる
エクスプローラの「ServerScriptService」にマウスを乗せて、右に出る「+」を押し、検索欄に「Script」と入力して、一覧の「Script」を押します。
エクスプローラでServerScriptServiceの中に入った「Script」を選んだ状態でF2キーを押し、名前を「EnemyService」に変えます。
EnemyServiceを選んだまま、プロパティの検索欄に「RunContext」と入力し、「▼動作」の欄にあるRunContextの一覧から「Server」を選びます(赤い枠)。RunContextは、スクリプトをどこで動かすかを決める設定で、「Server」はゲームのサーバー側で動かす設定です。

EnemyServiceの画面で、中身を全部消して、次のコードを貼り付けます。
local Players = game:GetService("Players")
local enemy = workspace:WaitForChild("Enemy")
local humanoid = enemy:WaitForChild("Humanoid")
local root = enemy:WaitForChild("HumanoidRootPart")
local CHASE_RANGE = 60 -- この距離(スタッド)より近い人を追いかける
local DAMAGE = 20 -- 1回に減らす体力
local COOLDOWN = 1 -- 同じ人にもう一度当たるまでの秒数
humanoid.WalkSpeed = 12 -- プレイヤー(16)より少し遅くして、逃げられるようにする
-- 敵の動きの計算をサーバーに任せる(固定されているとできないので、そのときは知らせる)
local ok = pcall(function()
root:SetNetworkOwner(nil)
end)
if not ok then
warn("Enemy のパーツの Anchored を外してください")
end
-- いちばん近い、生きているプレイヤーの HumanoidRootPart を探す
local function findNearestTarget()
local nearest = nil
local nearestDistance = CHASE_RANGE
for _, player in Players:GetPlayers() do
local character = player.Character
local targetHumanoid = character and character:FindFirstChildOfClass("Humanoid")
local targetRoot = character and character:FindFirstChild("HumanoidRootPart")
if targetHumanoid and targetRoot and targetHumanoid.Health > 0 then
local distance = (targetRoot.Position - root.Position).Magnitude
if distance < nearestDistance then
nearest = targetRoot
nearestDistance = distance
end
end
end
return nearest
end
-- 敵に触れた人の体力を減らす(同じ人は COOLDOWN 秒に1回まで)
local lastHit = {}
local function onTouched(hit)
if humanoid.Health <= 0 then return end
local character = hit.Parent
local player = Players:GetPlayerFromCharacter(character)
if not player then return end -- アバター以外が触れたときは何もしない
local targetHumanoid = character:FindFirstChildOfClass("Humanoid")
if not targetHumanoid or targetHumanoid.Health <= 0 then return end
local now = os.clock()
if lastHit[player] and now - lastHit[player] < COOLDOWN then return end
lastHit[player] = now
targetHumanoid:TakeDamage(DAMAGE)
end
-- 敵の体のパーツ全部に「触れたとき」をつなぐ
for _, part in enemy:GetDescendants() do
if part:IsA("BasePart") then
part.Touched:Connect(onTouched)
end
end
-- 抜けた人の記録を消す
Players.PlayerRemoving:Connect(function(player)
lastHit[player] = nil
end)
-- 0.2秒ごとに、いちばん近い人の位置へ歩かせる
while humanoid.Health > 0 do
local target = findNearestTarget()
if target then
humanoid:MoveTo(target.Position)
else
humanoid:MoveTo(root.Position) -- 近くに誰もいなければ、その場で止まる
end
task.wait(0.2)
end

| コードの部分 | していること |
|---|---|
| findNearestTarget | 全員の中から、CHASE_RANGEで決めた60スタッドより近くて体力が残っている人を1人選ぶ |
| 0.2秒ごとのMoveTo | 相手は動くので、行き先を何度も出し直す |
| onTouchedとlastHit | 触れたのがプレイヤーか確かめ、人ごとに最後に当てた時刻を覚えて、COOLDOWNで決めた1秒がたつまでは減らさない |
| TakeDamage | 体力を減らす |
| SetNetworkOwner(nil) | 敵の動きをサーバーが計算する |
MoveToで歩かせたNPCは、8秒たっても行き先に着かないと止まります。ただし、行き先を出し直すと、8秒は最初から数え直しになります(どちらもRobloxの公式の説明に書かれている決まりです)。このコードは0.2秒ごとに出し直すので、途中で止まりません。
3. テストする
3D画面の上の作品のタブ「Place7」を押して3D画面に戻り、F5キーでテストを始めます(タブの名前はパソコンによって違います)。敵がこちらへ歩いてきます(赤い枠)。

敵に追いつかれると、右上の体力バーが減ります(画像の1)。敵(画像の2)は、すぐ後ろにくっついてきます。

体力が0になるとアバターは倒れて、少しするとスポーン地点で生き返ります。
以下は、アソビシフト編集部が新しいBaseplateで確かめた結果です。生き返った直後も、すぐに体力が減りました。新しいBaseplateのスポーン地点は、出てすぐの守り(ForceField)の時間(Duration)が0になっているためです。エクスプローラで「SpawnLocation」を選んだ状態で、プロパティの検索欄に「Duration」と入力すると、0になっているのが分かります。出てすぐの数秒を守りたいときは、このDurationの数を大きくします(Robloxの公式の説明では、出てきた人をその秒数だけForceFieldで守る設定です)。
4. 敵の速さを決める
コードの humanoid.WalkSpeed = 12 で、敵の速さが決まります。プレイヤーの歩く速さの初期値は16です。変えるときは、エクスプローラのServerScriptServiceの中の「EnemyService」をダブルクリックして開き、コードの11行目「humanoid.WalkSpeed = 12」の12を書き換えます。
| WalkSpeed | プレイヤー(16)と比べて |
|---|---|
| 8 | 半分の速さ |
| 12(このコード) | 4分の3の速さ。まっすぐ逃げれば離れていく |
| 16 | 同じ速さ |
| 20 | プレイヤーより速い。まっすぐ逃げても追いつかれる |
5. 壁を置くと、敵が止まってしまう
Shift + F5キーでテストを停止します。
スポーン地点と敵のあいだに壁を置きます。エクスプローラの「Workspace」にマウスを乗せて、右に出る「+」を押し、検索欄に「Part」と入力して、一覧の「Part」を押します。
エクスプローラでWorkspaceの中に入った「Part」を選んだ状態でF2キーを押し、名前を「Wall」に変えます。
Wallのプロパティは次のとおりにします。
| プロパティ | 値 |
|---|---|
| Size | 30, 10, 2 |
| Position(▼CFrameのすぐ下) | 0, 5, -20 |
| Anchored | チェックを入れる |
| Color | 124, 92, 70(BrickColorの欄は「Brown」になる) |
順にやっていきます。Wallを選んだまま、プロパティの検索欄に「Size」と入力し、Sizeの欄を「30, 10, 2」にしてEnterキーを押します(赤い枠)。

続けてWallを選んだまま、プロパティの検索欄に「Position」と入力し、「▼CFrame」のすぐ下のPositionを「0, 5, -20」にしてEnterキーを押します(赤い枠)。

続けてWallを選んだまま、プロパティの検索欄に「Anchored」と入力し、チェックを入れて固定します(赤い枠)。

続けてWallを選んだまま、プロパティの検索欄に「Color」と入力します。BrickColorとColorの2つの欄が並ぶので、下のColorの欄の数字を「124, 92, 70」に書き換えてEnterキーを押します。壁が茶色になり、上のBrickColorの欄が「Brown」に変わります(赤い枠)。

3D画面の上の作品のタブ「Place7」を押して3D画面に戻り、F5キーでテストを始めます(タブの名前はパソコンによって違います)。
ただ、敵は壁の手前で止まってしまいました(赤い枠)。しばらく待っても、壁の手前から動きません。MoveToは、行き先へまっすぐ歩くだけで、壁をよけてはくれないからです。

6. 壁を回り込む「経路探索」に書きかえる
Shift + F5キーでテストを停止します。
壁をよけて歩かせるには、PathfindingService(パスファインディングサービス)で道を計算します。コードの変更箇所は、2行目のPathfindingService、定数STEPS_BEFORE_RECALC、CreatePathの部分、chaseWithPathの部分、最後のくり返しです。WalkSpeedの行などの説明のコメントは省いています。findNearestTargetとonTouchedの動きは同じです。
エクスプローラのServerScriptServiceの中の「EnemyService」をダブルクリックして開き、中身を全部消して、次のコードを貼り付けます。
local Players = game:GetService("Players")
local PathfindingService = game:GetService("PathfindingService")
local enemy = workspace:WaitForChild("Enemy")
local humanoid = enemy:WaitForChild("Humanoid")
local root = enemy:WaitForChild("HumanoidRootPart")
local CHASE_RANGE = 60 -- この距離(スタッド)より近い人を追いかける
local DAMAGE = 20 -- 1回に減らす体力
local COOLDOWN = 1 -- 同じ人にもう一度当たるまでの秒数
local STEPS_BEFORE_RECALC = 4 -- 道の点をいくつ進んだら計算し直すか
humanoid.WalkSpeed = 12
local ok = pcall(function()
root:SetNetworkOwner(nil)
end)
if not ok then
warn("Enemy のパーツの Anchored を外してください")
end
-- 敵の大きさに合わせた道の探し方(数字は Roblox の標準と同じ)
local path = PathfindingService:CreatePath({
AgentRadius = 2,
AgentHeight = 5,
AgentCanJump = true,
})
local function findNearestTarget()
local nearest = nil
local nearestDistance = CHASE_RANGE
for _, player in Players:GetPlayers() do
local character = player.Character
local targetHumanoid = character and character:FindFirstChildOfClass("Humanoid")
local targetRoot = character and character:FindFirstChild("HumanoidRootPart")
if targetHumanoid and targetRoot and targetHumanoid.Health > 0 then
local distance = (targetRoot.Position - root.Position).Magnitude
if distance < nearestDistance then
nearest = targetRoot
nearestDistance = distance
end
end
end
return nearest
end
local lastHit = {}
local function onTouched(hit)
if humanoid.Health <= 0 then return end
local character = hit.Parent
local player = Players:GetPlayerFromCharacter(character)
if not player then return end
local targetHumanoid = character:FindFirstChildOfClass("Humanoid")
if not targetHumanoid or targetHumanoid.Health <= 0 then return end
local now = os.clock()
if lastHit[player] and now - lastHit[player] < COOLDOWN then return end
lastHit[player] = now
targetHumanoid:TakeDamage(DAMAGE)
end
for _, part in enemy:GetDescendants() do
if part:IsA("BasePart") then
part.Touched:Connect(onTouched)
end
end
Players.PlayerRemoving:Connect(function(player)
lastHit[player] = nil
end)
-- 相手までの道を計算して、少しだけ進む
local function chaseWithPath(targetRoot)
local success = pcall(function()
path:ComputeAsync(root.Position, targetRoot.Position)
end)
if success and path.Status == Enum.PathStatus.Success then
local waypoints = path:GetWaypoints()
-- 1つめの点は今いる場所なので、2つめから歩く
local last = math.min(#waypoints, 1 + STEPS_BEFORE_RECALC)
for i = 2, last do
local waypoint = waypoints[i]
if waypoint.Action == Enum.PathWaypointAction.Jump then
humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
humanoid:MoveTo(waypoint.Position)
local reached = humanoid.MoveToFinished:Wait()
if not reached or humanoid.Health <= 0 then
break -- 8秒たっても着かなかったら、計算し直す
end
end
else
-- 道が見つからないときは、まっすぐ向かう
humanoid:MoveTo(targetRoot.Position)
end
task.wait(0.1)
end
while humanoid.Health > 0 do
local target = findNearestTarget()
if target then
chaseWithPath(target)
else
humanoid:MoveTo(root.Position)
task.wait(0.5)
end
end

| コードの部分 | していること |
|---|---|
| CreatePath | 敵の大きさ(半径2、高さ5)に合わせて、通れる道の探し方を決める |
| ComputeAsync | 敵の場所から相手の場所までの道を計算する |
| GetWaypoints | 道を、点の並びにして取り出す |
| STEPS_BEFORE_RECALC | 相手は動くので、4つの点を進んだら道を計算し直す |
| 道が見つからないとき | まっすぐ相手へ向かう |
3D画面の上の作品のタブ「Place7」を押して3D画面に戻り、F5キーでテストを始めます(タブの名前はパソコンによって違います)。敵(画像の1)は壁の端を回り込んで、スポーン地点のアバターまで追ってきました。体力バーも減っています(画像の2)。

| 試したコード | 壁があるとき |
|---|---|
| コード1(MoveToだけ) | 壁の手前で止まった |
| コード2(経路探索) | 壁を回り込んで追いついた |
敵が追ってこない・体力が減らないときの確かめ方
| 症状 | 見るところ | 直し方 |
|---|---|---|
| 敵がまったく動かない | HumanoidRootPartのAnchored | チェックを外す(出力に「Anchored を外してください」と出る) |
| 遠くにいると追ってこない | CHASE_RANGE | 数を大きくする |
| 壁の前で止まる | 使っているコード | コード2(経路探索)にする |
| 触れても体力が減らない | スクリプトの場所 | ServerScriptServiceのScriptか、RunContextがServerか |
| 生き返った直後もすぐ減る | SpawnLocationのDuration | SpawnLocationを選んで検索欄に「Duration」と入力し、0より大きい数にする |
| 一度にたくさん減る | COOLDOWN | 数を大きくする |
スクリプトが動いているか分からないときは、スクリプトが動かないときの確認手順で出力の見方を確かめてください。
AIに相談するときの伝え方
Roblox Studioで、Workspace/Enemy という敵が、60スタッド以内でいちばん近いプレイヤーを追いかけるようにしたいです。
壁は PathfindingService で回り込み、道が見つからないときはまっすぐ向かってください。
触れたプレイヤーの体力を TakeDamage で20減らし、同じ人には1秒に1回までにしてください。
ServerScriptService の Script にして、wait() ではなく task.wait() を使ってください。
よくある質問
敵を倒せるようにするには?
敵のHumanoidの体力が0になると、このコードのくり返しは終わって、敵は動かなくなります。敵の体力を減らす仕組み(剣で叩く、ボールを当てるなど)を作れば、倒せる敵になります。
落ちたら途中から始められるようにしたい
チェックポイントを置くと、倒れても途中から生き返れます。作り方はチェックポイントの作り方で解説しています。
次は、手に持って使えるアイテムを作ります。次のステップ(中級 11/28)
次に知りたいことを選ぶ
- 次のステップ:手に持って使えるアイテムを作ります → ロブロックス(Roblox)スタジオで手に持てるアイテム(ツール)を作る方法
- 話しかけると喋るNPCも作りたい → ロブロックス(Roblox)スタジオでNPCを作る方法
- 倒れても途中から始めたい → ロブロックス(Roblox)のチェックポイントの作り方
- スクリプトが動かないときに確かめたい → ロブロックス(Roblox)でスクリプトが動かないときの確認手順