__________

Designing the Future with Circuits

반도체 회로설계 취준기

자습시간/Verilog

Pmod OLED 공룡 게임 만들기(2)

semicon_circuitdesigner 2025. 2. 17. 16:38

이번에는 저번에 구현했던 비트맵에서 더 나아가 여러가지 이미지를 번갈아가며 화면에 나타내어 움직이는 듯한 모습을 구현하고자 합니다.

저번에 사용했던 툴을 이용해서 공룡이 달리는 듯한 두 이미지를 생성하고, 이를 배열로 변환한 뒤 다음과 같이 코드를 작성해서 공룡이 뛰는 모션을 구현했습니다.

더보기
void Dino_Run() {
   OLED_ClearBuffer(&myDevice);
   OLED_MoveTo(&myDevice, 0, 0); // (0,0)에서 출력 시작
   OLED_Update(&myDevice); // OLED 화면 업데이트
   u8 *pat;
   char c;

   xil_printf("print dinosaur and cactus");
   c = 1;

   while(1){
	   xil_printf("Dinosaur running\n");
	   OLED_MoveTo(&myDevice, 0, 13); // (0,0) 좌표에서 비트맵 출력 시작	//MAX 128,32
	   //OLED_GetBmp(&myDevice, 128, 128, dinoncactus);
	  OLED_PutBmp(&myDevice, 19, 19, dinorun1);
	  usleep(100000);
	   OLED_Update(&myDevice);       // OLED 화면 업데이트
	  OLED_PutBmp(&myDevice, 19, 19, dinorun2);
	  usleep(100000);
	   //OLED_PutBmp(&myDevice, DINONCACTUS_WIDTH, DINONCACTUS_HEIGHT, dino);
	   OLED_Update(&myDevice);       // OLED 화면 업데이트
   }

}

 

위 코드를 실행하면 공룡이 발을 번갈아가며 들어올리며 걷는듯한 애니메이션을 구현할 수 있습니다.

 

이후 선인장이 우측 끝에서 좌측 끝으로 이동하는 것을 구현하기 위해 아래와 같이 코드를 작성했습니다.

더보기
void CactusMove() {
    int cactus_x = 128; // initial cactus position
    int cactus_width = CACTUS_WIDTH;
    int draw_x, draw_width;
    char c;

    while (1) {
        OLED_ClearBuffer(&myDevice);

        // 1️. print cactus on current position
        OLED_MoveTo(&myDevice, cactus_x, 16);
        OLED_PutBmp(&myDevice, CACTUS_WIDTH, CACTUS_HEIGHT, cactus);

        // 2️. move cactus to left
        cactus_x -= 1;

        // 3️. when cactus reaches left edge, start from the right again
        if (cactus_x < 0) {
            cactus_x = 128; // 오른쪽 끝으로 이동
        }

        OLED_Update(&myDevice);

        // 4️. speed control
        usleep(10000); // 100ms 대기*/
    }
}

 

 

 

이제 두 모션을 합쳐 좌측에는 공룡이 계속 뛰고 있고 선인장이 움직이는 애니메이션이 구현되도록 했습니다.

 

이후에는 장애물을 선인장2개, 바위를 추가하여 총 장애물 3개를 만든 뒤, 난수를 발생시켜 랜덤으로 장애물을 선택하고, 그 간격도 무작위로 나타나도록 코드를 최종 수정했습니다. 추가로 우측 상단에는 프레임에 따라 증가하는 점수판이 표시되도록 했습니다.

더보기
// 난수를 직접 생성하는 함수 (XORSHIFT 사용)
int get_random_offset() {
    static uint32_t seed = 0xACE1;
    seed ^= seed << 13;
    seed ^= seed >> 17;
    seed ^= seed << 5;
    return (seed % 40) + 20; // 20~60 범위의 난수 반환 (장애물 간격 조절)
}

// 장애물 종류 선택 함수
const uint8_t* get_random_obstacle(int* width, int* height) {
    static uint32_t seed = 0xBEEF;
    seed ^= seed << 11;
    seed ^= seed >> 7;
    seed ^= seed << 3;

    int obstacle_type = (seed % 3); // 0, 1, 2 중 하나 선택

    if (obstacle_type == 0) {
        *width = CACTUS_WIDTH;
        *height = CACTUS_HEIGHT;
        return cactus;
    } else if (obstacle_type == 1) {
        *width = CACTUS2_WIDTH;
        *height = CACTUS2_HEIGHT;
        return cactus2;
    } else {
        *width = ROCK_WIDTH;
        *height = ROCK_HEIGHT;
        return rock;
    }
}

void DinoCactus_Run() {
    int cactus_x = 128;
    int frame = 0;
    int dino_y = 15;
    int dino_x = 15;
    int is_jumping = 0;
    int jump_height = 0;
    int delay_time = 30000;
    int min_delay = 3000;
    int speed_increment_interval = 500;
    int anim_speed = 4;
    int score = 0;
    char score_str[5];

    // 첫 번째 장애물 초기화
    int obstacle_width, obstacle_height;
    const uint8_t* current_obstacle = get_random_obstacle(&obstacle_width, &obstacle_height);
    int next_obstacle_distance = get_random_offset();

    while (1) {
        OLED_ClearBuffer(&myDevice);

        // 점프 조건 확인
        if (!is_jumping && (cactus_x > (dino_x + DINO_WIDTH)) && (cactus_x - (dino_x + DINO_WIDTH) <= 5)) {
            is_jumping = 1;
            jump_height = 0;
        }

        // 점프 로직
        if (is_jumping) {
            if (jump_height < 5) {
                dino_y -= 2;
            } else if (jump_height < 18) {
                dino_y -= 1;
            } else if (jump_height < 22) {
                dino_y += 0;
            } else if (jump_height < 35) {
                dino_y += 1;
            } else if (jump_height < 40) {
                dino_y += 2;
            } else {
                is_jumping = 0;
                jump_height = 0;
                dino_y = 15;
            }
            jump_height++;
        }

        // 공룡 애니메이션
        OLED_MoveTo(&myDevice, dino_x, dino_y);
        if ((frame / anim_speed) % 2 == 0) {
            OLED_PutBmp(&myDevice, DINO_WIDTH, DINO_HEIGHT, dinorun1);
        } else {
            OLED_PutBmp(&myDevice, DINO_WIDTH, DINO_HEIGHT, dinorun2);
        }

        // 장애물 이동
        if (cactus_x >= 0) {
            OLED_MoveTo(&myDevice, cactus_x, 32-obstacle_height);
            OLED_PutBmp(&myDevice, obstacle_width, obstacle_height, current_obstacle);
        }

        // 충돌 감지
        if (cactus_x >= dino_x && cactus_x <= (dino_x + DINO_WIDTH) && dino_y >= 13) {
            OLED_ClearBuffer(&myDevice);
            OLED_MoveTo(&myDevice, 30, 10);
            OLED_DrawString(&myDevice, "GAME OVER");
            OLED_Update(&myDevice);
            xil_printf("GAME OVER!\n");

            while (1) { usleep(100000); }
        }

        // 장애물 이동 (1픽셀씩)
        cactus_x -= 1;

        // 장애물이 왼쪽 끝에 도달하면 새로운 장애물 생성
        if (cactus_x < -obstacle_width) {
            cactus_x = 128 + next_obstacle_distance;
            current_obstacle = get_random_obstacle(&obstacle_width, &obstacle_height);
            next_obstacle_distance = get_random_offset();
        }

        // 속도 증가 로직
        if (frame % speed_increment_interval == 0 && delay_time > min_delay) {
            delay_time = (int)(delay_time * 0.9);
        }

        // 점수 증가 (50 프레임마다 1점 증가)
        if (frame % 15 == 0 && score < 9999) {
            score += 1;
        }

        // 점수를 네 자리로 표시
        sprintf(score_str, "%04d", score);
        OLED_MoveTo(&myDevice, 90, 0);
        OLED_DrawString(&myDevice, score_str);

        OLED_Update(&myDevice);
        usleep(delay_time);
        frame++;
    }
}

이제 이전에 사용했던 키패드나 버튼을 활용하여 직접 공룡을 조작할 수 있도록 설계하고 공룡 게임 구현 프로젝트를 마무리할 예정입니다.