ロブロックス(Roblox)のアスレチックゲームの作り方【後編】|追う敵・体力ゲージ・回復・ダッシュ・保存・効果音を足して完成

制作手順難易度: 中級所要時間: 約90分(目安)情報確認日: 2026年10月18日

「前編のコースはできたけれど、敵や体力がないとゲームらしくない」「集めたコインが、次に遊んだときに0に戻ってしまう」

ロブロックス(Roblox)スタジオで、前編で作った夜のアスレチックゲーム「ナイトラン」を完成させる方法を、実際の画像付きで誰でも簡単に分かるように解説します。後編では、追いかけてくる敵、体力ゲージ、回復アイテム、Shiftキーのダッシュ、次に遊んだときも残るコイン、コインとゴールの効果音を足します。

中級ステップ28/28 前の記事:ロブロックス(Roblox)のアスレチックゲームの作り方【前編】 全体の順番を見る

使うもの:Roblox Studioが起動するパソコン、マウス、キーボード、前編で作ってRobloxに保存した作品。1つずつの仕組みは、それぞれの記事でくわしく解説しています。この記事では、前編のゲームへのつなぎ方を中心に進めます。

今回の完成状態:下の画像のように、紫の床に乗ると敵が追いかけてきて(画像の1)、ぶつかると画面の下の体力ゲージが減れば(画像の2)、ゲームの山場ができています。最後にゴールして、止めたあとにもう一度遊ぶと、前回のコインの数から始まれば完成です。

後編で足すもの

前編のコースのどこに、どの仕組みを足すかを先に決めておきます。

足すもの置く場所使う仕組みくわしい記事
敵の床と敵Checkpoint2とゴールの間決めた床の上の人だけを追う敵追いかけてくる敵の作り方
体力ゲージ画面の下ScreenGuiと、体力に合わせて長さを変えるLocalScript体力ゲージと回復アイテム
回復アイテム敵の床の角に2つフォルダーの中の箱に、まとめて回復の動きを付ける体力ゲージと回復アイテム
ダッシュプレイヤー側Shiftキーを押している間速く走るカメラとダッシュ
コインの保存前編のCoinServiceDataStoreで、抜けたときに保存して、入ったときに読み込むDataStoreの使い方
効果音SoundServiceコインを拾ったときと、ゴールしたときに鳴らす音楽と効果音の入れ方

前編のコースは足場が細く、まわりが溶岩です。そこで敵は、溶岩に落ちないように、広い床を1枚用意して、その上でだけ追いかけるようにします。

1. 前編の作品を開く

Roblox Studioを起動し、「最近使用したバーチャル空間」の「Asobishift Night Run」(赤い枠)を押します。一覧に出ていないときは、上の「ファイル」メニューの「Robloxから開く」から選びます。

前編の最後と同じ、夜の溶岩の上に足場が並んだ作品が開きます。

チーム制作がオンの作品は、変更が自動でクラウドに保存されます。この記事の手順の間、「Robloxに保存」を押し直す必要はありません。

2. 溶岩を広げて、ゴールを奥へ動かす

敵の床を置く場所を空けるため、溶岩を奥へ広げて、ゴールを奥へ動かします。

エクスプローラの「Workspace」の「Course」の中の「Lava」を押して選びます。プロパティの検索欄に「Size」と入力し、Sizeに「40, 1, 113」と入力します(赤い枠)。前編の88から、奥へ25スタッド長くします。

Lavaを選んだまま、プロパティの検索欄に「Position」と入力し、CFrameの中のPositionに「0, 0.6, -63.5」と入力します(赤い枠)。溶岩の手前の端は前編と同じ場所のまま、奥にだけ広がります。

エクスプローラの「Course」の中の「Goal」を押して選びます。プロパティの検索欄に「Position」と入力し、CFrameの中のPositionに「0, 10, -106」と入力します(画像の1)。ゴールが奥へ動きます(画像の2)。

3. 敵の床を置く

エクスプローラの「Course」にマウスを乗せて、右に出る「+」を押します(赤い枠)。

一覧の検索欄に「Part」と入れ、一覧の「Part」を押します(赤い枠)。

Courseの中にPartが入ります。エクスプローラで「Part」を押して選び、F2キーを押して名前を「EnemyFloor」に変えます(赤い枠)。敵のスクリプトは、この名前で床を探します。

EnemyFloorを選んだまま、プロパティの検索欄に「Size」と入力し、Sizeに「20, 1, 24」と入力します(赤い枠)。前編の足場(6×6)より大きくして、敵から逃げ回れる広さにします。

プロパティの検索欄に「Position」と入力し、CFrameの中のPositionに「0, 9, -89」と入力します(画像の1)。Checkpoint2とゴールの間に、大きな床が入ります(画像の2)。

プロパティの検索欄に「Color」と入力し、Colorに「110, 70, 160」と入力します(赤い枠)。紫にして、ほかの足場と区別します。

プロパティの検索欄に「Anchored」と入力し、Anchoredにチェックを入れます(赤い枠)。チェックがないと、テストを始めたときに床が溶岩へ落ちます。

4. 敵を置く

上の「ホーム」タブの「キャラクター」(画像の1)を押し、出てきた一覧の「ブロック型アバター(2012年)」(画像の2)を押します。

エクスプローラに「Rig」が入り(画像の1)、3D画面にキャラクターが置かれます(画像の2)。

エクスプローラで「Rig」を押して選び、F2キーを押して名前を「Enemy」に変えます(赤い枠)。

Enemyを選んだまま、プロパティの検索欄に「Origin」と入力します。Originの中のPositionに「0, 9.5, -95」、Orientationに「0, 180, 0」と入力します(赤い枠)。敵が紫の床の奥に立ち、スタートのほうを向きます。

Orientationは「-0, 180, -0」と表示されることがあります。0と同じ意味なので、そのままで大丈夫です。

エクスプローラの「Enemy」の左の「▶」を押して開き、中の「Humanoid」を押して選びます。プロパティの検索欄に「DisplayName」と入力し、DisplayNameに「敵」と入力します(画像の1)。頭の上の名前が「敵」になります(画像の2)。

5. 床の上だけ追いかけるスクリプトを入れる

エクスプローラの「Workspace」の左の「▼」を押して閉じます。「ServerScriptService」にマウスを乗せて、右に出る「+」を押します(赤い枠)。

一覧の検索欄に「Script」と入れ、一覧の「Script」を押します(赤い枠)。

エクスプローラで「Script」を押して選び、F2キーを押して名前を「EnemyService」に変えます(赤い枠)。

開いているEnemyServiceの画面で、中身を全部消して、次のコードを貼り付けます。

local Players = game:GetService("Players")

local enemy = workspace:WaitForChild("Enemy")
local humanoid = enemy:WaitForChild("Humanoid")
local root = enemy:WaitForChild("HumanoidRootPart")
local floor = workspace:WaitForChild("Course"):WaitForChild("EnemyFloor")

local DAMAGE = 20 -- 1回に減らす体力
local COOLDOWN = 1 -- 同じ人にもう一度当たるまでの秒数
local home = root.Position -- 最初に立っていた場所

humanoid.WalkSpeed = 12 -- プレイヤー(16)より少し遅くして、逃げられるようにする

local ok = pcall(function()
	root:SetNetworkOwner(nil)
end)
if not ok then
	warn("Enemy のパーツの Anchored を外してください")
end

-- 位置が EnemyFloor の上にあるか(敵が溶岩に落ちないよう、床の上の人だけ追う)
local function isOnFloor(position)
	local localPos = floor.CFrame:PointToObjectSpace(position)
	return math.abs(localPos.X) < floor.Size.X / 2 and math.abs(localPos.Z) < floor.Size.Z / 2
end

local function findTarget()
	local nearest = nil
	local nearestDistance = math.huge
	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 and isOnFloor(targetRoot.Position) 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)

-- 床の上に人がいれば追いかけ、いなければ最初の場所へ戻る
while humanoid.Health > 0 do
	local target = findTarget()
	if target then
		humanoid:MoveTo(target.Position)
	else
		humanoid:MoveTo(home)
	end
	task.wait(0.2)
end

追いかけてくる敵の作り方のコードは、60スタッド以内のいちばん近い人へ、まっすぐ歩いていきます。そのままこのコースで使うと、敵は床の端から溶岩の上へ下りてしまいます。そこで、追いかける相手を「EnemyFloorの上にいる人」だけにしました。

前の記事から変えたところコードの場所していること
床の上か確かめるisOnFloor位置を床から見た向きに直し、床の横幅と奥行きの半分より内側なら「床の上」とする
追う相手を選ぶfindTarget床の上にいて、生きている人の中から、いちばん近い人を選ぶ
誰もいないときhumanoid:MoveTo(home)その場で止まるのではなく、最初に立っていた場所へ戻る

床から下りた人を追いかけないので、Checkpoint2へ逃げれば敵は戻っていきます。逃げ場があるから、プレイヤーは「どこで戦うか」を選べるようになります。

6. 体力ゲージの枠を作る

エクスプローラの「ServerScriptService」の左の「▼」を押して閉じます。「StarterGui」にマウスを乗せて、右に出る「+」を押します(赤い枠)。

一覧の検索欄に「ScreenGui」と入れ、一覧の「ScreenGui」を押します(赤い枠)。

StarterGuiの中に、前編のGoalGuiと並んでScreenGuiが入ります。エクスプローラで「ScreenGui」を押して選び、F2キーを押して名前を「HealthGui」に変えます(赤い枠)。

エクスプローラの「HealthGui」にマウスを乗せて「+」を押し、一覧の検索欄に「Frame」と入れて、一覧の「Frame」を押します(赤い枠)。

エクスプローラで「Frame」を押して選び、F2キーを押して名前を「MeterBar」に変えます。MeterBarを選んだまま、プロパティの検索欄に「AnchorPoint」と入力し、AnchorPointに「0.5, 1」と入力します(赤い枠)。位置を決めるときの基準が、四角の「下の真ん中」になります。

プロパティの検索欄に「Position」と入力し、Positionに「0.5, 0, 1, -30」と入力します(赤い枠)。四角が画面の下の真ん中に移ります。

プロパティの検索欄に「Size」と入力し、Sizeに「0, 300, 0, 28」と入力します(画像の1)。横300ピクセル、縦28ピクセルの細長い帯になります(画像の2)。

プロパティの検索欄に「BackgroundColor3」と入力し、BackgroundColor3に「40, 40, 40」と入力します(赤い枠)。帯が黒っぽくなります。体力が減ったときに見える、ゲージの空いた部分の色です。

数字の意味(AnchorPointやPositionの4つの数)は、体力ゲージと回復アイテムの6章でくわしく解説しています。

7. 緑の帯と体力の数字を入れる

エクスプローラの「MeterBar」にマウスを乗せて、右に出る「+」を押します(赤い枠)。

一覧の検索欄に「Frame」と入れ、一覧の「Frame」を押します(赤い枠)。

MeterBarの中にFrameが入ります。エクスプローラで「Frame」を押して選び、F2キーを押して名前を「Fill」に変えます(赤い枠)。

Fillを選んだまま、プロパティの検索欄に「Size」と入力し、Sizeに「1, 0, 1, 0」と入力します(赤い枠)。FillがMeterBarの中いっぱいに広がります。

プロパティの検索欄に「BackgroundColor3」と入力し、BackgroundColor3に「60, 200, 90」と入力します(画像の1)。帯が緑になります(画像の2)。

もう一度、エクスプローラの「MeterBar」の「+」を押し、一覧の検索欄に「TextLabel」と入れて、一覧の「TextLabel」を押します(赤い枠)。

エクスプローラで「TextLabel」を押して選び、F2キーを押して名前を「HealthText」に変えます(赤い枠)。

HealthTextを選んだまま、プロパティを1つずつ変えます。どれも、プロパティの検索欄に名前を入力してから、値を入れます。

プロパティ入れる値画像
Size1, 0, 1, 0下の1枚目
BackgroundTransparency1下の2枚目(文字の後ろの白い四角が消える)
Text100 / 100下の3枚目
TextColor3255, 255, 255下の4枚目(文字が白になる)
TextScaledチェックを入れる下の5枚目(文字が帯の高さに合わせて大きくなる)
ZIndex2下の6枚目(文字が緑の帯に隠れなくなる)

Textの「100 / 100」は、編集画面で見た目を確かめるための仮の文字です。テストを始めると、次のスクリプトが本当の体力に書きかえます。

8. ゲージを動かすスクリプトを入れる

エクスプローラの「HealthGui」にマウスを乗せて「+」を押し、一覧の検索欄に「LocalScript」と入れて、一覧の「LocalScript」を押します(赤い枠)。

エクスプローラで「LocalScript」を押して選び、F2キーを押して名前を「HealthBar」に変えます(赤い枠)。

開いているHealthBarの画面で、中身を全部消して、次のコードを貼り付けます。体力ゲージと回復アイテムと同じコードです。

local Players = game:GetService("Players")
local StarterGui = game:GetService("StarterGui")

local gui = script.Parent
local meter = gui:WaitForChild("MeterBar")
local fill = meter:WaitForChild("Fill")
local label = meter:WaitForChild("HealthText")

-- Robloxの最初からある体力ゲージ(右上)をかくす
StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.Health, false)

local RED = Color3.fromRGB(230, 60, 60)
local GREEN = Color3.fromRGB(60, 200, 90)

local function update(humanoid)
	local ratio = math.clamp(humanoid.Health / humanoid.MaxHealth, 0, 1)
	fill.Size = UDim2.fromScale(ratio, 1) -- 体力の割合だけ、緑の帯をのばす
	fill.BackgroundColor3 = RED:Lerp(GREEN, ratio) -- 減るほど赤に近づける
	label.Text = math.ceil(humanoid.Health) .. " / " .. humanoid.MaxHealth
end

local function onCharacter(character)
	local humanoid = character:WaitForChild("Humanoid")
	update(humanoid)
	humanoid.HealthChanged:Connect(function()
		update(humanoid)
	end)
end

local player = Players.LocalPlayer
if player.Character then
	onCharacter(player.Character)
end
player.CharacterAdded:Connect(onCharacter)

体力ゲージのスクリプトは、どこで体力が減ったかを気にしません。前編の溶岩で倒れたときも、敵にぶつかったときも、同じゲージが動きます。

9. 回復アイテムを2つ置く

3D画面の上の作品のタブを押して3D画面に戻ります。エクスプローラの「Workspace」にマウスを乗せて、右に出る「+」を押します(赤い枠)。

一覧の検索欄に「Folder」と入れ、一覧の「Folder」を押します(赤い枠)。

エクスプローラで「Folder」を押して選び、F2キーを押して名前を「HealthPickups」に変えます(赤い枠)。

エクスプローラの「HealthPickups」にマウスを乗せて「+」を押し、一覧の検索欄に「Part」と入れて、一覧の「Part」を押します(赤い枠)。

HealthPickupsの中にPartが入ります。エクスプローラで「Part」を押して選び、F2キーを押して名前を「HealthPack」に変えます(赤い枠)。

HealthPackを選んだまま、プロパティの検索欄に「Size」と入力し、Sizeに「2, 2, 2」と入力します(赤い枠)。

プロパティの検索欄に「Position」と入力し、CFrameの中のPositionに「7, 11, -81」と入力します(画像の1)。紫の床の手前の右の角に、箱が浮かびます(画像の2)。

続けてHealthPackを選んだまま、次の4つを変えます。どれもプロパティの検索欄に名前を入力してから変えます。

プロパティ入れる値理由
Color60, 220, 100回復アイテムだと分かる緑にする(下の1枚目)
MaterialNeon光らせて、夜でも見つけやすくする(下の2枚目)
Anchoredチェックを入れる浮かせたまま止めておく(下の3枚目)
CanCollideチェックを外すぶつからずに通り抜けながら取れるようにする(下の4枚目)

エクスプローラで「HealthPack」を押して選び、Ctrl + Dキーを押します。同じ設定の「HealthPack」がもう1つできます(赤い枠)。

2つ目のHealthPackが選ばれたまま、プロパティの検索欄に「Position」と入力し、CFrameの中のPositionに「-7, 11, -99」と入力します(画像の1)。紫の床の奥の左の角に、2つ目の箱が移ります(画像の2)。

手前と奥の角に1つずつ置くと、敵をかわしながら箱まで走る遊び方ができます。

10. 回復のスクリプトを入れる

エクスプローラの「Workspace」の左の「▼」を押して閉じます。「ServerScriptService」の「+」を押し、一覧の検索欄に「Script」と入れて、一覧の「Script」を押します(赤い枠)。

エクスプローラで「Script」を押して選び、F2キーを押して名前を「PickupManager」に変えます(赤い枠)。

開いているPickupManagerの画面で、中身を全部消して、次のコードを貼り付けます。体力ゲージと回復アイテムと同じコードです。

local HEAL = 30 -- 1つで回復する体力
local RESPAWN_TIME = 5 -- 取ってから元に戻るまでの秒数

local pickupsFolder = workspace:WaitForChild("HealthPickups")

local function onTouchPickup(hit, pickup)
	if pickup:GetAttribute("Used") then
		return
	end
	local humanoid = hit.Parent:FindFirstChildWhichIsA("Humanoid")
	if not humanoid or humanoid.Health <= 0 or humanoid.Health >= humanoid.MaxHealth then
		return
	end
	-- 上限(MaxHealth)をこえないように回復する
	humanoid.Health = math.min(humanoid.Health + HEAL, humanoid.MaxHealth)

	pickup:SetAttribute("Used", true)
	pickup.Transparency = 0.8 -- うすくして、取ったことを分かるようにする
	task.wait(RESPAWN_TIME)
	pickup.Transparency = 0
	pickup:SetAttribute("Used", false)
end

-- フォルダーの中の回復アイテムに、まとめて同じ動きを付ける
for _, pickup in pickupsFolder:GetChildren() do
	pickup.Touched:Connect(function(hit)
		onTouchPickup(hit, pickup)
	end)
end

1つ取ると体力が30戻り、うすくなった箱は5秒後に元に戻ります。体力が満タンのときは取らない作りです。

11. Studioのテストでコインを保存できるようにする

コインを保存する仕組み(DataStore)は、最初の設定のままだとStudioのテストで使えません。上の「ファイル」メニューを開き、「バーチャル空間設定」(赤い枠)を押します。

バーチャル空間設定の画面が開きます。左の「セキュリティ」(赤い枠)を押します。

「StudioのAPIサービスへのアクセスを有効化」のスイッチ(赤い枠)は、最初はオフです。設定の画面を開いたとき、下の出力の欄にオレンジの「Failed to parse secrets」という文字が出ることがあります。Studioの設定の画面の中で出る表示で、作品のスクリプトとは関係ありません。

スイッチを押してオンにし(画像の1)、右下の「保存」(画像の2)を押します。

Robloxの公式の説明によると、Studioのテストは、公開しているゲームと同じ保存データを読み書きします。たくさんの人が遊んでいるゲームでは、このスイッチをオンにしないでください。テストで本番のデータを書きかえてしまうおそれがあるからです。この記事の作品はまだ公開していないので、オンにして大丈夫です。

12. コインのスクリプトに保存を足す

エクスプローラの「ServerScriptService」の中の、前編で作った「CoinService」(赤い枠)をダブルクリックして開きます。

開いたCoinServiceの画面で、中身を全部消して、次のコードを貼り付けます。前編のコイン集めの仕組みに、DataStoreの使い方の保存と読み込みを足したものです。

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")

local store = DataStoreService:GetDataStore("NightRunCoins")
local coins = workspace:WaitForChild("Coins")
local RESPAWN_SECONDS = 5 -- 拾われたコインが戻るまでの秒数

local loaded = {} -- 読み込みに成功した人だけ true
local saving = 0 -- 今、保存している途中の数

-- 参加した人ごとに、保存してあるコインの数を読み込んで、右上の Coins を用意する
local function addLeaderstats(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	local count = Instance.new("IntValue")
	count.Name = "Coins"
	count.Value = 0
	count.Parent = leaderstats

	local ok, saved = pcall(function()
		return store:GetAsync("player_" .. player.UserId)
	end)
	if ok then
		count.Value = saved or 0
		loaded[player] = true
	else
		-- 読み込みに失敗したときは保存しない(前のデータを 0 で上書きしないため)
		warn("Coinsの読み込みに失敗しました", saved)
	end
	leaderstats.Parent = player -- 数を入れてから右上に出す
end

local function save(player)
	if not loaded[player] then return end
	loaded[player] = nil -- 同じ人を2回保存しないよう、先に印を外す
	saving += 1
	local value = player.leaderstats.Coins.Value
	local ok, err = pcall(function()
		store:SetAsync("player_" .. player.UserId, value)
	end)
	saving -= 1
	if ok then
		print(player.Name, "のCoinsを保存しました", value)
	else
		warn("Coinsの保存に失敗しました", err)
	end
end

Players.PlayerAdded:Connect(addLeaderstats)
for _, player in Players:GetPlayers() do
	addLeaderstats(player)
end

Players.PlayerRemoving:Connect(save)

-- サーバーが閉じるときは、残っている人の分も保存し、保存が終わるまで待つ
game:BindToClose(function()
	for _, player in Players:GetPlayers() do
		task.spawn(save, player)
	end
	while saving > 0 do
		task.wait()
	end
end)

local function setupCoin(coin)
	local ready = true
	coin.Touched:Connect(function(hit)
		if not ready then
			return
		end
		local player = Players:GetPlayerFromCharacter(hit.Parent)
		if not player or not player:FindFirstChild("leaderstats") then
			return
		end
		ready = false
		player.leaderstats.Coins.Value += 1
		coin.Transparency = 1
		task.wait(RESPAWN_SECONDS)
		coin.Transparency = 0
		ready = true
	end)
end

for _, coin in coins:GetChildren() do
	setupCoin(coin)
end
前編から足したところコードの場所していること
保存場所を決めるGetDataStore("NightRunCoins")このゲームのコインを入れる場所に名前を付ける
入ったときに読み込むstore:GetAsync前回のコインの数を読み、右上のCoinsに入れる。初めての人は0
数を入れてから出すleaderstats.Parent = player読み込みが終わってから右上に出す。0から急に数が変わって見えるのを防ぐ
抜けたときに保存するPlayers.PlayerRemoving今のコインの数を書きこむ
サーバーが閉じるときにも保存するgame:BindToClose残っている人の分を保存し、保存が終わるまで待つ
2回保存しないloaded[player] = nil1人を保存したら印を外し、同じ人をもう一度保存しないようにする

BindToClose は、Robloxの公式の説明で、DataStoreを使うなら一緒に使うよう書かれている仕組みです。サーバーは閉じる前に最大30秒待ってくれるので、その間に保存を終わらせます。

最後の「2回保存しない」は、アソビシフト編集部が試して足したところです。最初は抜けたときとサーバーが閉じるときの両方で同じ人を保存していて、テストを止めるたびに出力にオレンジの「DataStore request was added to queue」が出ました。同じ人を続けて書きこむと、順番待ちになるという意味です。1人1回だけ保存する形に直すと、この表示は出なくなりました。

13. Shiftキーのダッシュを入れる

エクスプローラの「StarterPlayer」の左の「▶」を押して開き、中の「StarterPlayerScripts」にマウスを乗せて、右に出る「+」を押します(赤い枠)。

一覧の検索欄に「LocalScript」と入れ、一覧の「LocalScript」を押します(赤い枠)。

エクスプローラで「LocalScript」を押して選び、F2キーを押して名前を「DashControl」に変えます(赤い枠)。

開いているDashControlの画面で、中身を全部消して、次のコードを貼り付けます。カメラとダッシュと同じコードです。

local ContextActionService = game:GetService("ContextActionService")
local Players = game:GetService("Players")

local player = Players.LocalPlayer
local camera = workspace.CurrentCamera

local DASH_ACTION = "Dash"
local NORMAL_SPEED = 16 -- ふだんの歩く速さ(最初の値)
local DASH_SPEED = 28 -- Shiftキーを押している間の速さ
local NORMAL_FOV = 70 -- ふだんの見える広さ(最初の値)
local DASH_FOV = 80 -- 走っている間は、少し広く見せる

local function onDash(actionName, inputState, inputObject)
	local character = player.Character
	local humanoid = character and character:FindFirstChildWhichIsA("Humanoid")
	if not humanoid then
		return
	end
	if inputState == Enum.UserInputState.Begin then
		humanoid.WalkSpeed = DASH_SPEED
		camera.FieldOfView = DASH_FOV
	elseif inputState == Enum.UserInputState.End then
		humanoid.WalkSpeed = NORMAL_SPEED
		camera.FieldOfView = NORMAL_FOV
	end
end

-- 左Shiftキーに「Dash」をつなぐ。true にすると、スマホでは画面にボタンが出る
ContextActionService:BindAction(DASH_ACTION, onDash, true, Enum.KeyCode.LeftShift)
ContextActionService:SetTitle(DASH_ACTION, "ダッシュ")

敵の歩く速さは12、プレイヤーは16、ダッシュ中は28です。ふつうに歩いても逃げられるけれど、ダッシュすれば大きく引き離せる速さにしています。

14. 効果音を探す

上の「ホーム」タブの「ツールボックス」(画像の1)を押すと、左にツールボックスが開きます。「カテゴリ」の中の「オーディオ」(画像の2)を押します。

上の検索欄に「coin」と入れてEnterキーを押します(画像の1)。一覧のいちばん上の「Coin Collect」(画像の2)が、コインを拾った音です。名前にマウスを乗せると左に出る再生ボタンで、試しに聞けます。

「Coin Collect」を右クリックして、「アセットIDをコピー」(赤い枠)を押します。音の番号(ID)がコピーされます。メモ帳などに貼って、取っておきます。

同じように、検索欄に「victory」と入れてEnterキーを押します(画像の1)。一覧のいちばん上の「victory.wav」(画像の2)が、ゴールしたときの音です。作った人の欄が「Roblox」になっている、Robloxの公式の音です。

「victory.wav」も右クリックして「アセットIDをコピー」(赤い枠)を押し、IDを取っておきます。

使う音検索した言葉音の名前コピーしたID
コインを拾った音coinCoin Collect135483737426662
ゴールの音victoryvictory.wav12222253

検索の結果の順番は変わることがあります。同じ音が見つからないときは、上の表のIDをそのまま使えます。ツールボックスは、右上の「×」で閉じておきます。

15. 効果音を入れる

エクスプローラの「SoundService」にマウスを乗せて、右に出る「+」を押します(赤い枠)。

一覧の検索欄に「Sound」と入れ、一覧の「Sound」を押します(赤い枠)。

エクスプローラで「Sound」を押して選び、F2キーを押して名前を「CoinSound」に変えます(赤い枠)。

CoinSoundを選んだまま、プロパティの検索欄に「SoundId」と入力し、SoundIdに「rbxassetid://135483737426662」と入力します(赤い枠)。「rbxassetid://」のあとに、コピーしたIDをつなげます。

エクスプローラで「CoinSound」を押して選び、Ctrl + Dキーを押します。2つ目の「CoinSound」ができます(赤い枠)。

2つ目のCoinSoundを選び、F2キーを押して名前を「GoalSound」に変えます(赤い枠)。

GoalSoundを選んだまま、プロパティの検索欄に「SoundId」と入力し、SoundIdを「rbxassetid://12222253」に書きかえます(赤い枠)。

16. 効果音を鳴らすスクリプトを入れる

エクスプローラの「StarterPlayerScripts」の「+」を押し、一覧の検索欄に「LocalScript」と入れて、一覧の「LocalScript」を押します(赤い枠)。

エクスプローラで「LocalScript」を押して選び、F2キーを押して名前を「GameSounds」に変えます(赤い枠)。

開いているGameSoundsの画面で、中身を全部消して、次のコードを貼り付けます。

local Players = game:GetService("Players")
local SoundService = game:GetService("SoundService")

local player = Players.LocalPlayer
local coinSound = SoundService:WaitForChild("CoinSound")
local goalSound = SoundService:WaitForChild("GoalSound")

-- コインが増えたときだけ鳴らす(自分の画面だけで鳴る)
local coinsValue = player:WaitForChild("leaderstats"):WaitForChild("Coins")
local last = coinsValue.Value
coinsValue.Changed:Connect(function(value)
	if value > last then
		coinSound:Play()
	end
	last = value
end)

-- ゴールした印が付いたら鳴らす
player:GetAttributeChangedSignal("Cleared"):Connect(function()
	if player:GetAttribute("Cleared") then
		goalSound:Play()
	end
end)
行していること
coinsValue.Changed右上のCoinsの数が変わるたびに動く
if value > last then数が増えたときだけ鳴らす
GetAttributeChangedSignal("Cleared")前編のCourseServiceが付ける「ゴールした印」を見て、ゴールの音を鳴らす

LocalScriptで鳴らしているので、音は拾った本人の画面でだけ鳴ります。ほかの人がコインを拾っても、自分には聞こえません。音の聞こえ方の違いは、音楽と効果音の入れ方で解説しています。

12章で、読み込みが終わってから右上にCoinsを出すようにしたのは、この音のためでもあります。読み込みの前に0で出してしまうと、前回の数が入ったときに「数が増えた」と判定されて、コインの音が鳴ってしまいます。

17. テストで最初から最後まで遊ぶ

3D画面の上の作品のタブを押して3D画面に戻り、F5キーでテストを始めます。右上のCoinsは0から始まり(画像の1)、画面の下に体力ゲージ「100 / 100」が出ます(画像の2)。

右上の名前の欄はモザイクにしています。

コインに触ると、コインの音が鳴ります。前編と同じように足場を渡って、Checkpoint2まで進みます。Checkpoint2にいる間、敵は紫の床の奥で待っています(画像の1)。3枚とも拾っていれば、Coinsは3です(画像の2)。

紫の床に下りると、敵が追いかけてきます。ぶつかると体力が20減り、ゲージが短くなります(赤い枠)。

緑の箱に触ると体力が30戻り、箱がうすくなります(赤い枠)。画像では、敵に2回ぶつかって60になったあと、箱で90まで戻り、そこから自然に少し増えて92になっています。

Shiftキーを押している間は速く走り、見える範囲が少し広がります。下の2枚は、同じ場所でShiftキーを押す前(上)と、押している間(下)です。押している間は、紫の床やまわりの溶岩が小さく、広く見えます。

敵をかわしてゴールに乗ると、ゴールの音が鳴って「ゴール!おめでとう」が出ます(画像の1)。画像では、ゴールの前にもう1枚拾い直したので、Coinsは4です(画像の2)。

Shift + F5キーでテストを停止します。出力に「(アカウント名)のCoinsを保存しました 4」と出れば、保存できています(赤い枠)。

もう一度F5キーでテストを始めます。右上のCoinsが、0ではなく前回の4から始まります(赤い枠)。

試したこと結果
テストを始めるCoinsは0、体力ゲージは100 / 100
コインに触るコインの音が鳴り、Coinsが1増える
Checkpoint2にいる敵は紫の床の奥で待ったまま、追ってこない
紫の床に下りる敵が追いかけてきて、ぶつかるたびに体力が20減る(約1秒に1回まで)
緑の箱に触る体力が30戻る。満タンのときは取らない
Shiftキーを押す速く走り、見える範囲が広がる。離すと元に戻る
ゴールに乗るゴールの音が鳴り、「ゴール!おめでとう」が出る
停止して、もう一度始める停止したときの出力に「保存しました」。次のテストは前回のCoinsの数から始まる

確かめ終わったら、Shift + F5キーでテストを停止します。

ゲームを自分らしく変える

EnemyService・PickupManager・DashControlは、コードの中の数字です。エクスプローラでその名前のスクリプトをダブルクリックして開き、表の名前の行の数字を書き換えます。

最後の「CoinSound・GoalSoundのVolume」はプロパティです。エクスプローラでSoundServiceの中の「CoinSound」を押して選び、プロパティの検索欄に「Volume」と入力して、右の欄に「0.3」と入力します。GoalSoundも同じように変えます。

変えたいこと変える場所例
敵を速くするEnemyServiceの humanoid.WalkSpeed12 → 14。16にすると、ダッシュしないと逃げられない
敵の攻撃を弱くするEnemyServiceの DAMAGE20 → 10
回復を多くするPickupManagerの HEAL30 → 50
ダッシュを速くするDashControlの DASH_SPEED28 → 32
効果音を小さくするCoinSound・GoalSoundのVolume0.5 → 0.3

アシスタントに頼むときは、部品の名前とスクリプトの名前を書くと、思ったとおりに直してもらいやすくなります。

Roblox Studioで、ServerScriptServiceの「EnemyService」が、Workspaceの「Enemy」を動かしています。
Enemyは、Courseフォルダーの「EnemyFloor」の上にいるプレイヤーだけを追いかけます。
敵が床の上を、プレイヤーがいないときは左右に行ったり来たりするようにしてください。
EnemyFloorの外には出ないようにしてください。

公開する前に確かめること

この記事では、作品を公開していません。遊んでもらうには「ファイル」メニューの「Robloxに公開」と、Creator Hubでの公開範囲の設定が必要です。手順と、公開できないときの条件は、テストと公開の方法で解説しています。

公開の前に、次の4つを確かめておくと安心です。

確かめること理由
最初から最後まで、一度も止まらずに遊べる足場の間が遠すぎる、敵が強すぎるなど、遊ぶ人が詰まるところを先に直す
出力に赤い文字が出ていないスクリプトの止まりは、遊ぶ人の画面では分からない
スマホの画面でも遊べるデバイスシミュレータ(3D画面の右上の歯車のアイコンの「デバイスシミュレーション」)で確かめる。ダッシュのボタンは画面に出る
「StudioのAPIサービスへのアクセスを有効化」をどうするか決める公開して人が遊ぶようになったら、本番のデータを守るためにオフにする

動かないときの確かめ方

症状見るところ直し方
敵が動かないEnemyのAnchoredEnemyの中のHumanoidRootPartのAnchoredのチェックを外す。出力に「Enemy のパーツの Anchored を外してください」と出る
敵が追ってこない立っている場所紫の床(EnemyFloor)の上にいるか。名前がEnemyFloorか、Courseの中にあるか
体力が減らないテスト開始直後生き返った直後は、まわりの青い守りの光が消えるまでダメージを受けない
ゲージが出ないHealthGuiの場所StarterGuiの中にあるか。MeterBar・Fill・HealthTextの名前が合っているか
出力にオレンジの「Coinsの読み込みに失敗しました」が出るバーチャル空間設定11章のスイッチがオンになっているか。作品がRobloxに保存されているか
Coinsが毎回0から始まる停止したときの出力「保存しました」が出ているか。出ていなければ、作品がRobloxに保存されているか確かめる
効果音が鳴らないSoundId「rbxassetid://」のあとが数字だけか。CoinSound・GoalSoundがSoundServiceの中にあり、名前が合っているか
出力に「Infinite yield possible」と出る名前出力に書かれた名前の物があるか。この表示の読み方はInfinite yield possibleの直し方で解説しています

よくある質問

停止したときに、オレンジの「DataStore request was added to queue」が出た

同じ人のデータを、短い間に続けて書きこんだときの表示です。この記事のコードは1人1回だけ保存するので、アソビシフト編集部が試したときは出なくなりました。前のコードのまま「抜けたとき」と「サーバーが閉じるとき」の両方で保存していると出ることがあります。12章のコードを貼り直してください。

コインの数を0に戻したい

CoinServiceの GetDataStore("NightRunCoins") の名前を、「NightRunCoins2」のように変えます。新しい保存場所になるので、Coinsは0から始まります。

中級はここまでです。次は上級(1/42)

中級の記事で、次のことができるようになりました。

  • ScriptとLocalScriptの違いを、2人のテストで見て確かめます
  • ゲームを抜けても進み具合が残るようにします
  • ボタンからサーバーに合図を送る仕組みを作ります
  • ゲームパスを作って売ります
  • 何回でも買えるアイテム(開発者製品)を売ります
  • 回る床と、行ったり来たりする床を作ります
  • 乗ると消える床と、時間内だけ渡れる橋を作ります
  • TweenServiceで、パーツや画面の枠をなめらかに動かします
  • 話しかけると喋るNPCを作ります
  • 追いかけてくる敵を作ります
  • 手に持って使えるアイテムを作ります
  • 剣で攻撃できるようにします
  • クリックした場所へレーザーを撃って当てるブラスターを作ります
  • 赤と青のチームに分けます
  • ゴールした人にバッジを渡します
  • お金をためて機械を買い足すタイクーンを作ります
  • ゲームが重くなったときに、原因を測って直します
  • 敵が道を歩き、タワーが撃つタワーディフェンスを作ります
  • お金でタワーを買って、好きな場所に置けるようにします
  • 朝・夕方・夜を作り、夜の街灯を光らせます
  • 体力ゲージと、拾うと回復するアイテムを作ります
  • 自分の画像を取り込み、看板とコインのアイコン付きスコアを作ります
  • 一人称視点・横から見るカメラ・Shiftキーのダッシュを作ります
  • AIが書いたスクリプトの変数・if・for・関数・イベントを読めるようにします
  • if・elseif・while・配列・辞書など、Lua言語の書き方を覚えます
  • 壁と屋根を組み、ドアと窓の穴を開けて家を建てます
  • これまでの仕組みを組み合わせて、夜のアスレチックゲームを作ります(前編)
  • 敵・体力・回復・ダッシュ・コインの保存・効果音を足して、アスレチックゲームを完成させます(後編)

次は、乗って上がるかごと呼びボタンで、1階と2階を行き来するエレベーターを作ります。

→ ロブロックス(Roblox)のエレベーターの作り方

次に知りたいことを選ぶ

次に読む記事