ROBLOX — Первое приложение с клиент-сервер — client-server

Автор: | 26 марта, 2020
Поделиться...

Повествование будет несколько сумбурное. так как оно делается пост фактум.

Начну с того, что решил переделать считалку (арифметический тренажёр) с Excel-евского макроса под Roblox. И вот тут выяснилось — что пользовательский интерфейс, это совсем не то же самое что серверное приложение. Как и то, что один игрок, это совсем не то же самое что мультиплеер.

По большому счёту можно конечно извращаться и через сервер менять пользовательский интерфейс (что в большинстве я и делал), но в итоге спотыкаемся на том, что действия пользователя ну никак не отследить сервером.

И тут на меня снизошло прозрение в виде LocalScript, с которыми ранее уже знакомились. Они то как раз работают исключительно на стороне пользователя! И тут вторая подножка — они не видят что творится на сервере!

Долго извращался и так и эдак, чтобы остановиться на каком нибудь приемлемом варианте, но в итоге «выкрутился» через одну функцию — удалённый запуск функции на сервере по запросу с клиента с параметром. И… вместо передачи клиенту переменные, операцию и результата сверки.. передаю ничего! Вывожу ему на экран пример в готовом виде и ожидаю вызова серверной функции с параметров в котором и есть ответ набранный пользаком.

https://developer.roblox.com/en-us/articles/Remote-Functions-and-Events/index.html

Причём на сам клиент я ничего не получаю, а с клиента получаю исключительно введённые подряд циферки (через LocalScript).

Скрипт, отслеживающий нажатия клавиш, ниже:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunctionTest")

print("кейлогер запущен")
local txt=""
local res
-- коды клавиш тут
-- https://developer.roblox.com/en-us/api-reference/property/InputObject/KeyCode

function onKeyPress(inputObject, gameProcessedEvent)
	-- print(inputObject.UserInputType) -- Enum.UserInputType.Keyboard -- клавиатура
    if inputObject.KeyCode == Enum.KeyCode.Return or inputObject.KeyCode == Enum.KeyCode.KeypadEnter then
		script.Parent.Parent.PlayerGui.Main.Primer.Text=""
		-- передаём значение на сервер, если оно не нулевое!!!
		--print("Enter was pressed")
		res = remoteFunction:InvokeServer(txt)
		-- print("ответ сервера:",res)
		txt=""
	elseif inputObject.KeyCode == Enum.KeyCode.Backspace or inputObject.KeyCode == Enum.KeyCode.Delete then
		-- стираем полностью ввод
		--print("Backspace was pressed")
		txt=""
	else
		-- добавляем цифру к введёному
		local code = inputObject.KeyCode
		if code.Value>47 and code.Value < 58 then
			-- print("Нажата клавиша: ", code)
			local num=code.Value-48
			--print(num, code.Name, code.Value)
			txt=txt .. num				
		end
		if code.Value>255 and code.Value < 266 then
			-- print("Нажата клавиша кеймпада: ", code)
			local num=code.Value-256
			--print(num, code.Name, code.Value)
			txt=txt .. num				
		end		
    end
	print(txt)
	script.Parent.Parent.PlayerGui.Main.Result.Text=txt
end
game:GetService("UserInputService").InputBegan:connect(onKeyPress)

Но, чтобы он работал нам нужно:

  • создать удалённую функцию в ReplicatedStorage:
  • и скрипт на сервере который эту функцию будет отрабатывать:
local Players = game:GetService("Players") -- берём ссылку на список игроков

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunctionTest")

local ws = game.Workspace
local me = script.Parent
 
-- Create a new part and return it
local function createPart(player,txt)
	print("От игрока:" .. player.Name .. " получено: " .. txt)
	
	-- ищем территорию игрока и вписываем туда ответ	
	local children = workspace:GetChildren() -- ищем детей Workspace
	for i, child in ipairs(children) do -- перебираем
		if child.ClassName == "Model" then -- ищем что это модель 
			if child:FindFirstChild("Rocket") ~= nil then -- и это модель пространства
				if child.Owner.Value == player.Name then -- кем то занято					
					child.VAL.sO.Value = tonumber(txt)
					-- ws.Main.VAL.sO.Value = tonumber(txt)
				end
			end
		end
	end
	
	return "received..."
end
 
-- Bind the "createPart()" function to the remote function's "OnServerInvoke" callback
remoteFunction.OnServerInvoke = createPart

Это и есть три основных момента про функцию клиент серверной ориентации. Т.е. запрос клиентом выполнения функции на сервере с заданным параметром.

И, если честно, на этом мои беды не закончились! Но, пораскинув мозгами, я таки выкрутился кучкой переменных и исключительно серверными скриптами.

Кстати, на этот раз я обошёлся без команд игроков (teams) и проверку осуществлял исключительно по имени текущего игрока и содержимому переменной Owner модели текущей территории. Например, Script преграды через которую может пройти только хозяин территории, выглядит так:

trap = script.Parent -- работаем с родителем скрипта
trap.Touched:Connect(function(Part) -- вызываем событие, когда касаемся столбика
	local player = game.Players:GetPlayerFromCharacter(Part.Parent) -- сохраняем информацию об игроке
	if player ~= nil then  
		if trap.Parent:FindFirstChild("Owner").Value == Part.Parent.Name then -- для хозяина забор проходимый			
			trap.CanCollide = false
			wait(1) -- на полсекунды проходимый
			trap.CanCollide = true
		end 
	end
end) -- конец function

Ещё один момент был в том, чтобы инициализировать повторный вычислительный процесс. На сей раз я сделал полную копию сохранённой в ServerStorage модели территории, поместил её на место текущей и.. выполнил всё что касается заклеймления данной территории текущим игроком. Вот финальный кусочек скрипта:

player.PlayerGui.Main.Timer.Text = "Prepare for restart..."
wait(5)

local original = game.ReplicatedStorage.Main -- Сохраняем в переменную оригинальный объект
local par=me.Parent -- сохраняем привязку

local pos=me:GetPrimaryPartCFrame() -- сохраняем текущую позицию

local copy = original:Clone() -- создаём копию объекта
copy.Parent = par -- указываем значение родителя у копии
copy.Name = me.Name -- сохраняем имя зоны
copy.Owner.Value = me.Owner.Value -- сохраняем привязку зоны
-- восстанавливаем Табло
copy.Tablo.SurfaceGui.TextLabel.Text=me.Owner.Value
copy.Tablo.Transparency = 0 -- выставляем прозрачность
copy.Tablo.SurfaceGui.Frame1.Visible=true

-- восстанавливаем положение зоны
copy:SetPrimaryPartCFrame(pos)

-- уничтожаем текущую - СЕБЯ
me:Destroy()

Ну, тут могу только прокомментировать, что вместо MoveTo использована SetPrimaryPartCFrame, т.к. MoveTo, как выяснено было ранее, работает не совсем корректно в случае наложения объектов друг на друга, а именно помещает новый над старым.

Как всегда, в конце ссылка на сам проект, где можно протестировать всё описанное в деле:

https://www.roblox.com/games/4789977549/Math-Rocket


Поделиться...

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *