Skip to main content

Урок 2. Вспоминаем Lua в Roblox

Урок 2. Вспоминаем Lua в Roblox
На протяжении модуля, пожалуйста, обращайтесь к документации и показывайте детям, как с ней работать. В этом уроке примеры будут, но в дальнейших - нет.
Итак, сегодня вспомним как и что работает в Roblox Studio. Откроем редактор и создадим пустую карту.
Перед началом - немного донастроимся. Отключите всю генерацию кода и заставьте детей это тоже сделать. Иначе, будете потом ходить и показывать особо внимательным ученикам, где именно расходится сгенерированный код с вашим, и почему у них все не работает
Настройте себе размер шрифта в окне скрипта ctrl+колесико так, чтобы было максимально хорошо видно. Скрывайте у себя ненужные в моменте окна. Дайте ребятам с задних парт увидеть то, что вы показываете
Пройдемся по нужным и основным системам игры:
Workspace
Все физически существующие объекты игры
Players
Все игроки, как соединение и аккауты. Модели игроков находятся в Workspace
ReplicatedFirst
та часть игры, которая копируется первой к клиенту, но не существует на сервере. Например - экран загрузки
ReplicatedStorage
та часть игры, которая должна быть и у сервера, и у клиента. Чаще всего - библиотеки функций для скриптов, триггеры событий
ServerScriptsService
место для всех серверных скриптов, которые не связаны с конкретным объектом
ServerStorage
место для всего шлака, который не находится на карте в момент запуска игры, но тем не менее должен быть в игре
StarterGui
gui, который покажется игроку по загрузке
StarterPack
инвентарь, копирующийся в персонажа игрока при каждом создании персонажа
StarterPlayer
внутри две подпапки - скрипты для игрока и для персонажа
чтобы открыть окно стандартной консоли (снизу) - window->output

Первый скрипт

Вспомним, как создавать предметы. Для любого создания, на самом деле, используется одна и та же функция - Instance. Как она работает? В языке заложено огромное количество различных "объектов" - текстовое сообщения, музыка, элемент графического окна, меш, геометрическая фигура (part), световой луч и тд. Все эти части имеют свои свойства, свои настройки, свой функционал, но все они создаются одинаковым образом. Для этого мы инстанцируем, т.е. создаем копию модели по некому "чертежу", которую потом уже донастраиваем.
Создаем скрипт внутри ServerScriptService
Создадим простейшую вещь - просто Part. Чтобы потом этот парт менять - запишем ссылку на новосозданный объект в переменную.
local currentPart = Instance.new('Part')
Чтобы открыть документацию нажимаем ctrl+f12 или через меню пкм
Откроется браузерное окно с документацией. Как видно из выделенного участка, new создает новый объект и возвращает его как результат выполнения своей функции.
Теперь мы можем изменять и другие параметры. А как посмотреть, что там собственно есть? В документации ищем в поиске Part и открываем. Поскольку Part наследует почти все свои поля из других объектов, то они будут по умолчанию скрыты и их нужно будет раскрыть:
К счастью для нас, почти все поля названы нормально, в смысле что по имени понятно, что находится внутри. К несчастью для большинства детей, все названия - на английском.
Пока что происвоим только два значения - имя (внутриигровое) и где объект находится. По рекомендации, присваивание параметра Parent, которое отвечает за то, где будет находится наш объект, происходит в самом конце:
local currentPart = Instance.new('Part')
currentPart.Name = 'Блок для экспериментов'
currentPart.Parent = workspace
Перейдем к позиции:
Давайте рассмотрим как работать с документацией на примере цвета. Цвет по английски будет color, поэтому ищем что-то с color на странице Part. Справа есть оглавение страницы, но оно показывает только ненаследуемые свойства:
Найдя там Color, нажмем на него. Тут нам покажут, что цвет является типом Color3, который можно создать из чисел разным способом. Конкретно new() создает цвет из трех чисел [0,1], т.е. нормализованный RGB
Собственно поэтому его используют максимально редко. Большинство людей привыкло использовать либо RGB (каждое число [0,255]) либо HSV ( [0,1] , но цвет выбирается на основе оттенка , насыщенности и яркости, а не по rgb), либо HEX - форма записи RGB как шестнадцаритичных чисел. Для этого упражнения возьмем, так уж и быть, new, а то че пылится:
local currentPart = Instance.new('Part')
currentPart.Name = 'Блок для экспериментов'
currentPart.Color = Color3.new( 0.7 , 0.2 , 0.9)
currentPart.Parent = workspace
Дайте детям задание самостоятельно попробовать поменять позицию (position)
Позиция задается как вектор, который можно создать с помощью конструктора new() , указав все три координаты. По соглашению Y-вверх
local currentPart = Instance.new('Part')
currentPart.Name = 'Блок для экспериментов'
currentPart.Color = Color3.new( 0.7 , 0.2 , 0.9)
currentPart.Position = Vector3.new( 10,10, -10)
currentPart.Parent = workspace
Конструкторы могут вместо простого значения получать на вход и вычисления. Разберем это на примере случайного места нашей детальки. Для создания случайного числа используется специальный объект - генератор
Тут же есть и как доставать из него числа:
Не забываем записать ссылку на генератор в переменную. Сидировать его мы не будем.
Создадим случайную позицию по осям X,Z, оставив Y фиксированной:
local currentPart = Instance.new('Part')
currentPart.Name = 'Блок для экспериментов'
currentPart.Color = Color3.new( 0.7 , 0.2 , 0.9)

local random = Random.new()

currentPart.Position = Vector3.new( random:NextInteger(-10 , 10) , 10 , random:NextInteger(-10 , 10) )
currentPart.Parent = workspace
Поскольку получается колбаса - рекомендую разнести каждый параметр на свою строку. К сожалению, как в питоне именовать параметры нельзя
local currentPart = Instance.new('Part')
currentPart.Name = 'Блок для экспериментов'
currentPart.Color = Color3.new( 0.7 , 0.2 , 0.9)

local random = Random.new()

currentPart.Position = Vector3.new( random:NextInteger(-10 , 10) ,
									random:NextInteger(-10 , 10) ,
									random:NextInteger(-10 , 10) )
currentPart.Parent = workspace

Сигналы

Теперь добавим возможность реагировать на сигналы. Например, когда можно спавнить блоки вокруг персонажа? При загрузке этого самого персонажа! Для этого:
1) обернем наш текущий код в одну функцию
local function placeBlock()
	local currentPart = Instance.new('Part')
	currentPart.Name = 'Блок для экспериментов'
	currentPart.Color = Color3.new( 0.7 , 0.2 , 0.9)

	local random = Random.new()

	currentPart.Position = Vector3.new( random:NextInteger(-10 , 10) ,
                                		random:NextInteger(-10 , 10) ,
                                		random:NextInteger(-10 , 10) )
                                		currentPart.Parent = workspace
end
2) добавим сигнал от игрока
Ниже даже есть примеры кода с использованием! Как прекрасно знать английский язык!
3) Создадим функцию, которая срабатывает при добавлении персонажа. Она берет игрока, и для его модели вызывают эту фукнцию:
local function placeBlock()
	local currentPart = Instance.new('Part')
	currentPart.Name = 'Блок для экспериментов'
	currentPart.Color = Color3.new( 0.7 , 0.2 , 0.9)

	local random = Random.new()

	currentPart.Position = Vector3.new( random:NextInteger(-10 , 10) ,
		random:NextInteger(-10 , 10) ,
		random:NextInteger(-10 , 10) )
		currentPart.Parent = workspace
end


local function addBlocksOnCharacter(player)
	player.CharacterAdded:Connect( placeBlock )
end

4) А теперь вызываем эту фукнцию для каждого добавленного игрока
local function placeBlock()
	local currentPart = Instance.new('Part')
	currentPart.Name = 'Блок для экспериментов'
	currentPart.Color = Color3.new( 0.7 , 0.2 , 0.9)

	local random = Random.new()

	currentPart.Position = Vector3.new( random:NextInteger(-10 , 10) ,
		random:NextInteger(-10 , 10) ,
		random:NextInteger(-10 , 10) )
		currentPart.Parent = workspace
end


local function addBlocksOnCharacter( player )
	player.CharacterAdded:Connect( placeBlock )
end

game:GetService('Players').PlayerAdded:Connect( addBlocksOnCharacter )
Теперь при подключении, и не раньше, создается камень. Можно проверить, что создается камень именно при создании персонажа, запустив сервер в режиме Run
Теперь добавим цикличность: пусть спавнится блок каждых 2 секунды. Для этого функцию placeBlock обернем в цикл используем task.wait(s):
local function placeBlock()
	while true do   --бесконечный цикл
		
	local currentPart = Instance.new('Part')
	currentPart.Name = 'Блок для экспериментов'
	currentPart.Color = Color3.new( 0.7 , 0.2 , 0.9)

	local random = Random.new()

	currentPart.Position = Vector3.new( random:NextInteger(-10 , 10) ,
		random:NextInteger(-10 , 10) ,
		random:NextInteger(-10 , 10) )
		currentPart.Parent = workspace
		
	task.wait(2)	--пауза
	end
end


local function addBlocksOnCharacter(player)
	player.CharacterAdded:Connect( placeBlock )
end

game:GetService('Players').PlayerAdded:Connect( addBlocksOnCharacter )
Теперь осталось сделать так, чтобы блоки спавнились вокруг персонажа, а не просто в центре карты. Для этого нам надо передавать персонажа внутрь функции. К счастью, :PlayerAdded автоматически передает ссылку на персонажа, а значит мы можем отлавливать ее внутри нашей функции placeBlock
Сам по себе персонаж не имеет поля позиция. Зато такое поле есть у, например, HumanoidRootPart (это part)
Поскольку Position имеет тип Vector3
то мы можем просто складывать эти два вектора между собой:
local function placeBlock(playerCharacter)
	while true do
		
		local currentPart = Instance.new('Part')
		currentPart.Name = 'Блок для экспериментов'
		currentPart.Color = Color3.new( 0.7 , 0.2 , 0.9)

		local random = Random.new()
		local playerPos = playerCharacter.HumanoidRootPart.Position
		currentPart.Position = Vector3.new( random:NextInteger(-10 , 10) ,
											random:NextInteger(-10 , 10) ,
											random:NextInteger(-10 , 10) ) +playerPos
		currentPart.Parent = workspace
		
	task.wait(2)	
	end
end


local function addBlocksOnCharacter(player)
	player.CharacterAdded:Connect( placeBlock )
end

game:GetService('Players').PlayerAdded:Connect( addBlocksOnCharacter )
На сладенькое - сделаем так, чтобы блоки удалялись после времени.