본문 바로가기
개발 일지

C언어 콘솔로 간단한 RPG 게임 만들기 - 02. 캐릭터 드로우와 점프

by PrintedLove 2020. 1. 24.

 

 

 여러분 새해 복 많이 받으세요! Printed Love입니다!

 어제 오늘 열심히 작업한 분량을 들고 왔습니다. 일단 영상을 보시죠.

 

 

 아래는 소스 코드 입니다.

 

// [C Game] Simple RPG
// made by "PrintedLove"
// https://printed.tistory.com/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <windows.h>
#define MAP_X_MAX 96
#define MAP_Y_MAX 32
#define FALSE 0
#define TRUE 1

typedef struct _Character {
    
    short x = MAP_X_MAX / 2 + 1, y = MAP_Y_MAX / 2 + 1;
    short size_x = 3, size_y = 3;
    char sprite[10] = " 0 (|)_^_";
    
    bool direction = TRUE;			//true=right, false=left
    
    float t_jump = 2;
    short leg_m = 1;				//leg motion
    unsigned int t_leg = 0;
}Character;

Character character;

void SetConsole();
void SetUI();
void ControlUI();
void ControlCharacter();

void FillMap(char str[], char str_s, int max_value);
void EditMap(int x, int y, char str);
void DrawSprite(int x, int y, int size_x, int size_y, char spr[]);

char wall[MAP_X_MAX];
char mapData[MAP_X_MAX * MAP_Y_MAX];

int main() {
	
	srand((unsigned int)time(NULL));
	
	SetConsole();
	SetUI();
	unsigned int system_tick = GetTickCount();
	
	while (TRUE) {
		
		if (system_tick + 30 < GetTickCount()) {
			
			system_tick = GetTickCount();
			
			FillMap(mapData, ' ', MAP_X_MAX * MAP_Y_MAX);
		
			ControlCharacter();
			ControlUI();
			
			printf("%s", mapData);				//update map
		}
	}
	
	return 0;
}

void SetConsole() {

	system("mode con:cols=96 lines=32");
	system("title RPG test");
	
	HANDLE hConsole;
    CONSOLE_CURSOR_INFO ConsoleCursor;
    hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    
    ConsoleCursor.bVisible = 0;
    ConsoleCursor.dwSize = 1;
    SetConsoleCursorInfo(hConsole , &ConsoleCursor);
}

void SetUI() {
		
	FillMap(wall, '=', MAP_X_MAX);
}

void ControlUI() {
	
	DrawSprite(1, 22, MAP_X_MAX, 1, wall);
}

void ControlCharacter() {
	
	unsigned int tick = GetTickCount();
	bool move = FALSE;
	
	if (GetAsyncKeyState(VK_LEFT) & 0x8000 && character.x > 1) {
		
		character.x -= 1;
		character.direction = FALSE;
		move = TRUE;
	}
	
	if (GetAsyncKeyState(VK_RIGHT) & 0x8000 && character.x < MAP_X_MAX - 2) {
		
		character.x += 1;
		character.direction = TRUE;
		move = TRUE;
	}
	
	if (GetAsyncKeyState(VK_UP) & 0x8000 && character.y > 1 && character.y == 19)
		character.t_jump = 0;

	//jump
	character.y -= 2;
	
	if (character.y < 19) {
		
		if (character.t_jump < 4)
			character.t_jump += 0.3;
			
		character.y += floor(character.t_jump);
		
		if (character.y > 19)
			character.y = 19;
	} else {
		
		character.t_jump = 0;
		character.y = 19;
	}

	//sprite
	if (tick > character.t_leg + 60) {			
		
		character.t_leg = tick;
		
		if (move == TRUE)
			character.leg_m++;
		else
			character.leg_m = 0;
			
		if (character.leg_m > 3)
			character.leg_m = 1;
	}
	
	character.sprite[4] = '|'; character.sprite[6] = '_'; character.sprite[8] = '_';
	
	if (character.direction == TRUE) {
		
		character.sprite[3] = '('; character.sprite[5] = 'o';
		
		switch (character.leg_m) {
			
			case 1: character.sprite[4] = 'I'; character.sprite[6] = '.'; character.sprite[8] = '-'; break;
			case 2: character.sprite[6] = '\''; character.sprite[8] = '_'; break;
			case 3: character.sprite[6] = '.'; character.sprite[8] = '_'; break;
			default: break;
		}
	} else {
		
		character.sprite[3] = 'o'; character.sprite[5] = ')';
		
		switch (character.leg_m) {
			
			case 1: character.sprite[4] = 'I'; character.sprite[6] = '-'; character.sprite[8] = '.'; break;
			case 2: character.sprite[6] = '_'; character.sprite[8] = '\''; break;
			case 3: character.sprite[6] = '_'; character.sprite[8] = '.'; break;
			default: break;
		}
	}
	
	DrawSprite(character.x, character.y, character.size_x, character.size_y, character.sprite);
}

void FillMap(char str[], char str_s, int max_value) {
	
	for (int i = 0; i < max_value; i++)
		str[i] = str_s;
}

void EditMap(int x, int y, char str) {
	
	if (x > 0 && y > 0 && x - 1 < MAP_X_MAX && y - 1 < MAP_Y_MAX)
		mapData[(y - 1) * MAP_X_MAX + x - 1] = str;
}

void DrawSprite(int x, int y, int size_x, int size_y, char spr[]) {
	
	for (int i = 0; i < size_y; i++) {
		
		for (int n = 0; n < size_x; n++)
			EditMap(x + n, y + i, spr[i * size_x + n]);
	}
}

 

 무려 194줄이라는 무지막지한 양으로 늘어났습니다 ㄷㄷ

 그리고 첫번째 글과 많은것들이 달라졌습니다. 참고해 주세요.

 

 중력이라던지 캐릭터 이동 등은 저번 간단한 수식이라 스킵하고 제일 힘든! 전 힘들었습니다요 그래픽 부분을 설명해 드리겠습니다.

 일단 세가지만 짚고 넘어갑시다.

 코드 초반부를 보시면, 구조체 Character가 보입니다. 이 안에는 x, y 좌표와 sprite라는 char멤버가 있습니다.

 그리고 mapData라는 화면 크기의 문자열이 있습니다. 이 문자열은 30ms 마다 초기화되어 화면을 갱신합니다.

 마지막으로 소스코드 끝부분의 EditMap 함수와 DrawSprite 함수를 주목해 주세요.

 

 캐릭터가 화면에 그려지는 과정은 간단합니다.

 우선 Character의 멤버인 x, y좌표값은 캐릭터의 위치 정보로 현재 위치를 나타냅니다. 키보드 조작시 이 값이 변경됩니다.

 그리고 sprite 문자열은 캐릭터의 그래픽 정보입니다. 아스키 코드로 모든 그래픽을 처리하는 게임 특성상, 따로 외부파일을 끌어올 필요 없이 코드 내부에서 변수처럼 사용할 수 있습니다.

 EditMap 함수에 x, y좌표값과 char 값을 넣으면 좌표에 해당하는 mapData 인덱스 값을 매개변수 char 값으로 수정해 줍니다.

 DrawSprite 함수는 EditMap 함수를 for문 처리해 스프라이트 x, y사이즈와 좌표를 넣으면 해당 좌표를 기준으로 x * y반경의 mapData 값을 수정해 줍니다.

 이 두 함수로 마치 일반 게임에서 이미지 파일을 그리듯이 화면위에 그래픽을 구현할 수 있습니다.

 

 

엉성하게나마 캐릭터 그래픽을 콘솔창에 띄우는 원리를 그림으로 그려보았습니다. 엉성 그 자체

 

다음 글에서는 UI 파트를 손볼 것 같습니다

 

 

댓글