PICO-8 Fanzine 이슈 1 정리 (1) - 스쿼시

Scope.H·2025년 1월 5일
0

주: 직접 해보면서 정리하고 있기 때문에 실제 Fanzine 내용과는 차이가 있을 수 있음

스쿼시

고전 게임 PONG 과 같은 스타일의 스쿼시 게임을 만들어보자.

스쿼시: 벽에 대고 라켓으로 테니스 공을 쳐서 받아내는 1인용 구기 운동

PICO-8을 처음 기동하면, 명령줄 모드(Command mode) 에서 시작하게 된다.
여기서 ESC 키를 누르면 에디터(Editor) 모드 로 들어가게 되며, 여기에서 게임을 만들 수 있다.

게임을 실행시키려면 ESC 키를 눌러 명령줄 모드로 돌아간 뒤 run 을 입력하고 enter 키를 누른다.
명령줄 모드로 돌아가려면 언제든지 ESC 키를 눌러 돌아갈 수 있다.

명령줄 모드에 있을 때, 다음을 입력하여 게임을 저장할 수 있다.

save GAME_NAME

게임을 로드하기 위해서는 다음을 입력한다.

load GAME_NAME

이 게임의 경우, 스쿼시(Squasy)라는 이름을 사용할 것이므로, 다음과 같이 입력한다.

save squashy

주: PICO-8은 대/소문자 구분을 지원하지 않는다. 따라서 모든 알파벳은 Shift 없이 입력하도록 한다. Shift 입력에 대해서는 뒤에서 자세히 다루도록 한다.

이제 PICO-8에서 첫 번째 게임을 만들어보도록 하자.

1. 움직이는 라켓

움직이는 방망이(라켓)를 만들어보도록 하자.

PICO-8은 Lua 5를 기반으로 약간의 커스텀이 가해진 스크립트 언어를 사용한다.
이제 다음 코드를 입력한다.

-- paddle
padx=52
pady=122
padw=24
padh=4

function mov_pad()
	if btn(⬅️) then padx-=3
	elseif btn(➡️) then padx+=3 end
end

function _update()
	mov_pad()
end

function _draw()
	cls(0)
	-- draw paddle
	rectfill(padx,pady,padx+padw,pady+padh,6)
end

입력을 완료했다면 저장하도록 하자.

주: 저장 단축키는 시스템 상관없이 CTRL + S, 실행 단축키는 CTRL + R.

코드 해설

function _update()

1초당 30번씩 실행되는 함수이다. 이 함수 내에 들어있는 내용대로 값을 업데이트한다.
즉, PICO-8은 기본적으로 30fps로 실행되는 게임 엔진이다.

function _draw()

_update() 함수 실행 직후마다 호출되어 게임상의 내용을 화면에 그리는 함수이다.

btn()

어떤 버튼이 눌렸는지를 체크하는 함수이다. 0, 1, 2, 3으로 좌, 우, 상, 하를, 4, 5로 O, X 버튼을 처리한다. (O, X는 키보드상에서 Z, X에 해당)

주: btn(⬅️)에서 보듯, 코드에서는 숫자가 아닌 기호를 입력하였는데, 이는 PICO-8만의 기능으로, Shift를 누르고 알파벳을 입력할 시 기호가 입력되는 특징이 있다. 따라서 Shift를 누른 채로 L/R/U/D를 누르면 ⬅️/➡️/⬆️/⬇️ 기호가 입력되고, 이는 숫자를 입력한 것과 동일하게 취급된다. O, X 버튼 역시 Shift를 누른 채로 O/X를 입력하면 해당되는 기호가 입력된다.

cls()

PICO-8의 화면을 지우는 함수이다. 0은 어떤 색으로 지울지를 지정하는 색상 코드로, 0-15까지 사용가능하다. 여기서는 검은색으로 지웠다. (색상 코드는 나중에 다루도록 한다)

rectfill()

시작 좌표, 끝 좌표, 색상 코드를 지정하여 화면에 사각형을 그리는 함수로 시작 좌표는 끝 좌표보다 항상 왼쪽 위에 있어야 한다.

-- comment

주석이다. 코드에 대한 설명을 적을 때 사용되며, 실행에 영향을 주지는 않는다.

2. 공 추가

이제 코드 상단에 다음 코드를 추가한다. paddle에 대해 정의해둔 부분 아래에 입력한다.

-- ball
ballx=64
bally=64
ballr=3
ballxdir=5
ballydir=-3

그리고 _draw() 함수 안에 다음 코드를 추가한다.

-- draw ball
circfill(ballx,bally,ballr,6)

코드 해설

circfill()

(x, y)좌표를 중심으로 하는 반지름 R의 원을 지정된 색상으로 그리는 함수

3. 공 움직이게 하기

이제 다음 코드를 mov_pad() 함수 바로 밑에 입력하도록 하자.

function mov_ball()
	ballx+=ballxdir
	bally+=ballydir
end

그리고 _update() 함수에 다음을 추가하여 공이 움직일 수 있도록 한다.

function _update()
	mov_pad()
	mov_ball()	-- here
end

이제 실행시켜 보면 공이 꽤 빠른 속도로 화면 우상단으로 비스듬히 움직여 화면 밖으로 사라지는 걸 볼 수 있다.

4. 공이 코트를 벗어나지 않도록 하기

위에서 확인한 바와 같이 지금 우리의 공은 화면 밖을 벗어나 (아마도) 무한한 우주를 계속해서 가로지르고 있다. 스쿼시를 하기 위해선 공이 벽에 튕겨 우리의 라켓으로 와야 하므로, 이번에는 공이 화면 가장자리에서 반사되도록 하자. 어렵지 않다. 공의 x, y 좌표를 확인해서 화면 가장자리인지를 체크하는 로직이 있으면 된다.

그 전에... 재미 요소를 하나 추가해보도록 하자. 바로 소리다.

편집기 모드에서 화면 우상단을 보면 아이콘 5개가 있는 것을 확인할 수 있다. 이 중 4번째, 역재생 아이콘처럼 생긴 것을 눌러보자.

사운드 편집기가 나타난다.

이제 "띡" 하는 소리를 추가해주도록 하자. 마우스를 이용해 편집기 화면 가장 왼쪽을 누르면 음이 입력되고 SPACEBAR를 누르면 소리를 들어볼 수 있다.

마음에 들 때까지 소리를 조정한 뒤 다시 코드 편집기 (우상단 아이콘 중 소괄호 아이콘을 누르면 된다)로 돌아와 mov_ball() 함수 밑에 다음 코드를 입력한다.

function bounce()
	-- left
	if ballx<ballr then ballxdir=-ballxdir sfx(0) end
	-- right
	if ballx>128-ballr then ballxdir=-ballxdir sfx(0) end
	-- top
	if bally<ballr then ballydir=-ballydir sfx(0) end
end

그리고 _update()에 다음과 같이 추가한다.

function _update()
	mov_pad()
	mov_ball()
	bounce()	-- here
end

이제 실행시켜 보면 오른쪽 위로 날아가는 공이 오른쪽 가장자리에 닿아 위로 튕기고, 벽에 부딪힌 후, 다시 왼쪽 가장자리에 튕겨 오른쪽 아래로 날아오는 걸 볼 수 있다. 공이 튕길 때마다 띡띡 소리도 난다.

이제 라켓으로 공을 받아보자....는 공이 라켓을 통과해 이번에는 아주 깊은 심연으로 떨어지는 것을 볼 수 있다. 다음은 이걸 해결해보도록 하자.

코드 설명

sfx()

PICO-8의 사운드 편집기에서 생성된 n번째 파일의 사운드를 재생한다.

5. 공 쳐내기

이 부분이 조금 까다롭다. 공이 라켓의 범위 안에 들어왔는지 체크해야 하는데, 우리의 라켓은 가느다란 선이 아닌 굵직한 상자이기에, x와 y의 범위를 모두 체크해야 한다. 그 전에, 라켓에 부딪히는 소리를 하나 더 만들어보도록 하자.

사운드 편집기로 가서 화면 좌상단 < 00 > 라고 되어 있는 부분의 오른쪽 화살표를 눌러 01 로 바꾸자. 아까와 마찬가지로 화면 가장 왼쪽을 적당히 눌러 띡 소리를 만들어주고 (먼젓번 소리보다 약간 낮게 만들어주는 것을 추천한다) 다시 코드 편집기로 돌아와 bounce() 함수 밑에 다음 코드를 입력한다.

function bounce_pad()
	if ballx>=padx and ballx<=padx+padw and bally>pady then
		sfx(1)
		ballydir=-ballydir
	end
end

and 명령이 새로 나왔다. and 는 연결된 조건이 모두 참일 경우에 참으로 하라는 의미이다. 한국어로는 '그리고'에 해당한다.

주: 이전까지는 if 문을 작성 시 한 줄로 작성하였는데, 이는 PICO-8의 문자 수/토큰 수 제한 조건 때문으로, 이번 if 문은 조건이 길기 때문에 여러 줄로 나누어 작성하였다. lua는 여러 줄로 쓰던 한 줄로 쓰던, 토큰이 적절히 구분만 가능하면 문제 없이 작동한다는 특이점이 있다.

sfx(1) 로 아까 만든 두번째 소리를 씀에 유의한다.

이제, _update() 함수에 다음과 같이 코드를 추가한다.

function _update()
	mov_pad()
	bounce()
	bounce_pad()	-- here
	mov_ball()
end

코드의 순서를 약간 바꾸었음에 주의하라. 공이 움직이는 것이 가장 후순위로 되어 있다.

이제 게임을 실행하고 라켓을 움직여 공을 받아내보자. 공이 라켓에 부딪히고 다시 퉁겨나가는 걸 볼 수 있다. 하지만 여전히 라켓이 공을 따라가지 못하면 공이 화면 밖으로 나가 밑에 자리잡은 심연 속에서 튀어다니는 걸 알 수 있다.

다음에는 라켓으로 공을 받아내지 못하면 '죽은 공(dead ball)'으로 처리할 수 있도록 해보자.

6. 공을 다시 로드하기

공이 라켓에 맞지 않고, 화면 밑으로 빠질 경우, 공을 다시 화면 중앙으로 돌려놓도록 하자, 이때, 공을 받아내지 못하면 라이프를 잃는 기능은 추후에 만들어보도록 하자.

일단, 공을 놓쳤음을 알 수 있게 3번째 사운드(sfx(2))를 만들자.

그리고 코드 편집기로 돌아와 새 함수를 mov_ball() 밑에 추가한다.

function lose_ball()
	if bally>128 then sfx(2) bally=24 end
end

그리고 _update() 에 위 함수를 추가한다.

function _update()
	mov_pad()
	bounce()
	bounce_pad()
	mov_ball()
	lose_ball()	-- here
end

이제 실행해 보면 라켓으로 공을 맞히지 못할 경우 사운드 효과와 함께 공이 화면 중앙에 나타나는 걸 확인할 수 있다.

팁: 테스트할 때 가지고 있는 게임 콘솔용 컨트롤러를 연결해 보자. 훨씬 플레이하기 수월해진다.

7. SCORE!

스쿼시 게임인 만큼 점수 시스템을 도입해 최고 점수를 노릴 수 있도록 만들어보자.

먼저 프로그램 상단에 다음을 추가한다.

score=0

그리고 bounce_pad() 함수를 수정하자

function bounce_pad()
	if ballx>=padx and ballx<=padx+padw and bally>pady then
		sfx(1)
		ballydir=-ballydir
		-- score
		score+=10	-- here
	end
end

마지막으로 _draw() 함수도 다음과 같이 수정하자.

function _draw()
	cls(0)
	-- draw score
	print("score  "..score,12,6,6)	-- here
	-- draw paddle
	rectfill(padx,pady,padx+padw,pady+padh,6)
	-- draw ball
	circfill(ballx,bally,ballr,6)
end

이제 실행해 보면 제법 그럴듯한 모습의 게임이 나오고, 라켓으로 공을 받아칠 때마다 점수가 10점씩 올라가는 걸 볼 수 있다.

코드 설명

print()

지정된 내용을 해당 좌표에 지정된 색으로 글을 출력한다.
문자열에 변수를 포함하고 싶다면 .. 를 쓴다.

8. 잔기 시스템

먼저 잔기를 표시할 이미지(스프라이트)를 그려보도록 하자.

편집기 모드 상단 우측에 고양이(?) 아이콘이 있는 버튼(2번째)을 누르면 스프라이트 편집기가 나온다. 다음과 같이 아래쪽 팔레트 상단 위의 x자가 쳐진 부분을 누르고, 그 위에 하트를 그린다. 잘못 그렸을 때는 검정색을 사용해 덧칠하여 지우도록 하자.

그런 다음 코드 편집기로 돌아와 아래와 같이 프로그램 상단에 잔기 변수를 추가한다.

life=5

그리고 _draw() 함수에 다음 내용을 추가한다.

-- draw the lives
spr(000, 12,13) print(" x "..life,20,15,6)

마지막으로 lose_ball() 함수의 내용을 아래와 같이 수정한다.

function lose_ball()
	if bally>128 then
		-- from here
		if life>0 then
			sfx(2) bally=24 life-=1
		else
			ballxdir=0 ballydir=0 bally=64
		end
		-- to here
	end
end

이제 저장하고 실행시켜 보자. 제법 그럴싸한 게임이 완성되었다. 잔기가 소진되면 게임이 멈춘다.

코드 설명

spr()

지정된 번호의 스프라이트를 지정된 좌표 위치에 그린다.

마무리

이렇게 하여 PICO-8의 첫 게임인 스쿼시 게임을 완성하였다. 눈치챈 독자도 있겠지만, 이걸 응용한 게임이 바로 벽돌깨기(breakout) 이다. 관심 있으면 추가로 더 구현해보길 바란다.

profile
개발자이고 싶은 미니 코딩쟁이... TIL 글을 주로 올립니다.

0개의 댓글

관련 채용 정보