게임 메이커: 아두이노 게임기 만들기 PART4 – 게임 화면 전환
강좌 시리즈:
.
지난 강좌에서는 게임 화면을 만들기 위한 소스코드의 기본 구조를 잡았습니다. 이제 이 소스를 다듬어서 각 화면을 완성시키고 조이스틱+버튼을 이용해서 화면간 전환이 될 수 있도록 해보겠습니다.
이번 강좌에서 사용되는 소스는 GitHub에서 받을 수 있습니다. [Template/Template2] 폴더를 확인하시면 됩니다.
https://github.com/godstale/game-maker
아두이노에 전원이 들어가면 가장 먼저 보여지는 시작 애니메이션과 로고 화면은 지난 강좌의 코드에서 바뀐 게 없습니다. 로고 화면에서 버튼을 눌러 메뉴 화면으로 전환될 때부터 필요한 코드들을 추가해야 합니다.
.
추가된 전역 변수와 함수
setup() 함수가 시작되기 전, 필요한 전역 변수들과 함수를 위해 아래 코드들이 추가되었습니다.
// Menu #define MENU_START 1 #define MENU_CREDIT 2 #define MENU_MIN 1 #define MENU_MAX 2 int prevMenu = MENU_MAX; int currentMenu = 0; PROGMEM const char* stringTable[] = { "Start game", "Credit" }; // Utilities int getOffset(int s); void initUserInput(); void stopUntilUserInput(); void setMenuMode(); void setGameMode(); void setResultMode(); void setCreditMode();
메뉴 화면을 보여주고 조이스틱으로 현재 선택된 항목을 이동시키기 위해서 prevMenu, currentMenu 변수를 만들어 뒀습니다. 그리고 메뉴의 시작과 끝 index 값을 표시하는 MENU_MIN, MENU_MAX를 정의해 뒀습니다. 메뉴화면에서는 딱 2개의 메뉴만 보여줄 겁니다. “Start game”과 “Credit”입니다. 따라서 이 두 문자열을 stringTable[] 배열로 미리 잡아뒀습니다.
그리고 화면 전환, 사용자 입력을 받을 때까지 대기하는 함수들을 추가해뒀습니다.
- int getOffset(int s);
문자열을 우측 정렬하기 위해 우측에서 얼마나 띄워서 표시해야 하는지를 계산하는 함수 - void initUserInput();
사용자 입력 상태를 담은 변수들을 초기화하는 함수. 사용자 입력 상태에 따라 처리가 끝나면 이 함수를 이용해 입력 상태를 초기화 함. - void stopUntilUserInput();
사용자가 버튼을 누르기 전까지 while() 반복문으로 무한대기시켜주는 함수 - void setMenuMode();
메뉴 화면으로 전환 - void setGameMode();
게임 화면으로 전환 - void setResultMode();
결과 화면으로 전환 - void setCreditMode();
게임 제작자 정보 화면으로 전환
.
메뉴 화면 만들기
시작 애니메이션 – 로고 화면이 출력되면 사용자가 버튼을 입력할 때 까지 대기하도록 수정해야 합니다. 그리고 사용자가 버튼을 누르면 메뉴 화면으로 전환하도록 아래와 같이 수정했습니다. setup() 함수의 가장 마지막 코드입니다.
// display main image display.clearDisplay(); display.drawBitmap(0,0,nightrun,128,64,1); display.display(); stopUntilUserInput(); // Wait until user touch the button setMenuMode(); // Menu mode
setMenuMode() 함수를 호출하면 gameState 변수가 STATUS_MENU 로 바뀝니다. loop() 함수가 반복될 때 gameState 변수의 값에 따라 디스플레이로 표시될 화면이 결정됩니다.
loop() 함수의 코드 중 if(millis() > lTime + gameFPS) {} 블록 안에 있는 부분들이 실제 화면을 그려주는 부분들입니다. 이 코드 중 메뉴 화면을 구성하기 위해 아래와 같이 수정했습니다.
if (gameState == STATUS_MENU) { // Main menu if(up) currentMenu--; else if(down) currentMenu++; if(currentMenu > MENU_MAX) currentMenu = MENU_MAX; if(currentMenu < MENU_MIN) currentMenu = MENU_MIN; if(aBut || bBut) { if(currentMenu == MENU_CREDIT) { setCreditMode(); } else if(currentMenu == MENU_START) { setGameMode(); } } else { if(prevMenu != currentMenu) { prevMenu = currentMenu; // Remember current menu index // Draw menu display.clearDisplay(); for(int i=0; i<MENU_MAX; i++) { display.setCursor(24, 10+i*12); display.print((const char*)pgm_read_word(&(stringTable[i]))); } display.setCursor(10, 10 + (currentMenu - 1)*12); display.print("> "); display.display(); } } }
일단 사용자가 조이스틱을 어떻게 움직였는지를 체크해서 커서(>)의 위치를 나타내는 currentMenu 값을 변경합니다. 그리고 메뉴와 커서를 화면에 그려줍니다. 이 코드만으로 메뉴 화면은 조이스틱의 움직임에 반응합니다.
그리고 A(aBut), B(bBut) 버튼을 누르면 현재 커서의 위치에 해당하는 화면으로 전환하도록 코딩되어 있습니다. currentMenu 값에 따라 setCreditMode(), setGameMode() 함수가 호출되어 화면이 전환됩니다.
.
게임 제작자 정보(Credit) 화면
사용자가 Credit 메뉴를 선택하면 setCreditMode() 함수가 실행되면서 gameState 변수의 값이 STATUS_CREDIT 으로 바뀝니다. 그러면 loop() 함수가 다시 실행될 때 Credit 화면을 그리기 시작합니다.
else if (gameState == STATUS_CREDIT) { // Draw credit screen // Draw credit screen display.clearDisplay(); display.drawBitmap(0,0,nightrun,128,64,1); display.setCursor(18,5); display.print(F("Presented by")); display.setCursor(18,20); display.print(F("HardCopyWorld.com")); display.display(); // Make sure final frame is drawn stopUntilUserInput(); // Wait until user touch the button setMenuMode(); }
Credit 화면은 로고 이미지를 출력하고 그 위에 제작자 정보 문자열을 출력해줍니다. 그리고 stopUntilUserInput(); 함수를 호출해서 사용자가 버튼을 누를 때까지 대기합니다. 사용자가 버튼을 누르면 setMenuMode(); 함수가 실행되면서 다시 메뉴 화면으로 돌아갑니다.
소스코드 중간에 버튼을 누를 때 까지 멈추는 효과를 주고 싶을 때 stopUntilUserInput(); 함수를 사용하면 됩니다. 이 함수는 소스코드 하단에 정의되어 있습니다.
void stopUntilUserInput() { while (true) { // While we wait for input if (digitalRead(BUTTON_A) == LOW || digitalRead(BUTTON_B) == LOW) { // Wait for a button press break; } delay(200); // Slight delay the loop } }
.
게임 화면
메뉴 화면에서 Start game 메뉴를 선택하면 setGameMode(); 함수를 실행하면서 게임 화면으로 전환됩니다. 가장 중요하고 복잡한 게임 엔진이 실행되어야 하므로 미리 변수들을 setGameMode() 함수 안에서 초기화 해줘야 합니다.
지금은 각 화면 간 전환하는 코드만을 작성할 예정이므로 게임 화면에서는 아무 것도 하질 않습니다. 게임 화면 상태가 되면 바로 결과 화면으로 전환해 버리도록 작성해 뒀습니다. setResultMode(); 함수를 호출하면 결과 화면으로 전환됩니다.
else if (gameState == STATUS_PLAYING) { // If the game is playing // Run game engine // Draw game screen setResultMode(); }
.
결과 화면
결과 화면은 현재 게임 스코어, 최고 스코어를 표시해주는 화면입니다. 그리고 최고 스코어를 갱신한 경우 EEPROM 에 기록해서 전원이 꺼지더라도 데이터가 유실되지 않도록 해줍니다. (EEPROM 부분은 나중에 구현)
else if (gameState == STATUS_RESULT) { // Draw a Game Over screen w/ score if (gameScore > gameHighScore) { gameHighScore = gameScore; } // Update game score display.display(); // Make sure final frame is drawn // delay(100); // Pause for the sound // Draw game over screen display.drawRect(16,8,96,48, WHITE); // Box border display.fillRect(17,9,94,46, BLACK); // Black out the inside // display.setCursor(39,12); // display.print("Game Over!"); display.drawBitmap(30,12,gameover,72,14,1); display.setCursor(56 - getOffset(gameScore),30); display.print(gameScore); display.setCursor(69,30); display.print("Score"); display.setCursor(56 - getOffset(gameHighScore),42); display.print(gameHighScore); display.setCursor(69,42); display.print("High"); display.display(); stopUntilUserInput(); // Wait until user touch the button gameState = STATUS_MENU; // Then start the game paused gameScore = 0; // Reset score to 0 setMenuMode(); }
코드가 복잡해 보이지만 실은 별게 없습니다. 현재의 게임 화면을 지우지 않고 그 위에 사각형 박스를 그립니다. 그리고 게임 종료 이미지(gameover[])와 스코어 문자를 적당한 위치에 출력한 겁니다.
stopUntilUserInput(); 함수를 호출해서 버튼을 누를 때까지 작업을 멈추고 버튼이 눌러지면 메뉴 화면으로 다시 돌아갑니다.
.
기타
loop() 함수 아래쪽에 setup(), loop() 안에서 사용되는 다양한 함수들을 정의해 뒀습니다. 추후 필요한 함수들도 여기에 계속 추가해 나갈 것입니다.
테스트
여기까지 게임에 필요한 화면들과 화면 간 전환 코드를 구현했습니다. 이 코드를 컴파일해서 아두이노에 올리면 조이스틱과 버튼을 이용해 각 화면들을 확인해 볼 수 있습니다. 게임 화면은 아직 구현이 되어 있질 않지만 다음 강좌부터 본격적으로 게임 화면을 구현해 나갈 것입니다.