본문 바로가기

로블록스 개발 기초

로블록스 코딩 - UI로 점수 보여주기(3/3)

플레이어의 Died 이벤트

테스트를 해보면 알 수 있겠지만, 게임을 시작하고 죽어도 점수의 증가는 멈추지 않는다. 아직까지의 서버의 소스 코드는 1초 단위로 각 플레이어의 점수를 계속 1씩 증가시키고 있을 뿐이기 때문이다. 따라서 플레이어의 죽음을 알아 내서 죽고 나서는 더 이상 점수를 늘려주지 않는 코드를 넣어줘야 한다.

앞선 용암 튜토리얼에서도 다뤘던 Player 객체에서 캐릭터 모델에 이은 휴머노이드 객체을 찾는 과정이 필요한다. 이 휴머노이드 객체의  Died 이벤트를 통해서 플레이어가 죽었음을 알아 낼 수 있기 때문이다. 결국은 Died 이벤트에 연결하기 위한 과정임을 잊지 말자.

local Players = game:GetService("Players")
-- 캐릭터가 게임 입장했을때 불려질 함수
local function onCharacterAdded(character, player)
end

-- 플레이어가 게임 입장했을때 불려질 함수
local function onPlayerAdded(player)
    local leaderstats = Instance.new("Folder")
    leaderstats.Name = "leaderstats"
    leaderstats.Parent = player
	
    local points = Instance.new("IntValue")
    points.Name = "Points"
    points.Value = 0
    points.Parent = leaderstats
    -- player 객체의 CharacterAdded 이벤트에 연결된 Anonymous함수
    player.CharacterAdded:Connect(function(character)
        onCharacterAdded(character, player)
    end)
end

player 객체의 CharacterAdded 이벤트과 연결한 함수는 함수의 이름이 없이 바로 필요한 곳에서 선언을 하는 Anonymous 함수의 형태를 띄고 있다. 이 Anonymous 함수는 굳이 따로 함수를 만들 필요가 없는 상황에서 쓰인다. 재 활용을 생각할 필요도 없고 함수명에 대한 고민도 필요없다.

이 경우에는 onCharacterAdded() 함수를 바로 호출시키는 역할만 한다.

플레이어가 죽었는지 알아내기

캐릭터 모델을 찾아냈으니 다음은 휴머노이드 객체차례이다. 이 휴머노이드 객체에서 Died 이벤트를 통해서 죽는 순간을 알아낼 수 있기 때문이다. 플레이어가 죽으면, 점수를 0으로 리셋시키고 시간에 상관없이 더이상 점수를 증가시키지 말아야 한다. 

앞선 용암 튜토리얼에서는 휴머노이드 객체를 찾기 위해서 FindFirstChild() 함수를 호출하여 찾아내었다. FindFirstChild() 함수를 통해서 휴머노이드 객체를 찾고 Health를 0으로 만들어서 플레이어를 게임오버 시켰었다. 그 경우는 게임이 진행중이었고 플레이를 하고 있다는 것이 전제된 상황이었기 때문에 FindFirstChild()를 써도 괜찮았다.

하지만, 이번에는 WaitForChild() 함수를 사용할 것이다. player 객체의 캐릭터 모델은 플레이어가 스폰이 된 후에 차례로 구성 요소들이 로드가 된다. 속도가 좀 느린 컴퓨터에서 확인해보면 캐릭터의 생성이 팔 다리 몸통... 하나씩 생성됨을 볼 수 있다. 즉, 플레이어를 스폰한 후 캐릭터 모델을 구성하게 되므로 휴머노이드 객체가 생성되었는지 아직인지는 현 상황에서는 알 수가 없다. 그래서 휴머노이드 객체가 생성될 때까지 기다리고 반환하는 WaitForChild() 함수를 사용한다. 

local function onCharacterAdded(character, player)
    local humanoid = character:WaitForChild("Humanoid")
end

플레이어가 죽으면 휴머노이드 객체에서는 자동으로 Died 이벤트를 발생시킨다.  이번에도 Anonymous 함수를 Died 이벤트에 연결하고 해당 player객체의 leaderstats폴더의 Points를 리셋시키면 된다.

local Players = game:GetService("Players")
 
local function onCharacterAdded(character, player)
    local humanoid = character:WaitForChild("Humanoid")
    humanoid.Died:Connect(function()
        local points = player.leaderstats.Points
        points.Value = 0
    end)
end

여기까지하면 플레이어가 죽으면 점수가 0으로 리셋되는 것까지 확인할 수 있다. 물론 아직 끝난 것은 아니다. 플레이어가 죽어서 점수가 0으로 리셋이 되긴 했지만, 죽었음에도 1초마다 점수가 1씩 늘어나는 현상은 바람직하지 않기 때문이다. 플레이어가 죽었으면 0으로 리셋되고, 더이상 점수가 늘어나면 안된다.

플레이어 상태 체크하기

플레이어가 죽었음을 알아내서 점수를 0으로 리셋했으니, 이제 해당 플레이어가 되살아나기 전까지는 점수를 증가시키는 코드를 실행시키면 안 된다. 서서히 사라지는 블럭 튜토리얼에서는 연속 실행을 방지하기 위해서 변수를 하나 사용하였다. 이 케이스에서도 플레이어가 죽었는지 살아 있는지를 확인하기 위해서 새로운 변수를 사용하면 될 것이다.

다만, 다시 한번 언급하지만, 이 소스 코드는 서버의 소스코드이고 플레이어가 하나가 아니라 여러개일 것을 가정하고 만들어야 하므로 여러개의 변수가 필요하게 될 것이다. 정확하게는 플레이어의 수만큼의 변수가 필요하다. 해당 플레이어가 죽었는지 살았는지의 값을 가지고 있어야 하므로.

lua언어의 또다른 특징인 객체에 attribute(속성)를 추가하는 방법을 사용할 것이다. player객체에 SetAttribute() 함수를 사용하여 IsAlive라는 attribute를 끼어 넣을 수 있게 된다. 이런 방법은 다른 언어에서는 좀처럼 보기 힘든 lua 언어의 특징적인 방법이다.

속성이라고만 표현하면 property와 attribute가 헤깔릴 수 있으므로 여기서는 영어를 그대로 사용함
local function onPlayerAdded(player)
    local leaderstats = Instance.new("Folder")
    leaderstats.Name = "leaderstats"
    leaderstats.Parent = player
    
    local points = Instance.new("IntValue")
    points.Name = "Points"
    points.Value = 0
    points.Parent = leaderstats
    -- 플레이어가 스폰되는 과정이므로 player 객체에 IsAlive속성을 추가하고 false로 셋팅한다.
    player:SetAttribute("IsAlive", false)
    
    player.CharacterAdded:Connect(function(character)
        onCharacterAdded(character, player)
    end)
end

위의 코드에서는 플레이어의 캐릭터 모델이 완전히 로드가 된 상황이 아니므로 일단 IsAlive 속성은 false로 만든다. 이후에 캐릭터 모델이 완전히 로드가 된 후에 즉, CharacterAdded 이벤트가 발생한 후에 IsAlive 속성을 true로 만들 것이다. 그리고 나서 Died 이벤트가 발생하면 다시 IsAlive 속성을 false로 만들어서 속성이 제대로 된 값을 가질 수 있게한다. 물론 IsAlive속성의 실제 셋팅은 각 이벤트에 연결된 함수 안에서 해야할 것이다.

local Players = game:GetService("Players")
 
local function onCharacterAdded(character, player)
    -- 캐릭터 모델이 실제로 로드 완료됐으므로 IsAlive 속성을 true로.
    player:SetAttribute("IsAlive", true)
    local humanoid = character:WaitForChild("Humanoid")
    humanoid.Died:Connect(function()
        local points = player.leaderstats.Points
        points.Value = 0
        -- 플레이어가 죽었으므로 IsAlive 속성을 false.
        player:SetAttribute("IsAlive", false)
    end)
end

 이제 남은 과정은 player 객체의 IsAlive 속성을 보고 점수를 증가시킬지 말지를 결정하는 일만 남았다. 객체의 속성에 접근하기 위해서는 GetAttribute() 함수를 사용한다.

while true do
    wait(1)
    local playerList = Players:GetPlayers()
    for currentPlayer = 1, #playerList  do
        local player = playerList[currentPlayer]
        -- IsAlive 속설을 보고 플레이어가 죽었으면 실행시키지 않는 if문을 추가함.
        if player:GetAttribute("IsAlive") then
            local points = player.leaderstats.Points
            points.Value = points.Value + 1
        end
    end
end

이제 다시 한번 테스트하고 완벽하게 작동하는지를 확인하자.

 

전체 코드를 확인해보자.

local Players = game:GetService("Players")
 
local function onCharacterAdded(character, player)
    player:SetAttribute("IsAlive", true)
    local humanoid = character:WaitForChild("Humanoid")
    humanoid.Died:Connect(function()
        local points = player.leaderstats.Points
        points.Value = 0
        player:SetAttribute("IsAlive", false)
    end)
end
 
local function onPlayerAdded(player)
    local leaderstats = Instance.new("Folder")
    leaderstats.Name = "leaderstats"
    leaderstats.Parent = player
    
    local points = Instance.new("IntValue")
    points.Name = "Points"
    points.Value = 0
    points.Parent = leaderstats
    
    player:SetAttribute("IsAlive", false)
    
    player.CharacterAdded:Connect(function(character)
        onCharacterAdded(character, player)
    end)
end
 
Players.PlayerAdded:Connect(onPlayerAdded)
 
while true do
    wait(1)
    local playerList = Players:GetPlayers()
    for i = 1, #playerList  do
        local player = playerList[i]
        if player:GetAttribute("IsAlive") then
            local points = player.leaderstats.Points
            points.Value = points.Value + 1
        end
    end
end

완성된 코드는 아직 완벽하지 않다. 로비 구성이라던지, 여러 플레이어중에서 우승자 보여주기, 최적화등의 내용등 여러가지 게임 요소를 더 넣을 수 있을 것이다.

 

마침

기초과정 튜토리얼은 여기까지입니다. 다시한번 언급하지만, 해당 튜토리얼은 로블록스 정식 튜토리얼의 번역판이이라고 할 수는 없습니다. 소스코드와 영상, 이미지를 사용하였지만, 튜토리얼을 읽고 나서 개인적인 해설을 맘대로 쓴 글입니다. 잘못된 부분도 있을 수 있으니 댓글로 알려주시기 바랍니다.

로블록스의 중급이상의 정식 튜토리얼은 현재 기준(2021년4월22일)으로 아직 별로 올라 온 것이 없습니다. 중급이상의 정식 튜토리얼은 나올때마다 포스팅하기로 하고, 그와 별도로 앞으로의 포스팅은 그때그때 필요하거나 중요하거나, 게임에 부분적으로 활용가능한 내용이라고 생각되는 것이 있으면 포스팅하도록 하겠습니다. 기초과정이었지만, 꽤 긴 튜토리얼이었네요. 수고하셨습니다. 꾸벅.