「タワーディフェンスで、タワーを自分で好きな場所に置けるようにしたい」「敵を倒したらお金が増えるようにしたい」
ロブロックス(Roblox)スタジオで作るタワーディフェンスの後編です。実際の画像付きで誰でも簡単に分かるように解説します。前編では、敵が道を歩き、タワーが近くの敵を撃つところまで作りました。後編では、敵を倒すとお金がもらえて、画面のボタンを押してから地面をクリックすると、そこにタワーを置けるようにします。
中級ステップ19/28 前の記事:ロブロックス(Roblox)スタジオでタワーディフェンスを作る方法【前編】 全体の順番を見る
使うもの:前編で作ったタワーディフェンス。お金を右上に出す「leaderstats」はコイン集めゲームの作り方、ボタンからサーバーに知らせる「RemoteEvent」はRemoteEventの使い方と同じしくみです。
今回の完成状態:下の画像のように、ボタンに「置きました!」と出て(画像の1)、クリックした場所にタワーが立ち(画像の2)、右上のMoneyが減れば成功です(画像の3)。

タワーを置くしくみ
タワーを置くときは、遊ぶ人の画面とサーバーが、次の順番で知らせ合います。
| 順番 | どこで | すること |
|---|---|---|
| 1 | 遊ぶ人の画面(LocalScript) | ボタンが押されたら「置く場所を選ぶ」状態にする |
| 2 | 遊ぶ人の画面(LocalScript) | 地面がクリックされたら、その位置をRemoteEventでサーバーに送る |
| 3 | サーバー(Script) | お金が足りるか、置ける場所かを自分で確かめる |
| 4 | サーバー(Script) | 大丈夫なら、お金を減らしてタワーを置き、結果を画面に返す |
お金を減らすのも、タワーを置くのも、サーバーだけです。画面の側にお金の計算を書くと、ずるをする人に「お金を減らさずに置いた」と送られてしまいます。
1. タワーの手本をServerStorageにしまう
前編の終わりの状態から始めます。エクスプローラの「Towers」の中の「Tower」を選び、Ctrl+Dで複製します。Towersの中にTowerが2つ並びます(赤い枠)。

エクスプローラで下に増えたほうのTowerをドラッグして、「ServerStorage」の上で離します。ServerStorageの中にTowerが入ります(赤い枠)。これが、買ったときに複製して置く手本です。

キーボードで移すこともできます。Towerを選んでCtrl+Xで切り取り、「ServerStorage」を選んでCtrl+Shift+V(選んだものの中に貼り付けるショートカット)を押します。ただ、選んでいる場所によってはこのショートカットが効かず、ServerStorageの中に入らないことがありました。そのときは、エクスプローラでTowerがどこにあるかを確かめて、上のドラッグでServerStorageに移してください。切り取ったTowerが見当たらないときは、Towersの中のTowerをもう一度Ctrl+Dで複製してから移します。
2. 知らせの通り道(RemoteEvent)を入れる
エクスプローラの「ReplicatedStorage」にマウスを乗せて「+」を押し、検索欄に「RemoteEvent」と入れて、一覧の「RemoteEvent」を押します(赤い枠)。

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

3. 画面にボタンを置く
エクスプローラの「StarterGui」にマウスを乗せて「+」を押し、検索欄に「ScreenGui」と入れて、一覧の「ScreenGui」を押します(赤い枠)。

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

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

プロパティの検索欄に「Size」と入れ、Sizeを「{0, 240},{0, 50}」にします(赤い枠)。横240、縦50のボタンになります。

プロパティの検索欄に「Position」と入れ、Positionを「{0, 20},{1, -80}」にします(赤い枠)。画面の左下、下の端から80上の位置にボタンが移ります。

プロパティの検索欄に「TextScaled」と入れ、チェックを入れます(赤い枠)。

プロパティの検索欄に「Text」と入れ、Textを「タワーを置く(50)」にします(画像の1)。画面の左下にボタンが出ます(画像の2)。ボタンの作り方はボタンの作り方でも解説しています。

4. ボタンと地面のクリックを伝えるLocalScriptを入れる
エクスプローラの「TextButton」にマウスを乗せて「+」を押し、検索欄に「LocalScript」と入れて、一覧の「LocalScript」を押します(赤い枠)。

エクスプローラでF2キーを押し、名前を「PlaceClient」に変えて開き、もとから入っている1行を消して、次のコードを貼り付けます。エクスプローラのTextButtonの中にPlaceClientが見えます(赤い枠)。
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local placeTower = ReplicatedStorage:WaitForChild("PlaceTower")
local button = script.Parent
local mouse = Players.LocalPlayer:GetMouse()
local BUTTON_TEXT = "タワーを置く(50)"
local placing = false -- 置く場所を選んでいる途中か
-- ボタンを押すと「置く場所を選ぶ」状態になる(もう一度押すとやめる)
button.Activated:Connect(function()
placing = not placing
if placing then
button.Text = "置く場所をクリック"
else
button.Text = BUTTON_TEXT
end
end)
-- 選んでいる途中に地面をクリックしたら、その位置をサーバーに伝える
mouse.Button1Down:Connect(function()
if not placing then
return
end
placing = false
button.Text = BUTTON_TEXT
placeTower:FireServer(mouse.Hit.Position)
end)
-- サーバーからの返事を、ボタンに1.5秒だけ出す
placeTower.OnClientEvent:Connect(function(message)
button.Text = message
task.wait(1.5)
if not placing then
button.Text = BUTTON_TEXT
end
end)

| コードの部分 | していること |
|---|---|
| button.Activated | ボタンが押されたら「置く場所を選ぶ」状態にして、ボタンの文字を「置く場所をクリック」に変える |
| mouse.Button1Down | 選んでいる途中に画面がクリックされたら、マウスが指していた位置をサーバーに送る |
| mouse.Hit.Position | マウスの先にある地面などの位置 |
| OnClientEvent | サーバーから返ってきた「置きました!」などを、ボタンに1.5秒だけ出す |
このLocalScriptは位置を送るだけで、お金には触りません。
5. お金とタワーを置くScriptを入れる
エクスプローラの「ServerScriptService」にマウスを乗せて「+」を押し、検索欄に「Script」と入れて、一覧の「Script」を押します。
Scriptが入り、中身の画面が開きます。エクスプローラで「Script」を押して選び、F2キーを押して名前を「ShopService」に変えます。
ShopServiceを選んだまま、プロパティの検索欄に「RunContext」と入れて、「▼動作」の欄にあるRunContextの一覧から「Server」を選びます(赤い枠)。RunContextは、スクリプトをどこで動かすかを決める設定です。「Server」にすると、サーバーで動きます。前編のWaveServiceやTowerServiceと同じ設定です。

開いているShopServiceの画面で、もとから入っている1行を消し、次のコードを貼り付けます(赤い枠)。
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local td = workspace:WaitForChild("TowerDefense")
local towers = td:WaitForChild("Towers")
local enemies = td:WaitForChild("Enemies")
local placeTower = ReplicatedStorage:WaitForChild("PlaceTower")
local towerTemplate = ServerStorage:WaitForChild("Tower")
local START_MONEY = 100 -- 最初に持っているお金
local REWARD = 10 -- 敵を1体倒すともらえるお金
local PRICE = 50 -- タワー1つの値段
local CENTER = Vector3.new(0, 0, -30) -- 置ける場所の中心
local AREA = 30 -- 中心からこの距離(スタッド)までなら置ける
-- 参加した人ごとに Money を用意する
local function addMoney(player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
local money = Instance.new("IntValue")
money.Name = "Money"
money.Value = START_MONEY
money.Parent = leaderstats
end
Players.PlayerAdded:Connect(addMoney)
-- 上の WaitForChild を待っている間に入ってきた人にも用意する
for _, player in Players:GetPlayers() do
if not player:FindFirstChild("leaderstats") then
addMoney(player)
end
end
local function getMoney(player)
local leaderstats = player:FindFirstChild("leaderstats")
return leaderstats and leaderstats:FindFirstChild("Money")
end
-- 敵が倒れたら、遊んでいる全員にお金を渡す
enemies.ChildAdded:Connect(function(enemy)
local humanoid = enemy:WaitForChild("Humanoid")
humanoid.Died:Connect(function()
for _, player in Players:GetPlayers() do
local money = getMoney(player)
if money then
money.Value += REWARD
end
end
end)
end)
-- 置く場所に重なると困る部品(道・目印・拠点・タワー)
local params = OverlapParams.new()
params.FilterType = Enum.RaycastFilterType.Include
params.FilterDescendantsInstances = {
td:WaitForChild("Road"),
td:WaitForChild("Waypoints"),
td:WaitForChild("Base"),
towers,
}
placeTower.OnServerEvent:Connect(function(player, position)
-- 1. 送られてきたものが本当に位置(Vector3)か
if typeof(position) ~= "Vector3" then
return
end
-- 2. お金が足りるか(サーバーが自分で調べる)
local money = getMoney(player)
if not money or money.Value < PRICE then
placeTower:FireClient(player, "お金が足りません")
return
end
-- 3. 置ける広さの中か
local spot = Vector3.new(math.round(position.X), 3, math.round(position.Z))
if (Vector3.new(spot.X, 0, spot.Z) - CENTER).Magnitude > AREA then
placeTower:FireClient(player, "ここには置けません")
return
end
-- 4. 道やほかのタワーに重ならないか
local hits = workspace:GetPartBoundsInBox(CFrame.new(spot), Vector3.new(3, 6, 3), params)
if #hits > 0 then
placeTower:FireClient(player, "ここには置けません")
return
end
-- 全部だいじょうぶなら、お金を減らしてタワーを置く
money.Value -= PRICE
local tower = towerTemplate:Clone()
tower.Position = spot
tower.Parent = towers
placeTower:FireClient(player, "置きました!")
end)

| コードの部分 | していること |
|---|---|
| addMoney | 参加した人ごとに、右上に出るMoneyを、START_MONEYで決めた100で用意する |
| humanoid.Died | 敵が倒れたら、遊んでいる全員のMoneyを、REWARDで決めた10だけ増やす |
| typeof(position) | 送られてきたものが本当に位置かを確かめる |
| money.Value < PRICE | お金が50より少なければ「お金が足りません」と返す |
| AREA | 道のまわり(中心から30スタッド)より遠ければ「ここには置けません」と返す |
| GetPartBoundsInBox | 置く場所に道・目印・拠点・ほかのタワーがあれば「ここには置けません」と返す |
| towerTemplate:Clone() | ServerStorageの手本を複製して、クリックした場所に置く |
置いたタワーは「Towers」フォルダーに入ります。前編のTowerServiceは「Towers」の中のパーツを全部タワーとして動かすので、置いたタワーはすぐに敵を撃ち始めます。TowerServiceは書きかえなくて大丈夫です。
6. テストでお金を確かめる
3D画面の上の作品のタブ「Place2」を押して3D画面に戻り、F5キーでテストを始めます(タブの名前はパソコンによって違います)。右上にMoneyの一覧が出ます(一覧が出ないときはTabキーを押すと出ます)。最初のお金は100です(画像の1)。左下にはボタンが出ています(画像の2)。

画像は、前編と同じように、道全体が見えるようにカメラを上に引いて写しています。前編のタワーが敵を倒すたびに、Moneyが10ずつ増え、3体倒して130になりました。
7. タワーを置く
ボタンを押すと、ボタンの文字が「置く場所をクリック」に変わります(画像の1)。道の内側の、まだ何もない地面をクリックします(画像の2)。

クリックした場所にタワーが立ち(画像の2)、ボタンに「置きました!」と出ました(画像の1)。右上のMoneyは、置く前の画像の130から、90になっています(画像の3)。タワーの代金50が引かれ、そのあと敵を1体倒して10増えたためです。置いたタワーのまわりにも青い丸が出て、すぐに敵を撃ち始めました。

8. 置けない場所を確かめる
ボタンを押してから道の上をクリックすると、タワーは置かれず、ボタンに「ここには置けません」と出ました(画像の1、2)。Moneyも減りません。この画像は、タワーを置いたあとに敵を倒してMoneyが130まで増えたところで試しています。

道から遠く離れた、置ける広さの外をクリックしても、「ここには置けません」と出ました(画像の1、2)。この画像は、タワーを何本か足してウェーブを最後まで守りきったあとに試したものです。道が灰色っぽく見えるのは、タワーの届く範囲を示す青い丸が重なっているためです。

タワーを置いていってMoneyが0になったあとにボタンを押してクリックすると、「お金が足りません」と出て、タワーは置かれませんでした(画像の2、3)。タワーを5つ足したので、拠点の体力は10のまま「ぜんぶ守りきった!」になりました(画像の1)。

| 試したこと | 結果 |
|---|---|
| 敵を倒す | Moneyが10ずつ増えた |
| 道の内側の地面をクリック | タワーが立ち、Moneyが50減った |
| 道の上をクリック | 「ここには置けません」、Moneyは減らない |
| 置ける広さの外をクリック | 「ここには置けません」、Moneyは減らない |
| Money 0でクリック | 「お金が足りません」 |
| タワーを足して最後まで | 拠点の体力10のまま守りきった |
前編のタワー1つだけでは体力3まで減りましたが、タワーを足すと1体も拠点にたどり着きませんでした。
数字を変えて遊びを調整する
| 変えたいこと | 変える数 | 例 |
|---|---|---|
| 最初のお金 | ShopServiceのSTART_MONEY | 100 → 50にすると、最初は1つしか置けない |
| 1体倒したときのお金 | ShopServiceのREWARD | 10 → 5 |
| タワーの値段 | ShopServiceのPRICE | 50 → 100。ボタンの文字のBUTTON_TEXTも同じ数に直す |
| 置ける広さ | ShopServiceのAREA | 30 → 20にすると、道の近くにしか置けない |
ゲームを難しくしたいときは、お金を減らすより、前編のWaveServiceで敵の速さや体力を上げるほうが、遊ぶ人に変化が伝わりやすくなります。
タワーが置けないときの確かめ方
| 症状 | 見るところ | 直し方 |
|---|---|---|
| ボタンを押しても文字が変わらない | PlaceClient | TextButtonの中にあるLocalScriptか(Scriptでは動かない) |
| どこをクリックしても何も起きない | PlaceTower | ReplicatedStorageの中にあるか、名前が合っているか |
| いつも「ここには置けません」 | クリックした場所 | 道の上や、ほかのタワーのすぐ横をクリックしていないか |
| 置けたのにタワーが見えない | ServerStorageのTower | 手本のTowerがServerStorageの中にあるか |
| 置いたタワーが撃たない | 置いた場所 | 置いたTowerが「Towers」フォルダーの中に入っているか |
| 右上にMoneyが出ない | leaderstats | テスト中にTabキーを押したか。「leaderstats」が小文字で合っているか |
AIに相談するときの伝え方
Roblox Studioで、キットを使わずにタワーディフェンスを作っています。
StarterGuiのTextButtonの中のLocalScriptで、ボタンを押したあと地面をクリックすると、mouse.Hit.PositionをReplicatedStorageのRemoteEvent「PlaceTower」でサーバーに送っています。
ServerScriptServiceのScript「ShopService」で、お金(leaderstatsのMoney)が50以上あり、道やほかのタワーに重ならなければ、ServerStorageのTowerを複製して置いています。
置いたタワーをクリックすると、30払って強くできるようにしたいです。お金はサーバーでだけ変えてください。
よくある質問
スマホでも置ける?
このコードは、パソコンのマウスのクリックで動きます。スマホで遊べるようにするボタンの作り方は、ボタンの作り方で解説しています。
2人で遊ぶとお金はどうなる?
敵が倒れると、遊んでいる全員のMoneyが10ずつ増えます。タワーは、置いた人のMoneyだけが減ります。みんなで同じ拠点を守る遊び方になります。
抜けてもお金を残したい
このコードでは、ゲームを抜けるとお金は消えます。残したいときは、DataStoreで保存します。保存のしかたはDataStoreの使い方で解説しています。
次は、朝・夕方・夜を作り、夜の街灯を光らせます。次のステップ(中級 20/28)
次に知りたいことを選ぶ
- 次のステップ:朝・夕方・夜を作り、夜の街灯を光らせます → ロブロックス(Roblox)スタジオで夜にする方法
- 前編から作りたい → ロブロックス(Roblox)スタジオでタワーディフェンスを作る方法【前編】
- ボタンからサーバーに知らせるしくみを知りたい → ロブロックス(Roblox)のRemoteEventの使い方
- お金を保存したい → ロブロックス(Roblox)のDataStoreの使い方