Menu
CoddyTech

Загрузка при входе

Урок 20 из 20 курса Игра в Roblox: симулятор питомцев на Coddy.

Теперь считай сохранённые данные, когда игрок присоединяется. GetAsync возвращает то, что SetAsync сохранил под ключом, или nil, если игрок ещё никогда ничего не сохранял. Вызов тоже может завершиться ошибкой, поэтому вызывай его в pcall, который возвращает, сработал ли вызов, а затем значение:

local ok, saved = pcall(function()
	return saveStore:GetAsync(player.UserId)
end)
if ok and saved then
	print(saved.Coins)
end

Загружай данные после создания папок, чтобы у питомцев была папка Pets, в которую их можно поместить. Пропускай сохранённые имена, которых нет в petInfo: питомец, удалённый из игры, не должен мешать присоединению игрока.

challenge icon

Задание

Сложно

Верни игрокам то, что они сохранили.

  1. В PlayerAdded, после создания папки Pets: local ok, saved = pcall(function() return saveStore:GetAsync(player.UserId) end).
  2. Если ok and saved: установи значение Coins в saved.Coins, а атрибут Desert — в saved.Desert.
  3. Для каждого сохранённого питомца в petInfo вызови givePet(player, pet.Name, pet.Equipped == true).

Нажми «Играть», вылупи питомца, нажми «Остановить», затем снова «Играть»: твои монеты и питомцы вернутся. Затем нажми «Проверить».

Попробуйте сами

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

local remotes = game.ReplicatedStorage.Remotes
local piles = workspace.Piles
local petParts = workspace.PetParts
local saveStore = DataStoreService:GetDataStore("Pets")
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

-- Питомец: StringValue в папке Pets игрока, содержащее его имя, а
-- его редкость и сила хранятся в атрибутах
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
	-- Урок 20: здесь будет загрузка при входе
end)

-- Куча монет на месте: копия ServerStorage.CoinPile, у которой запас прочности
-- и стоимость хранятся в атрибутах
local function spawnPile(spot)
	local desert = spot.Parent.Parent.Name == "Desert"
	local health = desert and Settings.DesertHealth or Settings.PileHealth
	local pile = ServerStorage.CoinPile:Clone()
	pile.Position = spot.Position
	pile:SetAttribute("MaxHealth", health)
	pile:SetAttribute("Health", health)
	pile:SetAttribute("Value", desert and Settings.DesertValue or Settings.PileValue)
	pile:SetAttribute("Zone", spot.Parent.Parent.Name)
	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

-- По куче на каждом месте в каждой зоне
for _, zone in workspace.Zones:GetChildren() do
	for _, spot in zone.Spawns:GetChildren() do
		spawnPile(spot)
	end
end

-- Клиент запрашивает удар по куче. Никогда не доверяйте клиенту: кто угодно может вызвать
-- это удалённое событие с любыми данными, откуда угодно и с любой частотой.
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
	-- в зоне, которую игрок открыл
	if pile:GetAttribute("Zone") == "Desert" and not player:GetAttribute("Desert") then
		return
	end
	hitPile(player, pile)
end)

-- Взвешенный выбор: 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)
	-- Все узнают о самых редких питомцах
	if pet:GetAttribute("Rarity") == "Legendary" then
		remotes.Announce:FireAllClients(player.Name, name)
	end
end)

-- Выпущенные питомцы следуют за хозяином: у каждого есть объект в игровом мире, который
-- в каждом кадре располагается в ряду позади игрока
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)

-- Ворота в Desert: заплатив один раз, игрок открывает их только для себя
workspace.Gate.ProximityPrompt.Triggered:Connect(function(player)
	local coins = player.leaderstats.Coins
	if player:GetAttribute("Desert") or coins.Value < Settings.GatePrice then
		return
	end
	coins.Value -= Settings.GatePrice
	player:SetAttribute("Desert", true)
end)

-- Сохранение: Coins, Desert и всех питомцев
local function save(player)
	local pets = {}
	for _, pet in player.Pets:GetChildren() do
		table.insert(pets, { Name = pet.Value, Equipped = pet:GetAttribute("Equipped") == true })
	end
	local data = {
		Coins = player.leaderstats.Coins.Value,
		Desert = player:GetAttribute("Desert") == true,
		Pets = pets,
	}
	pcall(function()
		saveStore:SetAsync(player.UserId, data)
	end)
end

Players.PlayerRemoving:Connect(save)

game:BindToClose(function()
	for _, player in Players:GetPlayers() do
		save(player)
	end
end)
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Игра в Roblox: симулятор питомцев

Потренируйтесь самостоятельно: Песочница Roblox