PADOTAGY / Halligali

SW설계기초 15조 파도타기 팀의 레포지토리 입니다.
0 stars 0 forks source link

[Refactor] 벨 누르기 모션 로직 관련 #23

Closed HaiSeong closed 1 year ago

HaiSeong commented 1 year ago

🌊 Issue

처음에는 반복문으로만 구현하면 될줄 알았지만 나중에 게임 전체적인 로직을 고려 했을때 "모션을 출력하는 동안에는 다른 입력을 받을수 없다" 라는 문제가 있는것 같습니다. 이로 인해 발생할 수 있는 문제점은

  1. 5개가 만들어 지지 않았는데 벨을 누른 경우 바로 입력을 받지 못하고 모션이 끝났을때 입력을 해야한다.
  2. 무적아이템이 발동 했을때 막 누르는 모션을 보여줄 수 없다. 등등 여러 문제가 있다고 생각됩니다.

우선 이를 해결할 방법으로 떠올린것은 함수 외부의 반복문과 함수 내부의 static 변수를 통해 모션을 출력하는 것 입니다.

void ProcessKeyInput() // get input
{
    for (int i = 0; i < 20; i++) // 20 input / 1 sec
    {
        if (_kbhit() != 0) // if there was keyboard hit
        {
            int key = _getch(); // get key
            switch (key)
            {
            case LEFT: ShiftLeft(); break;
            case RIGHT: ShiftRight(); break;
            case UP: RotateBlock(); break;
            case SPACE: SpaceDown(); break;
            }
        }
        Sleep(speed); // speed : 50
    }
}

int main()
{
    // . . .
        while (1)
        {
            if (BlockDown() == 0)
            {
                // . . .
            }
            ProcessKeyInput();
        }
    // . . .

중간 까지 구현했던 테트리스 함수의 키 입력을 받는 메인함수 부분입니다. ProcessKeyInput() 함수에서 키 입력 반응 속도를 높이기 위해 반복문을 이용해 Sleep()을 쪼갰습니다.

아마 저희 게임에서도 같은 방식으로 키 입력을 받을거라고 생각됩니다. 따라서 외부 반복문은 ProcessKeyInput() 함수의 반복문을 사용하겠습니다.

또한 기존 함수는

  1. 기존 그려진 손을 지우는 작업
  2. 새로운 손을 그리는 작업
  3. 사용자에게 보여주기 위해 Sleep 을 반복문하고 있습니다.
void drawHitBellMotion()
{
    for (int posY = 63; posY > SCREEN_MID_Y-3; posY--) // hit bell
    {
        deleteHand(HAND_BASIS_X, posY+1);
        drawHand(HAND_BASIS_X, posY);
        Sleep(10);
    }
    Sleep(50);
    for (int posY = SCREEN_MID_Y-3 + 2; posY < 41; posY++) // return (on the bell)
    {
        deleteHand(HAND_BASIS_X, posY-1);
        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 8);
        for (int line = posY - BELL_BASIS_Y - 1; line < 15; line++) 
            printOnPos(bellModel[line].str, bellModel[line].posX, line + BELL_BASIS_Y);
        drawHand(HAND_BASIS_X, posY);
        Sleep(10);
    }
    for (int posY = 41; posY <= 63; posY++) // return (off the bell)
    {
        deleteHand(HAND_BASIS_X, posY-1);
        drawHand(HAND_BASIS_X, posY);
        Sleep(10);
    }
}

새로 리팩토링 할 함수는 함수가 한번 호출될때 모든 모션이 나오지 않습니다. 함수 호출 한번당 기존 손이 지워지고 새로운 손이 지워지는 작업을 합니다. 외부 반복문이 있기 때문에 반복되어 손이 음직이는 모션이 완성됩니다.

void drawHitBellMotion(int flint) // flint는 부싯돌 이란 뜻으로 손이 벨을 처러가는 모션이 시작된다는것을 알림
{
    static int beforePosY; // 기존 지울 손 위치
    static int newPosY; // 새로 출력할 손 위치
    static dy; // 변위

    if (flint == 1)
        dy = 1; // dy = 1;

    if (dy == 0) // 리턴
        return ;

    if (PosY < 벨의 위치) // 손이 올라가서 벨을 쳤으면 돌아와야함 (손이 올라간다  == PosY가 줄어든다)
        dy = -1;

    if (PosY <= max_height) // 손이 바닥이라면 dy = 0 으로 바꿔 함수가 다시 호출되더라도 손이 그려지지 않음
        dy = 0;

    // newPos = beforePos + dy
    // 기존 손 지우기
    // 새로운 손 그리기
    // beforePos = newPos
}

static 변수는 전역 변수처럼 프로그램이 종료되지 않는 한 메모리가 소멸되지 않습니다. 따라서 beforePosY는 함수가 반복적으로 호출될 때 사라지지 않고 값을 유지하고 있습니다. 인자인 flint는 부싯돌 이라는 뜻으로 마치 불을 붙이는 과정과 비슷한것 같아 붙인 이름입니다. 스페이스가 눌리면 case SPACE: drawHitBellMotion(1); break; 으로 flint가 1이 됩니다. 이러면 dy = 1이 되어 if (dy == 0) return ; 에 들어가지 않고 아래 모션들을 출력합니다.

void ProcessKeyInput() // get input
{
    for (int i = 0; i < 20; i++) // 20 input / 1 sec
    {
        if (_kbhit() != 0) // if there was keyboard hit
        {
            int key = _getch(); // get key
            switch (key)
            {
            case LEFT:   .  .  . ; break;
            case RIGHT:   .  .  . ; break;
            case UP:  .  .  . ; break;
            case SPACE: drawHitBellMotion(1) Sleep(speed); break;
            }
        }
        drawHitBellMotion(0);
        Sleep(speed); // speed : 50
    }
}

이 구조의 장점은 우선 빠른 반응속도를 얻을 수 있고 손이 내려가던 중이였더라도 다시 스페이스를 누르면 손이 올라갑니다.

우선 작업은 바로 진행하지 않고 화요일 회의때 공유해본 후 진행해보겠습니다.


#include <stdio.h>
#include <windows.h>
#include <stdlib.h>
#include <conio.h>

#define SPACE 32

void test(int flint);
void ProcessKeyInput();

void ProcessKeyInput() // get input
{
    for (int i = 0; i < 20; i++) // 20 input / 1 sec
    {
        if (_kbhit() != 0) // if there was keyboard hit
        {
            int key = _getch(); // get key
            switch (key)
            {
            case SPACE: test(1); Sleep(50); break;
            }
        }
        test(0);
        Sleep(50); // speed : 50
    }
}

void test(int flint) // flint는 부싯돌 이란 뜻으로 손이 벨을 처러가는 모션이 시작된다는것을 알림
{
    static int beforePosY; // 기존 지울 손 위치
    static int newPosY; // 새로 출력할 손 위치
    static int dy; // 변위

    if (flint == 1)
        dy = 1;

    if (dy == 0) // 리턴
        return;

    // 기존 손 지우기
    newPosY = beforePosY + dy;

    // if (PosY < 벨의 위치) // 손이 올라가서 벨을 쳤으면 돌아와야함 (손이 올라간다  == PosY가 줄어든다)
    if (newPosY > 10)
        dy = -1;

    // if (PosY <= max_height) // 손이 바닥이라면 dy = 0 으로 바꿔 함수가 다시 호출되더라도 손이 그려지지 않음
    if (newPosY <= 0)
        dy = 0;

    // 새로운 손 그리기
    printf("beforePos = %d newPos = %d\n",beforePosY, newPosY);
    beforePosY = newPosY;
}

int main()
{
    while (1)
    {
        ProcessKeyInput();
    }
}

📌 To-do

mooyoung2309 commented 1 year ago

아직 전부 이해는 못했는데, 다음의 코드로 바꾸면 모션 중에서도 아이템 사용, 카드 사용, 입력 등등의 모든 이벤트를 처리할 수 있나요 ?

jujoko commented 1 year ago

갑자기 든 생각인데, 손이 올라가고 내려가는 모션 없이 스페이스 바를 누르자 마자 손이 벨에 생기고 0.5초 정도 뒤에 사라지는 모습은 별로인가요? 전에 봤던 모션에서는 손 올라가고 내려가는 속도가 느려서 스페이스바를 눌렀을 때, 종을 치는 게 즉각적이지 않은 듯 해서 여쭙습니다.

HaiSeong commented 1 year ago

@mooyoung2309 예 모션중 입력을 모두 받을 수 있도록 디자인 중입니다.

@jujoko 저도 막상 계속해서 돌리다보니 속도가 많이 느리다는 생각을 했습니다. 처음에 설계할때 부드러운 액션을 주고싶어서 y값을 1식 변화시켜 모션을 주다보니 많이 느리고 바로 종을 치지 못하는 문제가 있습니다. 하지만 손이 보여지는 모션이 재미요소가 될수 있다 생각합니다. 그래서 제생각엔 기존 y값을 1식 변화시키는 모션은 그대로 가져가지 못하고 두현님 말씀대로 빠른 반응속도를 위해 전진할때는 변위를 5~ 6정도로 빠르게 전진시키고 후퇴할때는 2 ~3정도로 후퇴시키는 액션으로 바꿔보겠습니다.