Bytepath #1 게임 루프
May 2022
목차
시작
시작하기 전에 LÖVE를 설치하고 어떻게 LÖVE 프로젝트를 실행하는지 아셔야합니다. 이 튜토리얼에서 LÖVE의 버전은 0.10.2이며 여기서 다운 받으실 수 있습니다.
스텝을 따라가면서 자세한 설명은 이 페이지를 참고하시길 바랍니다.
다되었다면 프로젝트 폴더 안에 main.lua
파일을 다음처럼 만듭니다.
function love.load()
end
function love.update(dt)
end
function love.draw()
end
실행시키면 윈도우창이 보이시고 검은 화면을 출력합니다. 위 코드에서 LÖVE 프로젝트의 프로그램 시작 시 love.load
함수가 한번 실행되고 love.update
와 love.draw
가 매 프레임마다 실행됩니다.
그래서, 만약 이미지를 불러와서 출력시키고 싶다면 다음처럼 작성하시면 됩니다.
function love.load()
image = love.graphics.newImage('image.png')
end
function love.update(dt)
end
function love.draw()
love.graphics.draw(image, 0, 0)
end
love.graphics.newImage
는 이미지 텍스처를 불러와 image
변수에 할당한 뒤 매 프레임마다 (0, 0) 위치에 출력합니다.
love.draw
가 매 프레임마다 불려지는 지 확인하려면 다음을 시도해보세요.
love.graphics.draw(image, love.math.random(0, 800), love.math.random(0, 600))
윈도의 기본 사이즈는 800x600
이기 때문에 스크린 전체에서 이미지가 매우 빠르고 랜덤하게 출력됩니다.
매 프레임 사이는 화면이 초기화되며 그렇지 않다면 그려지는 이미지가 화면 전체를 천천히 채우게 될겁니다. LÖVE는 매 프레임의 끝에 화면 초기화를 해주고 있습니다. 한번 이 게임 루프를 어떻게 바꿀 수 있는지 알아봅시다.
게임 루프
기본 LÖVE의 게임 루프는 love.run
에서 볼 수 있으며 다음과 같습니다.
function love.run()
if love.math then
love.math.setRandomSeed(os.time())
end
if love.load then love.load(arg) end
-- We don't want the first frame's dt to include time taken by love.load.
if love.timer then love.timer.step() end
local dt = 0
-- Main loop time.
while true do
-- Process events.
if love.event then
love.event.pump()
for name, a,b,c,d,e,f in love.event.poll() do
if name == "quit" then
if not love.quit or not love.quit() then
return a
end
end
love.handlers[name](a,b,c,d,e,f)
end
end
-- Update dt, as we'll be passing it to update
if love.timer then
love.timer.step()
dt = love.timer.getDelta()
end
-- Call update and draw
if love.update then love.update(dt) end -- will pass 0 if love.timer is disabled
if love.graphics and love.graphics.isActive() then
love.graphics.clear(love.graphics.getBackgroundColor())
love.graphics.origin()
if love.draw then love.draw() end
love.graphics.present()
end
if love.timer then love.timer.sleep(0.001) end
end
end
프로그램이 love.run
을 시작하면 거기서 모든 일이 일어납니다. 함수는 주석이 잘되어 있어 LÖVE 위키에서 각 함수가 무엇을 하는 지 알 수 있습니다만
하나 하나 살펴봅시다.
if love.math then
love.math.setRandomSeed(os.time())
end
첫 라인에서 만약 love.math
가 nil이 아니라면 이라는 조건문을 볼 수 있습니다. Lua에서는 false
와 nil
을 제외한 모든 변수가 true
입니다. 그래서 love.math
가 뭐로 정의되었든 if love.math
는 true가 됩니다.
LÖVE의 경우 이러한 변수는 conf.lua 파일에서 활성화 여부를 설정합니다. 지금은 이 파일에 대해 몰라도 되지만 굳이 언급하는 이유는 이 파일은 love.math
와 같은 개별 시스템의 활성화 여부도 설정할 수 있기 때문에 이 함수에서 확인하는 것입니다.
Lua에서는 일반적으로 변수를 정의하지 않거나 정의되지 않은 변수를 참조한다면 nil
을 반환합니다.
그래서 random_variable = 1
처럼 변수를 정의하지 않는다면 if random_variable
는 false가 될 겁니다.
만약, love.math
모듈이 활성화되어 있으면(기본값은 활성화되어 있음) seed를 현재 시간을 기준으로 설정합니다.
love.math.setRandomSeed와 os.time을 참고하세요. 그 다음은 love.load
함수가 실행됩니다.
if love.load then love.load(arg) end
arg
는 프로젝트 실행 시 LÖVE에 넘겨지는 명령어 인수 입니다. 보면 알 수 있듯이 love.load
가 한번만 불리는 이유는 실제로 한번만 불리기 때문입니다. 반면, update와 draw 함수는 loop를 통해 여러번 불리게 됩니다. (한번의 loop는 한 프레임과 대응됩니다.)
-- lova.load를 포함한 첫 프레임의 dt를 포함시키지 않기 위해
if love.timer then love.timer.step() end
local dt = 0
love.load
가 정상적으로 실행되었다면, love.timer
가 정의되어 있는 지 확인 후 마지막 두 프레임 사이에 시간이 얼마나 걸렸는 지 측정하는 love.timer.step을 실행합니다. love.load
는 처리하는 데 많은 시간(이미지, 사운드 등 모든 것을 로드하기 때문에)이 걸리기에 주석에서 설명했듯이 게임의 첫 프레임의 love.timer.getDelta
는 사용하지 않습니다.
dt
는 0으로 초기화합니다. Lua에서 변수는 기본적으로 전역(global)이기 때문에 local dt
를 현재 블록의 지역 변수로 한정시킵니다. 이 경우 지역은 love.run
함수로 한정됩니다. 블록에 관해서는 여기를 보세요
-- Main loop time.
while true do
-- Process events.
if love.event then
love.event.pump()
for name, a,b,c,d,e,f in love.event.poll() do
if name == "quit" then
if not love.quit or not love.quit() then
return a
end
end
love.handlers[name](a,b,c,d,e,f)
end
end
end
여기가 메인 루프의 시작입니다. 각 프레임에서 첫 번째는 이벤트 처리입니다. love.event.pump는 이벤트를 이벤트 큐에 푸시하고 해당 이벤트에 대한 설명에 따라 해당 이벤트는 사용자에 의해 생성됩니다. 예를 들면 키 누르기, 마우스 클릭, 창 크기 조정 등이 있습니다.
루프는 love.event.poll를 사용하여 이벤트 큐를 통해 각 이벤트를 처리합니다. love.handlers
는 관련된 콜백(callback) 함수를 정의한 테이블입니다. 예를 들어, love.handlers.quit
는 love.quit
함수가 존재한다면 실행합니다.
LÖVE는 이벤트가 발생할 때 호출될 콜백을 main.lua
파일에서 정의할 수 있습니다. 콜백의 전체 리스트는 다음을 참고 하세요. 콜백은 다음에 자세히 다루도록 하겠지만 콜백은 이렇게 실행됩니다. love.handlers[name]
으로 넘겨지는 a, b, c, d, e, f
인수는 관련 함수에서 사용할 수 있는 모든 가능한 인수입니다. 예를 들어, love.keypressed는 인수로 키 눌림, 키 코드, 키 눌림 반복 여부를 받으며 이 경우 love.keypressed
는 a, b, c
는 어떤 한 값으로 정의되지만 d, e, f
는 nil이 됩니다.
-- Update dt, as we'll be passing it to update
if love.timer then
love.timer.step()
dt = love.timer.getDelta()
end
-- Call update and draw
if love.update then love.update(dt) end -- will pass 0 if love.timer is disabled
love.timer.step은 마지막 두 프레임 사이의 시간을 측정하고 love.timer.getDelta
로 반환되는 값을 변경합니다.
그래서 dt
는 이전 프레임이 실행되는데 걸리는 시간을 포함합니다. 이 값은 프레임률의 변화에도 불구하고 love.update
함수에 전달되어 일정한 속도로 사물을 정의할 수 있기 때문에 유용합니다.
if love.graphics and love.graphics.isActive() then
love.graphics.clear(love.graphics.getBackgroundColor())
love.graphics.origin()
if love.draw then love.draw() end
love.graphics.present()
end
love.update
를 부른 후 love.draw
가 호출됩니다. 하지만 그전에 love.graphics
모듈이 유효한지 확인하고 화면에 그릴 수 있는 지 love.graphics.isActive
로 확인합니다.
화면은 미리 정의된 배경색(초기값은 검은색)으로 love.graphics.clear
로 초기화합니다.