Menu
CoddyTech

احفظ عند المغادرة

الدرس 20 من 21 في دورة لعبة Roblox: البرج اللانهائي على Coddy.

يجب أن تظل مرات الفوز وأفضل الأوقات موجودة في المرة القادمة. يحتفظ Roblox بالبيانات بين الزيارات في DataStore، وهو جدول على خوادم Roblox يبقى بعد إغلاق اللعبة. احفظ البيانات عندما يغادر لاعب، ولجميع اللاعبين الذين ما زالوا يلعبون عند إيقاف تشغيل الخادم:

local coinStore = DataStoreService:GetDataStore("Coins")

local function save(player)
	pcall(function()
		coinStore:SetAsync(player.UserId, { Coins = player.leaderstats.Coins.Value })
	end)
end

Players.PlayerRemoving:Connect(save)
game:BindToClose(function()
	for _, player in Players:GetPlayers() do
		save(player)
	end
end)

قد يفشل الحفظ من جهة Roblox، وقد يؤدي الفشل إلى إيقاف البرنامج النصي، لذا يوضع داخل pcall. المفتاح هو UserId الخاص باللاعب، وهو لا يتغير أبدًا، بخلاف اسمه.

challenge icon

التحدي

صعب

احفظ عدد مرات فوز كل لاعب وأفضل وقت له عند مغادرته.

  1. بالقرب من الأعلى: local DataStoreService = game:GetService("DataStoreService") وlocal towerStore = DataStoreService:GetDataStore("Tower").
  2. اكتب saveProgress(player): داخل pcall، استدعِ towerStore:SetAsync(player.UserId, { Wins = ..., BestTime = ... }) مستخدمًا بيانات لوحة المتصدرين الخاصة به.
  3. استدعِها عند Players.PlayerRemoving، ولكل لاعب داخل game:BindToClose.

اضغط «العب»، ثم افز واضغط «إيقاف»: سيُحفَظ عدد مرات فوزك لتتمكن من تحميله في الدرس التالي. ثم اضغط «تحقق».

جرّب بنفسك

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

local stages = ServerStorage.Stages
local lobbySpawn = workspace.Lobby.SpawnLocation
local tower = nil
local towerStages = {}
local lava = nil
local roundStart = 0
local finished = {}

print("Each tower has " .. Settings.StageCount .. " stages")

-- يحصل كل لاعب على Wins وStage وBestTime في لوحة الصدارة
Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player

	local wins = Instance.new("IntValue")
	wins.Name = "Wins"
	wins.Parent = leaderstats

	local stage = Instance.new("IntValue")
	stage.Name = "Stage"
	stage.Parent = leaderstats

	local best = Instance.new("NumberValue")
	best.Name = "BestTime"
	best.Parent = leaderstats

	-- يبدأ اللاعب الذي يسقط مجددًا من آخر مرحلة وصل إليها،
	-- إلا إذا غطّتها الحمم البركانية: عندها ينتظر في الردهة
	player.CharacterAdded:Connect(function(character)
		task.wait()
		local checkpoint = towerStages[stage.Value]
		local covered = checkpoint and lava and checkpoint.Start.Position.Y < lava.Position.Y + lava.Size.Y / 2
		if checkpoint and not covered and not finished[player] then
			character:PivotTo(checkpoint.Start.CFrame + Vector3.new(0, 4, 0))
		end
	end)
end)

-- اللوحة الموجودة في الردهة
local gui = Instance.new("SurfaceGui")
gui.Parent = workspace.Lobby.Board

local boardLabel = Instance.new("TextLabel")
boardLabel.Name = "Status"
boardLabel.Size = UDim2.fromScale(1, 1)
boardLabel.BackgroundColor3 = Color3.fromRGB(25, 25, 35)
boardLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
boardLabel.TextScaled = true
boardLabel.Text = "Endless Tower"
boardLabel.Parent = gui

local function setBoard(text)
	boardLabel.Text = text
end

-- أي شيء يحمل الوسم Kill ينهي حياة اللاعب
local function killOnTouch(hit)
	local humanoid = hit.Parent:FindFirstChild("Humanoid")
	if humanoid then
		humanoid.Health = 0
	end
end

-- تتلاشى المنصة المختفية بعد وقت قصير من هبوط أحد عليها، ثم تعود
local function makeVanish(part)
	part.Touched:Connect(function(hit)
		if part:GetAttribute("Gone") or not hit.Parent:FindFirstChild("Humanoid") then
			return
		end
		part:SetAttribute("Gone", true)
		task.wait(Settings.VanishDelay)
		part.Transparency = 0.8
		part.CanCollide = false
		task.wait(Settings.VanishTime)
		part.Transparency = 0
		part.CanCollide = true
		part:SetAttribute("Gone", nil)
	end)
end

-- ينزلق الليزر إلى الموضع المحدد بإزاحة Slide ثم يعود، بلا توقف
local function makeSlide(part)
	local info = TweenInfo.new(Settings.SlideTime, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut, -1, true)
	local goal = { Position = part.Position + part:GetAttribute("Slide") }
	TweenService:Create(part, info, goal):Play()
end

-- تقذف منصة القفز من يهبط عليها إلى أعلى في الهواء
local function bounce(hit)
	local root = hit.Parent:FindFirstChild("HumanoidRootPart")
	if root then
		root.AssemblyLinearVelocity = Vector3.new(0, Settings.BouncePower, 0)
	end
end

-- تدور العناصر الدوّارة قليلًا في كل إطار
RunService.Heartbeat:Connect(function(dt)
	for _, spinner in CollectionService:GetTagged("Spinner") do
		if spinner:IsDescendantOf(workspace) then
			spinner:PivotTo(spinner:GetPivot() * CFrame.Angles(0, math.rad(Settings.SpinSpeed) * dt, 0))
		end
	end
end)

-- فعّل كل عقبة في المرحلة وفقًا لوسومها
local function setupStage(stage, index)
	for _, part in stage:GetDescendants() do
		if part:HasTag("Kill") then
			part.Touched:Connect(killOnTouch)
		end
		if part:HasTag("Vanish") then
			makeVanish(part)
		end
		if part:HasTag("Conveyor") then
			part.AssemblyLinearVelocity = part.CFrame.LookVector * Settings.BeltSpeed
		end
		if part:HasTag("Slider") then
			makeSlide(part)
		end
		if part:HasTag("Bounce") then
			part.Touched:Connect(bounce)
		end
	end
	-- يُحتسب الوصول إلى Start الخاص بالمرحلة في لوحة الصدارة
	stage.Start.Touched:Connect(function(hit)
		local player = Players:GetPlayerFromCharacter(hit.Parent)
		if player and player.leaderstats.Stage.Value < index then
			player.leaderstats.Stage.Value = index
		end
	end)
end

-- تزداد صعوبة المراحل كلما ارتفعنا: الثلث السفلي سهل، والثلث العلوي صعب
local function pickStage(index)
	local difficulty = math.ceil(index / Settings.StageCount * 3)
	local choices = {}
	for _, stage in stages:GetChildren() do
		if stage:GetAttribute("Difficulty") == difficulty then
			table.insert(choices, stage)
		end
	end
	return choices[math.random(1, #choices)]
end

-- ضع نسخة من نموذج فوق البرج؛ تُرجع النسخة والارتفاع الجديد لقمة البرج
local function stack(template, bottom)
	local copy = template:Clone()
	local box, size = copy:GetBoundingBox()
	local lift = bottom - (box.Position.Y - size.Y / 2)
	copy:PivotTo(copy:GetPivot() + Vector3.new(0, lift, 0))
	copy.Parent = tower
	return copy, bottom + size.Y
end

-- قمة البرج: فوز ثم عودة إلى ساحة الانتظار
local function reachTop(hit)
	local player = Players:GetPlayerFromCharacter(hit.Parent)
	if not player or finished[player] then
		return
	end
	finished[player] = true
	player.leaderstats.Wins.Value += 1
	local seconds = math.floor((time() - roundStart) * 10) / 10
	local best = player.leaderstats.BestTime
	if best.Value == 0 or seconds < best.Value then
		best.Value = seconds
	end
	print(player.Name .. " reached the top in " .. seconds .. " seconds")
	hit.Parent:PivotTo(lobbySpawn.CFrame + Vector3.new(0, 4, 0))
end

-- ابنِ برجًا عشوائيًا جديدًا من الأسفل إلى الأعلى
local function buildTower()
	tower = Instance.new("Model")
	tower.Name = "Tower"
	tower.Parent = workspace
	towerStages = {}
	local top = 0
	for i = 1, Settings.StageCount do
		local stage, newTop = stack(pickStage(i), top)
		top = newTop
		setupStage(stage, i)
		towerStages[i] = stage
	end
	local finish = stack(ServerStorage.Finish, top)
	finish.FinishPad.Touched:Connect(reachTop)
	return top
end

-- ترتفع الحمم البركانية من الأرض حتى أسفل قمة البرج مباشرةً
local function startLava(top)
	lava = Instance.new("Part")
	lava.Name = "Lava"
	lava.Anchored = true
	lava.CanCollide = false
	lava.Size = Vector3.new(30, 2, 30)
	lava.Position = Vector3.new(0, -2, 0)
	lava.Color = Color3.fromRGB(255, 90, 20)
	lava.Material = Enum.Material.Neon
	lava.Parent = workspace
	lava.Touched:Connect(killOnTouch)
	local info = TweenInfo.new(top / Settings.LavaSpeed, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut, 0, false, Settings.LavaDelay)
	TweenService:Create(lava, info, { Position = Vector3.new(0, top - 2, 0) }):Play()
end

local function startRound()
	finished = {}
	for _, player in Players:GetPlayers() do
		player.leaderstats.Stage.Value = 0
	end
	local top = buildTower()
	startLava(top)
	roundStart = time()
	for _, player in Players:GetPlayers() do
		if player.Character then
			player.Character:PivotTo(towerStages[1].Start.CFrame + Vector3.new(0, 4, 0))
		end
	end
end

local function endRound()
	for _, player in Players:GetPlayers() do
		if player.Character and not finished[player] then
			player.Character:PivotTo(lobbySpawn.CFrame + Vector3.new(0, 4, 0))
		end
		player.leaderstats.Stage.Value = 0
	end
	tower:Destroy()
	lava:Destroy()
	towerStages = {}
end

-- الدرس 20: يُضاف هنا حفظ التقدم عند المغادرة

-- حلقة الجولات
while true do
	for i = Settings.Intermission, 1, -1 do
		setBoard("Next tower in " .. i)
		task.wait(1)
	end
	startRound()
	for i = Settings.RoundLength, 1, -1 do
		setBoard("Climb! " .. i .. " seconds left")
		task.wait(1)
	end
	endRound()
end

جميع دروس لعبة Roblox: البرج اللانهائي

تدرّب بنفسك: Playground لـ Roblox