본문 바로가기

로블록스 개발에 필요한 전반적인 내용들

로블록스의 클라이언트 서버 통신에 대하여 (3)

로블록스내에서 RemoteEvent, RemoteFunction 사용방법

앞선 내용에서 이미 RemoteEvent와 RemoteFunction은 ReplicatedStorage의 자식 오브젝트로 추가하는 방식이 일반적이라고 밝혔다. 그 이유로는 서버와 클라이언트 양쪽에서 접근이 가능해야 하기 때문에, 그 역할에 가장 알맞는 서비스가 ReplicatedStorage 이기 때문이다.

 Remote Event 사용법 

Remote Event 를 사용하기 위해서는 RemoteEvent의 객체를 생성해야 한다. 이 RemoteEvent 객체는 서버와 클라이언트양쪽에서 접근이 가능해야 한다. 즉 ReplicatedStorage의 자식으로 RemoteEvent를 추가한다. ReplicatedStorage를 탐색기 창(Explorer window)에서 찾고 +버튼을 누르고 RemoteEvent 추가. 이름을 RemoteEventTest로 이름을 교체함.

RemoteEventTest라는 이름으로 ReplicatedStorage의 자식으로 추가

클라이언트 -> 서버로

로컬스크립트에서의 처리는 해당 유저에게만 영향을 미친다. 서버 전체에 영향을 미치는 처리를 할려면 클라이언트에서 서버로 이벤트를 보내줘야 한다. 한 유저가 블럭(Part)하나를 생성하면 서버로 블럭 생성 이벤트를 보내서 서버내의 모든 클라이언트들이 그 블럭이 생성 되었음을 알게 된다.

-- 이 코드는 로컬스크립트에서 구현한다.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
 
 -- RemoteEventTest 를 찾아서 복제.
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEventTest")
 
-- 서버로 해당 리모트 이벤트를 전달(Fire)
-- remoteEvent:FireServer()
-- 덧붙혀서, 서버에 리모트 이벤트 전달할 때, 필요한 정보를 담아서 보낼 수도 있다.
remoteEvent:FireServer(BrickColor.Red(), Vector3.new(0, 25, 0))

 

-- 이 코드는 서버스크립트로 구현한다.(ServerScriptService에 Script추가)

local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- 서버 스크립트에서도 같은 이벤트 객체를 생성
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEventTest")
 
-- 새 블럭(Part) 생성 (서버에서 블럭을 생성)
local function onCreatePart(player, partColor, partPos)
    print(player.Name .. " fired the remote event")
    local newPart = Instance.new("Part")
    newPart.BrickColor = partColor
    newPart.Position = partPos
    newPart.Parent = workspace
end
 
-- RemoteEvent 로 연결. 클라이언트의 FireServer() 에 반응하게 됨.
remoteEvent.OnServerEvent:Connect(onCreatePart)
서버 스크립트의 연결 함수 onCreatePart의 파라메터로 player 를 주목. 클라이언트에서 FireServer()에서 보낸 적이 없는 player가 1번 파라메터로 전달됨. 함수를 직접 호출한 것이 아니라 로블록스내에서 이벤트 처리 과정에 추가 한 것임.

서버 -> 클라이언트로

서버에서 클라이언트에 이벤트를 날리는 방식은 두가지이다. 특정 클라이언트에게만 보내는 방식과 모든 클라이언트들에게 보내는 방식. 어느 경우든지 ReplicatedStorage에 RemoteEvent를 추가하고 시작한다.

 

특정 클라이언트에만 보내는 방법

-- 서버 스크립트에서 구현함

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
 
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEventTest")
 
local function onPlayerAdded(player)
    -- 추가된 플레이어에게 리모트 이벤트를 날림.
    -- 관련 player 를 처음 파라메터로 지정.
    -- MaxPlayers, RespawnTime은 추가로 커스텀하게 파라메터가 된다.
    remoteEvent:FireClient(player, Players.MaxPlayers, Players.RespawnTime)
end

-- Players 서비스를 해서 플레이어가 접속할 때 호출될 함수 연결.
Players.PlayerAdded:Connect(onPlayerAdded)

 

-- 이 코드는 로컬 스크립트에서 구현 

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEventTest")
 
-- 추가로 보낸 maxPlaysers와 respawnTime을 파라메터로 받음.
local function onNotifyPlayer(maxPlayers, respawnTime)
    print("Incoming server event...")
    print(maxPlayers, respawnTime)
end
 
-- 서버로 부터 remoteEvent가 오면 호출할 함수 연결
remoteEvent.OnClientEvent:Connect(onNotifyPlayer)

게임내 모든 클라이언트에게 보내는 방법

-- 이 코드는 서버 스크립트에서 구현

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEventTest")
 
local secondsRemaining = 5
 
-- 타이머가 끝날 때까지 루프.
for t = secondsRemaining, 1, -1 do
    -- 모든 클라이언트에게 남은 시간을 전달
    remoteEvent:FireAllClients(t)
    -- 1초 기디림.
    wait(1)
end

 

-- 이코드는 로컬 스크립트에서 구현

local ReplicatedStorage = game:GetService("ReplicatedStorage") 
local remoteEvent = ReplicatedStorage:WaitForChild("RemoteEventTest")
 
-- 서버에서 모든 클라이언트들에게 타이머가 끝날때까지 매초 불려지게 됨.
local function onTimerUpdate(seconds)
    print(seconds)
end
 
-- 이벤트에 함수 연결.
remoteEvent.OnClientEvent:Connect(onTimerUpdate)
FireAllClients() 함수와 onTimerUpdate() 함수의 파라메터에 주목. 해당 이벤트를 발생시킨 원인 객체의 전달이 없다. 모든 클라이언트에게 보내는 이벤트이므로 특별히 필요하지 않은 모습. 대신 커스텀하게 원하는 파라메터를 전달 가능.

클라이언트 -> 클라이언트로

클라이언트간의 통신은 서버-클라이언트 모델의 특성상 직접 통신이 불가능함. 꼭 필요한 경우 서버를 거치게 설계하면 된다. 위의 클라이언트->서버 방식과 서버->클라이언트 방식을 연결하면 된다.

 Remote Fuction 사용법 

애초에 설명한대로, RemoteFunction은 서버-클라이언트간에 요청을 보내고 그 반환값을 기다리는 방식으로 설계되어 있다. 다시 한번 말하지만, RemoteEvent에 비해 리소스를 많이 잡아먹기 때문에 꼭 리모트함수를 써야 되는지 생각해보고 사용하자.

리모트함수는 RemoteFunction의 객체를 이용한다. 이것 역시 서버와 클라이언트 양쪽에서 접근해야하므로 ReplicatedStorage의 자식으로 사용하면 좋다. RemoteEvent와 마찬가지로 ReplicatedStorage를 탐색기 창(Explorer window)에서 찾고 +버튼을 누르고 RemoteFunction 추가. 이름을 RemoteFunctionTest로 이름을 교체함.

RemoteFunctionTest라는 이름으로 ReplicatedStorage의 자식으로 추가

클라이언트 -> 서버

클라이언트 스크립트에서의 리모트함수는 서버가 뭔가 해주길 원하고, 플러스, 그 결과를 알기을 원할 때 사용(invoke)된다. 간단한 예로 클라이언트에서 서버에게 블록을 하나 생성하게 하고 그 결과를 받는 코드로 알아보자.

-- 이 코드는 서버 스크립트임.

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

-- 블럭(Part) 생성하고 그 블럭을 반환.
local function createPart(player, partColor, partPos)
    print(player.Name .. " requested a new part")
    local newPart = Instance.new("Part")
    -- Use the "partColor" and "partPos" parameters to set the brick color/position
    newPart.BrickColor = partColor
    newPart.Position = partPos
    newPart.Parent = workspace
    return newPart
end
 
-- createPart()함수와 OnServerInvoke을 연결 시킴.
remoteFunction.OnServerInvoke = createPart

 

-- 이 코드는 로컬 스크립트임

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remoteFunction = ReplicatedStorage:WaitForChild("RemoteFunctionTest")
 
-- 리모트 함수를 호출(invoke), 결과 값을 받는 점을 주목.
local newPart = remoteFunction:InvokeServer(BrickColor.Red(), Vector3.new(0, 25, 0))
print("The server created the requested part:", newPart)
파라메터의 주고 받음은 리모트이벤트와 비슷.

서버 -> 클라이언트 

서버에서도 클라이언트에게 리모트함수를 호출(InvokeClient())할 수도 있다. 다만, 앞서 기술했듯이 리모트 함수는 동기식이다. 결과값이 올때까지 기다린다는 말이다. 즉 서버가 특정 클라이언트에게 리모트함수를 호출하면 그 결과 값을 받을때까지 서버는 기다리게 된다. 경우에 따라 서버가 멈춰버리는 현상을 겪게된다.

결론적으로 서버에서 클라이언트에게 뭔가 알려주기 위함이라면, 그냥 리모트 이벤트를 사용하자. 

 리모트 이벤트와 리모트 함수에 쓰여지는 커스텀 파라메터의 제한사항 

숫자, 스트링, boolean, 테이블을 포함하여 로블록스의 어떤 객체, 예를 들어 Enumeration, 인스턴스등등 모두 파라메터로 사용이 가능하다. 다만, 혼합 테이블, 스트링이 아닌 인덱스를 가진 테이블, 메타테이블, 그리고 Non-Replicated 인스턴스를 사용시에는 원하는 값이 제대로 전달되지 않으므로 조심해야 한다.

  • Mixed Table : 그냥 인덱스 타입과 값 타입이 같은걸 쓰자. 스트링형 + 숫자형 인덱스를 보내면 숫자형 인덱스 값만 보내진다던가, 서브 테이블은 또 괜찮다던가... 뭐... 자세한건 도큐먼트 찾아 보면 되는데, 그냥 같은 타입으로 쓰자.
  • 스트링형이 아닌 인덱스 : 애초에 문자형, 숫자형이 아닌 인덱스를 쓰지 않는 테이블... 을 생각해 본적이 없는데, lua 는 이게 가능한가 보다.  나는 쓸 일도 없으니 걱정이 없지만, 쓰시던 사람들은 조심하시길.
  • 메타테이블 : ... 그냥 테이블만 보내자
  • Non-Replicated Instance : ReplicatedStorage 의 자식 인스턴스라면 서버 - 클라이언트에서 모두 접근이 가능하므로, 보낼 수 있지만, 서버 - 클라이언트 모두에서 복제 가능한 인스턴스가 아니면 보내면 nil 이 됨.

정리

로블록스는 기본 서버-클라이언트 모델을 사용함.

서버와 클라이언트의 통신에는 리모트이벤트와 리모트함수를 사용함.

리모트이벤트는 비동식으로 한쪽에 메세지를 전달하고 넘어가지만, 리모트 함수의 경우는 결과값을 기다리는 동기식이므로 리소스를 많이 잡아먹음.