Menu
CoddyTech

購入できない

Coddyの「Robloxゲーム:タワーディフェンス」コースのレッスン 10/18。

これまでタワーは無料で、サーバーは要求されたものを何でも作っていました。プレイヤーは自分のデバイスを操作するため、悪用者は何でも指定して Remotes.Place を実行できます。別のタワーの上やベースプレート上にタワーを置いたり、存在しないタワーを置いたり、お金がまったくない状態で置いたりできます。

そこでサーバーは、作成する前に各リクエストをチェックし、最初に問題が見つかった時点で処理を終了します。

  • パッドが workspace.Pads 内の Instance であり、まだタワーが置かれていない(placeTower が設定する属性 Used がある)、
  • 種類が実在するタワーの名前を示す文字列である、
  • プレイヤーがお金を持っている。その後、サーバーがお金を差し引く。
if typeof(pad) ~= "Instance" or pad.Parent ~= workspace.Pads then
	return
end

クライアントが要求し、サーバーが判断します。このレッスンのチェックでは、悪用者の役を演じます。

challenge icon

チャレンジ

中級

Place ハンドラーでは、次の場合、タワーを建てずに処理を終了します。

  1. パッドが Instance ではない、workspace.Pads 内にない、または属性 Used が設定されている場合、
  2. kind が文字列ではない、または Settings.Towers 内のタワー名ではない場合、
  3. プレイヤーの Money がタワーの Price より少ない場合。

それ以外の場合は価格を差し引いて、タワーを建てます。placeTower では、パッドの Used 属性を true に設定します。

Play を押してから、Check を押します。

自分で試してみよう

local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local Debris = game:GetService("Debris")
local Settings = require(game.ReplicatedStorage.Settings)

local remotes = game.ReplicatedStorage.Remotes
local enemies = workspace.Enemies
local towers = workspace.Towers

print("The base can take " .. Settings.BaseHealth .. " hits")

-- 順番に並べたウェイポイント(名前は 1, 2, 3...)
local waypoints = workspace.Path.Waypoints:GetChildren()
table.sort(waypoints, function(a, b)
	return tonumber(a.Name) < tonumber(b.Name)
end)

-- workspace の属性として設定された基地の体力。すべてのクライアントが読み取れる
workspace:SetAttribute("BaseHealth", Settings.BaseHealth)

-- すべてのプレイヤーがリーダーボードに Money を持つ
Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player

	local money = Instance.new("IntValue")
	money.Name = "Money"
	money.Value = Settings.StartMoney
	money.Parent = leaderstats
end)

-- 敵が基地に到達した
local function damageBase(amount)
	local health = math.max(0, workspace:GetAttribute("BaseHealth") - amount)
	workspace:SetAttribute("BaseHealth", health)
end

-- 敵を経路に沿って移動させる。MoveTo は 8 秒後に移動を断念し、そのことを通知する
-- (MoveToFinished が false になる)ので、遅い敵には到着するまで移動を再指示する。
local function walkPath(enemy)
	local humanoid = enemy.Humanoid
	for i = 2, #waypoints do
		repeat
			humanoid:MoveTo(waypoints[i].Position)
		until humanoid.MoveToFinished:Wait() or not enemy.Parent
		if not enemy.Parent then
			return
		end
	end
	-- 基地に到達した
	damageBase(Settings.Enemies[enemy.Name].Damage)
	enemy:Destroy()
end

-- ゲートに現れ、経路を進む新しい敵
local function spawnEnemy(kind)
	local enemy = ServerStorage.Enemies[kind]:Clone()
	-- 最初のウェイポイントのすぐ上に配置する
	enemy:PivotTo(waypoints[1].CFrame + Vector3.new(0, 5, 0))
	local info = Settings.Enemies[kind]
	enemy.Humanoid.MaxHealth = info.Health
	enemy.Humanoid.Health = info.Health
	enemy.Humanoid.WalkSpeed = info.Speed
	enemy.Parent = enemies
	enemy.Humanoid.Died:Connect(function()
		-- すべてのプレイヤーが報酬を獲得する
		for _, player in Players:GetPlayers() do
			player.leaderstats.Money.Value += info.Reward
		end
		enemy:Destroy()
	end)
	task.spawn(walkPath, enemy)
	return enemy
end

-- 指定した地点から範囲内にいる最も近い生存中の敵。いなければ nil
local function nearestEnemy(position, range)
	local best, bestDistance = nil, range
	for _, enemy in enemies:GetChildren() do
		local root = enemy:FindFirstChild("HumanoidRootPart")
		if root and enemy.Humanoid.Health > 0 then
			local distance = (root.Position - position).Magnitude
			if distance <= bestDistance then
				best, bestDistance = enemy, distance
			end
		end
	end
	return best
end

-- タワーから標的に向かって光が一瞬走る
local function beam(from, to)
	local ray = Instance.new("Part")
	ray.Anchored = true
	ray.CanCollide = false
	ray.CanTouch = false
	ray.Material = Enum.Material.Neon
	ray.Color = Color3.fromRGB(255, 240, 150)
	ray.Size = Vector3.new(0.2, 0.2, (to - from).Magnitude)
	ray.CFrame = CFrame.lookAt((from + to) / 2, to)
	ray.Parent = workspace
	Debris:AddItem(ray, 0.1)
end

-- 射撃:ターゲットにダメージを与える
local function shoot(tower, target, info)
	local damage = info.Damage
	local at = target.HumanoidRootPart.Position
	target.Humanoid:TakeDamage(damage)
	beam(tower.Head.Position, at)
end

-- タワーの動作:射程内で最も近い敵を狙い、撃ってからクールダウンが終わるまで待つ
local function runTower(tower)
	local info = Settings.Towers[tower.Name]
	while tower.Parent do
		local target = nearestEnemy(tower.Head.Position, info.Range)
		if target then
			tower.Head.CFrame = CFrame.lookAt(tower.Head.Position, target.HumanoidRootPart.Position)
			shoot(tower, target, info)
			task.wait(info.Cooldown)
		else
			task.wait(0.1)
		end
	end
end

-- 設置台にタワーを建てる
local function placeTower(pad, kind)
	local tower = ServerStorage.Towers[kind]:Clone()
	tower:PivotTo(pad.CFrame + Vector3.new(0, pad.Size.Y / 2 + 1, 0))
	tower.Parent = towers
	task.spawn(runTower, tower)
end

-- クライアントが設置台へのタワーの配置を要求する
remotes.Place.OnServerEvent:Connect(function(player, pad, kind)
	placeTower(pad, kind)
end)

-- 1つのウェーブ:敵を1体ずつ出現させ、全員いなくなるまで待つ
local function runWave(list)
	for _, kind in list do
		spawnEnemy(kind)
		task.wait(Settings.SpawnGap)
	end
	while #enemies:GetChildren() > 0 do
		task.wait(0.2)
	end
end

-- 各ウェーブは休憩を挟んで開始する
for i, list in Settings.Waves do
	task.wait(Settings.WaveBreak)
	runWave(list)
end
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

Robloxゲーム:タワーディフェンスのすべてのレッスン