사막의 관문
Coddy의 Roblox 게임: 펫 시뮬레이터 코스 레슨. 18개 중 15번째.
뒤쪽의 빨간 벽은 사막으로 가는 관문이며, "잠금 해제" 프롬프트가 있습니다. Settings.GatePrice만큼 지불하면 관문이 열려야 하지만, 지불한 플레이어에게만 열려야 합니다.
이를 구현하려면 양쪽에서 모두 처리해야 합니다. 서버는 코인을 가져가고 플레이어의 속성인 Desert에 잠금 해제 상태를 기록합니다. 그러면 모든 규칙에서 이를 확인할 수 있습니다. 클라이언트는 해당 속성을 감시하고 자기 화면에서 관문을 엽니다. Roblox에서는 각 플레이어의 기기가 자기 캐릭터를 움직이므로, LocalScript가 벽을 비고체 상태로 만들면 그 플레이어는 통과할 수 있지만 다른 모든 플레이어에게는 계속 단단한 벽으로 남습니다.
-- 클라이언트
workspace.Gate.CanCollide = false
workspace.Gate.Transparency = 0.7같은 방식으로 관문의 프롬프트를 숨기면 두 번째 잠금 해제를 제안하지 않게 됩니다.
챌린지
어려움PetServer에서workspace.Gate.ProximityPrompt.Triggered를 연결하세요. 플레이어에게 이미Desert속성이 있거나 코인이Settings.GatePrice보다 적으면 중단하고, 그렇지 않으면 가격만큼 코인을 차감하고 플레이어의Desert속성을 true로 설정하세요.PetClient에서openGate()를 작성하세요. 플레이어의 Desert 속성이 설정되어 있으면 게이트의 CanCollide를 false로, Transparency를 0.7로, 프롬프트의 Enabled를 false로 설정하세요. 지금 호출하고 속성이 변경될 때도 호출하세요.
플레이를 눌러 100코인을 모으고 Desert를 잠금 해제하세요. 그런 다음 확인을 누르세요.
직접 해보기
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local RunService = game:GetService("RunService")
local Settings = require(game.ReplicatedStorage.Settings)
local remotes = game.ReplicatedStorage.Remotes
local piles = workspace.Piles
local petParts = workspace.PetParts
local pileSpot = {} -- 각 코인 더미 -> 그 더미가 놓인 자리
local lastHit = {} -- 각 플레이어 -> 마지막으로 타격한 시각
print("A Meadow pile gives " .. Settings.PileValue .. " coins")
-- 이름을 기준으로 정리한 Settings.Pets
local petInfo = {}
for _, info in Settings.Pets do
petInfo[info.Name] = info
end
-- 펫: 플레이어의 Pets 폴더에 있는 StringValue로, 이름을 값으로 저장하고
-- 희귀도와 위력을 속성으로 저장함
local function givePet(player, name, equipped)
local info = petInfo[name]
local pet = Instance.new("StringValue")
pet.Name = "Pet"
pet.Value = name
pet:SetAttribute("Rarity", info.Rarity)
pet:SetAttribute("Power", info.Power)
pet:SetAttribute("Equipped", equipped)
pet.Parent = player.Pets
return pet
end
-- 플레이어가 꺼내 놓은 펫의 수
local function equippedCount(player)
local count = 0
for _, pet in player.Pets:GetChildren() do
if pet:GetAttribute("Equipped") then
count += 1
end
end
return count
end
-- 플레이어의 펫이 각 타격에 추가하는 위력
local function petPower(player)
local power = 0
for _, pet in player.Pets:GetChildren() do
if pet:GetAttribute("Equipped") then
power += pet:GetAttribute("Power")
end
end
return power
end
-- 모든 플레이어의 리더보드에 Coins가 추가됨
Players.PlayerAdded:Connect(function(player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
local coins = Instance.new("IntValue")
coins.Name = "Coins"
coins.Parent = leaderstats
-- 플레이어가 소유한 펫
local pets = Instance.new("Folder")
pets.Name = "Pets"
pets.Parent = player
end)
-- 자리에 놓인 코인 더미: ServerStorage.CoinPile의 복사본으로, 체력과
-- 가치를 속성으로 가짐
local function spawnPile(spot)
local pile = ServerStorage.CoinPile:Clone()
pile.Position = spot.Position
pile:SetAttribute("MaxHealth", Settings.PileHealth)
pile:SetAttribute("Health", Settings.PileHealth)
pile:SetAttribute("Value", Settings.PileValue)
pile.HealthGui.Label.Text = pile:GetAttribute("Health") .. "/" .. pile:GetAttribute("MaxHealth")
pileSpot[pile] = spot
pile.Parent = piles
return pile
end
-- 한 번 타격할 때마다 코인 더미의 체력이 1에 플레이어 펫의 위력을 더한 만큼 줄어듦;
-- 0이 되면 더미가 부서지고 그 가치만큼 코인을 지급한다
local function hitPile(player, pile)
local health = pile:GetAttribute("Health") - (1 + petPower(player))
if health > 0 then
pile:SetAttribute("Health", health)
pile.HealthGui.Label.Text = health .. "/" .. pile:GetAttribute("MaxHealth")
return
end
player.leaderstats.Coins.Value += pile:GetAttribute("Value")
local spot = pileSpot[pile]
pileSpot[pile] = nil
pile:Destroy()
-- 몇 초 후 같은 자리에 새 더미가 생성된다
task.delay(Settings.RespawnTime, spawnPile, spot)
end
-- Meadow의 모든 위치에 더미 하나씩
for _, spot in workspace.Zones.Meadow.Spawns:GetChildren() do
spawnPile(spot)
end
-- 클라이언트가 더미를 공격하겠다고 요청한다. 클라이언트를 절대 신뢰하지 말 것: 누구나
-- 어디서든 원하는 만큼 빠르게 어떤 값으로든 이 remote를 호출할 수 있다.
remotes.Hit.OnServerEvent:Connect(function(player, pile)
-- 실제 더미인지 확인
if typeof(pile) ~= "Instance" or pile.Parent ~= piles then
return
end
-- 도달할 수 있는 거리인지 확인
local character = player.Character
local root = character and character:FindFirstChild("HumanoidRootPart")
if not root or (root.Position - pile.Position).Magnitude > Settings.Reach then
return
end
-- 너무 빠르게 공격하지 않는지 확인
local now = time()
if lastHit[player] and now - lastHit[player] < Settings.HitCooldown then
return
end
lastHit[player] = now
hitPile(player, pile)
end)
-- 가중치 추첨: 각 pet의 Chance가 전체 100 중 차지하는 비율만큼 당첨 확률을 갖는다
local function rollPet()
local roll = math.random() * 100
for _, info in Settings.Pets do
roll -= info.Chance
if roll <= 0 then
return info.Name
end
end
return Settings.Pets[1].Name
end
-- 부화: 알의 프롬프트로 진행하며 코인으로 비용을 지불한다
workspace.Egg.ProximityPrompt.Triggered:Connect(function(player)
local coins = player.leaderstats.Coins
if coins.Value < Settings.EggPrice then
return
end
coins.Value -= Settings.EggPrice
local name = rollPet()
-- 자리가 있으면 즉시 꺼낸다
local pet = givePet(player, name, equippedCount(player) < Settings.MaxEquipped)
print(player.Name .. " hatched a " .. name)
end)
-- 꺼낸 Pets는 주인을 따라다닌다: 각각 월드에 파트를 하나씩 두고,
-- 매 프레임 플레이어 뒤에 줄지어 배치됩니다
local petModels = {} -- 각 펫 -> 월드에서 해당 펫을 나타내는 파트
RunService.Heartbeat:Connect(function()
for _, player in Players:GetPlayers() do
local root = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
local pets = player:FindFirstChild("Pets")
local slot = 0
for _, pet in pets and pets:GetChildren() or {} do
local model = petModels[pet]
if pet:GetAttribute("Equipped") and root then
slot += 1
if not model then
model = ServerStorage.PetModels[pet.Value]:Clone()
model.Parent = petParts
petModels[pet] = model
end
model.CFrame = root.CFrame * CFrame.new((slot - 2) * 3, -1.5, 4)
elseif model then
model:Destroy()
petModels[pet] = nil
end
end
end
end)
-- 클라이언트가 펫을 꺼내거나 넣어 달라고 요청합니다. 서버는 해당 펫이
-- 플레이어의 소유인지, 꺼내 놓은 펫이 MaxEquipped를 초과하지 않는지 확인합니다.
remotes.Equip.OnServerEvent:Connect(function(player, pet)
if typeof(pet) ~= "Instance" or pet.Parent ~= player.Pets then
return
end
if pet:GetAttribute("Equipped") then
pet:SetAttribute("Equipped", false)
elseif equippedCount(player) < Settings.MaxEquipped then
pet:SetAttribute("Equipped", true)
end
end)
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.