로블록스 루아 스크립트 고급

정선호·2024년 4월 2일
0

Roblox-Lua

목록 보기
3/3

강의 재생목록

C계열 언어와 유니티를 익히고, 기초 강좌중급 강좌를 공부한 사람 기준으로 작성했습니다.


01.RenderStepped, update

한 프레임이 넘어갈 때마가 호출되는 이벤트로 로컬 스크립트에서만 사용할 수 있다.
이전의 무한 반복문으로 작성했던 지속적인 함수들을 RenderStepped 이벤트를 이용해 변경해주면 부드러운 움직임 등을 얻을 수 있다.

local RunService = game:GetService("RunService")

function update(step)
 script.Parent.Rotation += 60 * step
end

RunService.RenderStepped:Connect(update)

RenderStepped 이벤트에는 deltaTime매개변수가 입력된다. 매개변수명은 주로 step을 사용한다.
deltaTime은 한 프레임 사이에 몇 초가 지났느냐를 뜻한다.


02.RenderStepped, Stepped, Heartbeat

Stepped, Heartbeat는 서버 스크립트에서도 사용할 수 있는 함수이다.

플레이어 키보드 입력 감지 - 서버 맵 클라이언트로 복사 - 캐릭터 애니메이션 업데이트 - 물리엔진 게산 등등 한 프레임 안에서도 작업들이 순서대로 실행된다.

로컬 스크립트에서 RenderStepped는 캐릭터 조작, 물리, 카메라 등이 계산되기 전에 호출되는 함수이다. 따라서 캐릭터 조작, 물리, 카메라 등에 간섭하기 위해서는 RenderStepped함수를 사용해야 한다.

Stepped는 물리엔진이 계산되기 전에 호출되는 함수이다. 따라서 서버에 존재하는 파트나 모델에서 물리에 간섭하기 위해서는 Stepped함수를 사용해야 한다.
대강 RenderStepped -> 플레이어 캐릭터/카메라 -> Stepped -> 물리엔진 순서로 호출된다.

RenderStepped는 캐릭터/파트/모델의 위치에 영향을 주는 함수를 이벤트에 바인딩해 사용하고, Stepped, Heartbeat 영향을 받는 함수를 이벤트에 바인딩해 사용한다.

local part = workspace.Part
local RunService = game:GetService("RunService")
function update(step)
 part.CFrame = part.CFrame * CFrame.Angles(0,math.rad(180) * step ,0)
end
RunService.Stepped:Connect(update)

Heartbeat는 주로 GUI에 사용한다.


BindToRenderStep, RenderStepped 주의사항

BindToRenderStep

BindToRenderStep함수는 RenderStepped와 같이 로컬 스크립트에서 사용할 수 있는 이벤트 함수이지만, RenderStepped와 다르게 호출 순서를 개발자가 지정할 수 있다.
또한 UnbindFromRenderStep()함수로 바인드한 이벤트를 해제할 수 있다.

local RunService = game:GetService("RunService")

local a= 0
function update(step)
   a += 1
   script.Parent.Rotation += 60 * step
end

RunService:BindToRenderStep("Bind", 298, update)

RunService:UnbindFromRenderStep("Bind")

로블록스 내 시스템 함수 호출 순서는 Enum.RenderPriority에 선언되어 있다.
자세한 사항은 디벨로퍼 허브에서 확인할 수 있다.

RenderStepped 함수들의 주의사항

  • BindToRenderStep함수들에는 이미 스케줄된 호출 순서를 사용하면 안 된다.
  • UnbindFromRenderStep에 바인드하지 않은 함수 혹은 이미 언바인드 된 함수를 입력하면 오류가 발생한다.
  • RenderStepped계열 함수에 이벤트를 바인드할 때 이벤트 안에 Wait()와 같이 스크립트를 대기시키는 기능을 추가하면 안 된다. 로블록스의 API 문서에서 Yielding Function이라 지정된 함수들은 모두 스크립트를 대기시키는 함수들이다.

04.모듈 스크립트 기초

모듈 스크립트는 여러 곳에서 사용하는 함수나 변수들을 저장해주는 스크립트이다.
'개체 삽입' 기능을 이용해 ModuleScript를 추가하여 생성할 수 있다.

모듈 스크립트의 기본 형식은 다음과 같다. 모듈 이름과 모듈 스크립트 파일 이름을 동일하게 해주는 것이 정리에 이롭다.

local module = {}

return module

킬 파트를 모듈 스크립트화 한 예시는 다음과 같다.

local KillPartHandler = {}

-- 킬 파트를 활성화/비활성화 해주는 변수
KillPartHandler.Enabled = true

-- 특정 파트가 들어오면 그 파트의 캐릭터를 찾아 kill하는 함수
function KillPartHandler.KillCharacterFromPart(hit)
   local Humanoid = hit.Parent:FindFirstChild("Humanoid")
   if Humanoid and KillPartHandler.Enabled then
      Humanoid.Health = 0
   end
end

return KillPartHandler

모듈 스크립트를 불러오는 함수는 require()이다.
킬 파트를 사용하는 스크립트와 제한하는 스크립트 예시는 다음과 같다.

--킬파트 스크립트
local KillPartHandler = require(workspace.KiilPartHandler)

script.Parent.Touched:Connect(function(hit)
 KillPartHandler.KillCharacterFromPart(hit)
end)

--킬파트 끄는 스크립트
local KillPartHandler = require(workspace.KiilPartHandler)

script.Parent.Touched:Connect(function(hit)
 KillPartHandler.Enabled = false
end)

05.모듈 스크립트 기초2

모듈 스크립트는 스스로 실행되지 않고, 서버 혹은 로컬 스크립트에서 require로 불러 실행시켜줘야 작동한다.
모듈 스크립트는 서버와 클라이언트 각각에서 따로 돌아간다. 따라서 같은 모듈 스크립트를 불러와도 서버 스크립트와 로컬 스크립트 내에서 모듈 스크립트의 변수는 공유되지 않는다.
따라서 모듈 스크립트는 서버에서, 혹은 로컬에서 호출되었을 때를 분리해서 구현하게 된다.

local module = {}

local RunSevice = game.GetService("RunService")

RunSevice:IsStudio() -- 로블록스 스튜디오에서 작동중인지

function module.func()
	if RunSevice:IsServer() then
		-- 서버 작동 스크립트
	else
		-- 클라이언트 작동 스크립트
	end
end

return module

모듈 스크립트에서도 이벤트를 설정할 수 있다. 단 다른 스크립트에서 모듈 스크립트를 불러올 때에만 사용 가능하다.

-- 모듈 스크립트
local module = {}

workspace.BasePlate.Touched:Connect(function()
	print("BasePlate")
end)

return module
-- 모듈 스크립트 호출
require(workspace.ModuleScript)

모듈에서 실질적으로 사용하는 함수가 하나밖에 없을 때에는 모듈 선언을 생략할 수 있다.

-- 모듈 스크립트
function f()
    print("function")
end

return f
-- 모듈 스크립트 사용
local name = require(workspace.ModuleScript)
name.f()

모듈 스크립트를 '우클릭 - 로블록스에 저장'을 이용해 도구 상자에 업로드하고, 도구 상자에서 생성된 모듈ID를 이용해 모듈 스크립트를 불러올 수 있다.
이를 이용해 바이러스 공격에 대한 보안과 모듈 배포의 용이함을 챙길 수 있다.

모듈 스크립트는 주로 'ReplicatedStorage'와 'ServerScriptService'폴더에 저장해 두는데, 로컬과 서버 스크립트에서 모두 호출할 때에는 'ReplicatedStorage'에, 서버 스크립트에서만 호출할 때에는 'ServerScriptService'폴더에 저장한다.


06.Magnitude, Unit

Magnitude

두 포지션 사이의 거리를 구할 때 Magnitude를 사용해 구할 수 있다.

local pos = workspace.red.Position - workspace.blue.Position
print(pos.Magnitude)

Unit

파트의 크기를 1로 정규화하려할 때 Unit을 사용해 구할 수 있다.


workspace.Part11.Size = workspace.Part1.Size.Unit
workspace.Part22.Size = workspace.Part2.Size.Unit
workspace.Part33.Size = workspace.Part3.Size.Unit

print(workspace.Part11.Size.Magnitude) -- 1 출력
print(workspace.Part22.Size.Magnitude) -- 1 출력
print(workspace.Part33.Size.Magnitude) -- 1 출력

07. pcall

pcall()함수는 pcall함수로 감싼 안쪽에서 에러가 발생해도 바깥쪽에 영향을 주지 않도록 하는 함수이다.
단 스크립트 문법 오류시에는 pcall함수에서도 에러가 발생한다.

print(111111111)

--이렇게 쓰면 에러나겠지?
script.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent.Parent
 = script

print(222222222)

또한 pcall함수는 다음과 같은 변수들을 반환하여 어떠한 에러가 발생하였는지 알려주는 기능 또한 존재한다.

  • 성공 여부(bool)
  • 에러 메세지(string)
success, errorMessage = pcall(function()
    script.Parent.Parent.Parent.Parent.Parent.Parent.Parent = script
end)
if not success then
    print(errorMessage)
end

pcall 함수 외부에서 함수를 선언한 후에 pcall의 매개변수로 함수의 이름을 작성해 사용할 수 있다. 이 때 함수의 매개변수는 pcall의 다음 매개변수로 추가해주면 된다.

-- print("aaaaaa", "bbbbbb")를 pcall에 추가한 상황
success, errorMessage = pcall(print, "aaaaaa", "bbbbbb")
if not success then
   print(errorMessage)
end

xpcall(함수1, 에러 담당 함수, ...)함수는 함수1에서 오류가 발생하면 에러 담당 함수가 대신 호출되는 형식의 pcall 함수이다.

function HandleError(errorMessage)
   print(errorMessage)
end
-- print함수가 실패하면 HandleError 함수가 실행된다
xpcall(print, HandleError, "aaaaaa", "bbbbbb")

pcall함수는 주로 반복적인 데이터 저장에서 오류를 방지하는 데에 사용한다.
하지만 최적화가 되어있지 않기 때문에 상황에 따라 사용에 유의해야 한다.

local DataStore = game:GetService("DataStoreService"):GetDataStore("MyData")
local success, errorMessage
local count = 1 

repeat
   if count == 1 then
      warn("재시도 횟수:", count, " / 에러:", errorMessage)
      wait(7) 
   end

   success, errorMessage = pcall(DataStore.GetAsync, DataStore, "key")
   count = count + 1 
until success

08-09.코루틴

코루틴은 한 스크립트 안에서 동시에 여러 동작을 수행하게 만들고 싶을 때 사용한다.

코루틴을 생성할 때에는 coroutine.create(실행시킬 함수명)함수를
코루틴을 실행시킬 때에는 coroutine.resume(실행시킬 코루틴, 함수의 매개변수...)를 사용한다.
혹은 coroutine.wrap(실행시킬 함수명)으로 일반 함수를 코루틴 함수로 래핑해 사용할 수도 있다.

한번 코루틴을 수행한 함수는 다시 재활용할 수 없다.

코루틴을 수행한 함수 또한 값을 반환할 수 있다. 이 때 코루틴 내에서 wait와 같은 대기 함수를 사용하면 값 반환이 수행되지 않는다.
coroutine.resume()함수는 pcall함수와 비슷하게 첫 번째로 코루틴의 에러 메시지가, 그 이후로 코루틴 함수의 반환값이 반환된다.

local function ChangeColor(part,p,d)
   for i=1, 100 do
      part.BrickColor = BrickColor.random()
   end
   return part
end

local c1 = coroutine.wrap(ChangeColor)
local success1, result1 = coroutine.resume(c1, workspace.Left)

local c2 = coroutine.create(ChangeColor)
local success2, result2 = coroutine.resume(c2, workspace.Middle)

local c3 = coroutine.wrap(ChangeColor)
local result3 = c3(workspace.Left)

coroutine.resume()으로 감싼 코루틴 함수는 사용하는 중간에 coroutine.yield()로 일시정지 시킬 수 있다. 일시정지시킨 코루틴은 다시 coroutine.resume()으로 재가동시킬 수 있다.

local function ChangeColor(part)
    while true do
        for i=1, 100 do
            wait()
            part.BrickColor = BrickColor.random()

        end
        coroutine.yield()
    end
    return part
end

local c2 = coroutine.create(ChangeColor)
coroutine.resume(c2, workspace.Middle)
coroutine.resume(c2, workspace.Middle)

coroutine.status()함수를 이용해 현재 코루틴 함수의 상황을 확인할 수 있다. 코루틴의 상황종류는 다음과 같다.


10.스레드, spawn()

스레드

스레드는 한 번에 실행되는 코드의 흐름 단위라 생각하면 편하다.
wait()함수를 사용하면 사용한 스레드만을 대기시키는 함수이고, error가 발생해도 스레드 안에서만 발생하므로 다른 스레드에서 실행되는 함수들은 정상 작동한다.

spawn()

무한 반복문을 스레드를 나누어 사용하고 싶을 때 사용하는 함수이다. 코루틴보다 성능이 좋지 않아 코루틴으로 대체되었다.

function repeatChangeColor(a)
    print(a)
    while true do
        script.Parent.BrickColor = BrickColor.random()
        wait(0.2)
    end
end

spawn(function()
    repeatChangeColor("a")
end)

print("aaaa")

11.CFrame.Lerp

Lerp를 사용해 두 CFrame의 보간을 적용할 수 있다.

local RedPart = workspace.RedPart
local BluePart = workspace.BluePart

local Part = workspace.Part

-- BluePart와 RedPart의 사이 정 중앙에 Part를 위치
Part.CFrame = RedPart.CFrame:Lerp(BluePart.CFrame, 0.5)

-- Part가 BluePart를 따라다니게 만들기
while wait() do
 Part.CFrame = Part.CFrame:Lerp(BluePart.CFrame, 0.25)
end

이를 이용해 구현된 마우스를 따라 움직이는 머리 스크립트는 다음과 같다.


12.Bindable/Remote Function

Bindable Function

Bindable Function은 어떠한 스크립트에서 생성된 함수를 다른 스크립트에서 실행 가능하게 해주는 것은 동일하지만, Event와 다르게 매개변수 제공 및 반환값 획득이 가능하다.
로블록스에서 탐색기에 Bindable Function을 추가하여 생성할 수 있다.

ServerStorage에 Bindable Function을 추가하고 A스크립트 바인더블 실행, B스크립트에서 바인더블 할당을 하는 예시는 다음과 같다.

  • A스크립트
local BF = game.ServerStorage.BindableFunction

-- 플레이어가 터치할 시
script.Parent.Touched:Connect(function()
 -- 바인더블 함수 실행 후 RandomColor 변수에 리턴 값 저장
 local RandomColor = BF:Invoke()
 wait(2)
 script.Parent.BrickColor = RandomColor
end)
  • B스크립트
-- 파트의 색 변경 후 바꾼 색을 리턴값으로 반환
local function ChangeRandomColor()
 local RandomColor = BrickColor.random()
 script.Parent.BrickColor = RandomColor
 return RandomColor
end

-- 바인더블 함수 연결
local BF = game.ServerStorage.BindableFunction
BF.OnInvoke = ChangeRandomColor

바인더블 함수는 바인드시킨 일반 함수에 오류가 있을 시 리턴받는 스크립트에도 오류가 발생한다. 또한 이벤트보다 처리속도가 느리다.

또한 바인더블 함수는 FireAllClients()함수로 모든 서버 내 파트에게 적용된 바인더블 함수를 트리거시킬 수 있다.

Remote Function

바인더블 펑션의 사촌지간으로 탐색기에 Remote Function을 추가하여 생성할 수 있다.
로컬/서버 간 연결이 가능하여 Replicated Storage에 주로 만든다.

리모트 펑션은 로컬->서버 전송과 서버->로컬 전송 시 스크립트 작성 방식이 다르다.

  • 로컬에서 서버로 전송할 때
    • InvokeServer(매개변수)를 사용해 이벤트를 호출한다.
    • 서버에서 이벤트를 바인드할 때 첫 번째 매개변수로는 호출한 플레이어의 정보가 들어온다.
-- 로컬 스크립트에서 이벤트 호출
local RF = game.ReplicatedStorage.RemoteFunction

script.Parent.Activated:Connect(function()
 local Name = RF:InvokeServer("aaaaa")
 script.Parent.Text = Name
end)

-----------

-- 서버 스크립트에서 이벤트 바인드
local RF = game.ReplicatedStorage.RemoteFunction
RF.OnServerInvoke = function(player, aa)
 script.Parent.BrickColor = BrickColor.random()
 return script.Parent.BrickColor.Name
end
  • 서버에서 로컬로 전송할 때
    • InvokeClient(대상 플레이어, 매개변수...)를 사용해 특정 플레이어에 함수를 발동시킨다.
-- 서버 스크립트에서 이벤트 호출
local RF = game.ReplicatedStorage.RemoteFunction
local player = game.Players.PlayerAdded:Wait()
wait(3)
RF:InvokeClient(player, "aaaa", "bbbb")

-----------

-- 로컬 스크립트에서 이벤트 바인드
local RF = game.ReplicatedStorage.RemoteFunction
RF.OnClientInvoke = function(aaa, bbb)
 script.Parent.BackgroundColor = Color3.new(0.403922, 1, 1)
end

바인더블/리모트 펑션 모두 다음과 같은 매개변수는 제한된다.

  • 배열과 딕셔너리가 혼합된 테이블은 배열만 매개변수로 적용
  • 딕셔너리는 문자열 키만을 가진 딕셔너리만 매개변수로 적용
  • 메타테이블은 전달이 불가능


13.문자열 함수, string 라이브러리

  • tostring() : 숫자를 집어넣으면 문자열을 반환

  • tonumber() : 문자열을 집어넣으면 숫자를 반환, 숫자가 아닌 문자가 끼어 있을 시 nil 반환

  • 백슬래시를 이용해 문자열 안에 따옴표와 같은 특수 기호들을 넣을 수 있다." \"abc\" "

    • 큰따옴표, 작은따옴표, 대괄호, 백슬래시 등등
  • string.char(아스키 코드...) : 아스키 코드들을 집어넣고 문자열로 반환된 값을 얻을 수 있다.

  • string.len(문자열) : 문자열의 길이 반환

  • string.lower(문자열) : 문자열 내 대문자들을 모두 소문자로 바꿔줌

  • string.upper(문자열) : 문자열 내 소문자들을 모두 대문자로 바꿔줌

  • string.reverse(문자열) : 문자열을 뒤집음 (문자열 -> 열자문)

  • string.split(문자열, 기준 문자) : 기준 문자를 기준으로 문자열을 분할시킨다.

str = string.split("가격:500", ":")
print(str[1], str[2]) -- "가격", "500"
  • string.find(문자열, 찾을 문자열, 찾기 시작할 위치) : 찾을 문자열이 문자열의 몇 번째 위치에 있는지를 반환, 못 찾을시 nil 반환

  • string.match(문자열, 찾을 문자열, 찾기 시작할 위치) : 문자열 내의 찾을 문자열을 반환, 못 찾을시 nil 반환

  • string.sub(문자열, 시작위치, 마지막위치) : 문자열을 시작 위치부터 마지막 위치까지만 따로 나누어 반환

  • string.gsub(문자열, 변경 전 문자열, 변경 후 문자열) : 문자열 내의 변경 전 문자열들을 변경 후 문자열들로 바꾼 후 반환


14.문자열 포매팅, 한글 조사 처리

문자열 중간 삽입, 번역 등의 이슈로 문자열의 포매팅을 수행해야 할 때가 있다.
다음과 같이 특수기호들을 문자열 중간에 적은 후 string.format을 이용해 문자열을 합친다.

itemName = "사과"
message = "아이템 %s를 손에 넣었다!"

print(string.format(message, itemName))

한글 조사 처리를 수행하는 스크립트는 다음과 같다.

local function HasBatchim(munja)
	local lastChar = string.sub(munja, -3,-1)
	local charCode = utf8.codepoint(lastChar)
	if charCode < 44032 or charCode > 55203 then 
		warn(munja.."의"..lastChar..": 완성형 한글이 아님")
		return nil
	end
	if (charCode - 44032) % 28 == 0 then
		return false
	else
		return true
	end
end

local function josa(munja)
	if HasBatchim(munja) then
		return "을"
	else 
		return "를"
	end
end

function ShowMessage(itemName)
	local message = "%s%s 손에 넣었다!"
	print( string.format(message, itemName, josa(itemName)) )
end

ShowMessage("사화")
ShowMessage("귤귤")
ShowMessage("asdasdqㅂㅈ오면왐농ㄴㅇㅇㅇ")

15.숫자 포매팅, 타이머 표시

중괄호를 이용해 문자열 포메팅이 가능하다.

local number = 3
local itemName = "배"
local s = `{itemName} {number}개를 먹었습니다.`
print(s) -- "배 3개를 먹었습니다." 출력


16.테이블 복사, 다차원 테이블

일반적으로 문자, 숫자 등은 깊은 복사로 값을 옮기지만, 테이블, 파트 등은 얕은 복사로 레퍼런스를 옮긴다.

local a = 3
local b = a

print(a, b + 1) -- 3, 4 출력

local c = {1, 2}
local d = c
table.insert(d, 3)
print(c, d) -- {1, 2, 3}, {1, 2, 3} 출력

따라서 테이블을 깊은 복사를 하기 위해서는 따로 테이블 값들을 일일히 넣어주는 함수를 만들어야 한다.
다음은 재귀함수를 이용해 다차원 테이블을 깊은 복사해주는 함수의 예시이다.

function cloneTable(t)
	local c = {}
    for i, v in pairs(t)do
    	if type(v) == "table" then
        	c[i] = cloneTable(v)
        else
        	c[i] = v
        end
    end
    return c
end

local a = {{r = 0, l = 1}, {r = 2, l = 3}, 3}
local b = cloneTable(a)
profile
학습한 내용을 빠르게 다시 찾기 위한 저장소

0개의 댓글