본문 바로가기
개발 일지

C언어 콘솔로 간단한 RPG 게임 만들기 - 03. UI 추가

by PrintedLove 2020. 2. 2.

 

안녕하세요 Printed Love입니다. 이번에는 UI를 만들어보았습니다.

꽤 오랫만에 글을 썼는데요. 설날 끝나고 이것저것 할게 넘 많았어요... 하하

일단 영상 보시죠

 

 

 

영상에 대충이나마 '상태창'이 생긴것을 볼 수 있습니다.

참고로, H, M, L 버튼을 이용하여 HP와 MP, Exp값을 테스트 할 수 있습니다.

소스코드는 무려... 300줄이나 됩니다!

 

// [C Game] Simple RPG
// made by "PrintedLove"
// https://printed.tistory.com/
#include <stdio.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
#define EXPP 1.01

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
    
    char name[32];
    int lv = 1;
    unsigned long int exp = 0, expi = 100;
    int hpm = 100, hp = hpm;
    int mpm = 50, mp = mpm;
    unsigned int gen_tick;
    
    float jump_index = 2;
    short leg_m = 1;				//leg motion
    unsigned int leg_tick = 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[]);
void DrawNumber(int x, int y, int num);

int NumLen(int num);
int StrLen(char str[]);

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

int main() {
	
	srand((unsigned int)time(NULL));
	SetConsole();
	
	printf("Enter your name: ");
	scanf("%[^\n]s", character.name);
	
	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_floor, '=', MAP_X_MAX);
}

void ControlUI() {
	
	DrawSprite(1, 22, MAP_X_MAX, 1, wall_floor);	//draw floor
	
	DrawSprite(2, 2, 3, 1, "[ '");	//draw name, lv, exp
	DrawSprite(5, 2, StrLen(character.name), 1, character.name);
	int len = 5 + StrLen(character.name);
	DrawSprite(len, 2, 7, 1, "' - LV.");
	len += 7;
	DrawNumber(len, 2, character.lv);
	len += NumLen(character.lv);
	DrawSprite(len, 2, 2, 1, " (");
	len += 2;
	int expPer = roundf(character.exp * 100 / character.expi);
	if (!expPer) {
		
		EditMap(len, 2, '0');
		len ++;
	} else {
		
		DrawNumber(len, 2, expPer);
		len += NumLen(expPer);
	}
	DrawSprite(len, 2, 5, 1, " %) ]");
	
	DrawSprite(2, 4, 7, 1, "[ HP ] ");	//draw HP
	DrawNumber(9, 4, character.hp);
	EditMap(10 + NumLen(character.hp), 4, '/');
	DrawNumber(12 + NumLen(character.hp), 4, character.hpm);
	
	DrawSprite(2, 5, 7, 1, "[ MP ] ");	//draw MP
	DrawNumber(9, 5, character.mp);
	EditMap(10 + NumLen(character.mp), 5, '/');
	DrawNumber(12 + NumLen(character.mp), 5, character.mpm);
}

void ControlCharacter() {
	
	unsigned int tick = GetTickCount();
	bool move = FALSE;
	//LV up
	if (character.exp >= character.expi) {
		
		character.exp = 0;
		character.lv ++;
		character.expi += character.lv * 10;
	}
	//hp, mp
	if (character.gen_tick + 1000 < tick) {
		
		character.gen_tick = tick;
		character.hp += roundf(character.hpm * 0.01);
		character.mp += roundf(character.mpm * 0.05);
	}
	if (character.hp > character.hpm)
		character.hp = character.hpm;
	if (character.mp > character.mpm)
		character.mp = character.mpm;
	//keyboard
	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.jump_index = 0;
		
	if (GetAsyncKeyState(0x48) & 0x8000)	//H
		character.hp -= 1;
		
	if (GetAsyncKeyState(0x4D) & 0x8000)	//M
		character.mp -= 1;
		
	if (GetAsyncKeyState(0x4C) & 0x8000)	//L
		character.exp += 25;
	//jump
	character.y -= 2;
	if (character.y < 19) {
		
		if (character.jump_index < 4)
			character.jump_index += 0.3;
			
		character.y += floor(character.jump_index);
		
		if (character.y > 19)
			character.y = 19;
	} else {
		
		character.jump_index = 0;
		character.y = 19;
	}
	//leg tick
	if (tick > character.leg_tick + 90) {			
		
		character.leg_tick = tick;
		
		if (move == TRUE)
			character.leg_m++;
		else
			character.leg_m = 0;
			 
		if (character.leg_m > 3)
			character.leg_m = 1;
	}
	//sprite
	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]);
	}
}

void DrawNumber(int x, int y, int num) {
	
	int tmp = num;
	short len = NumLen(tmp);
    short cnt = len;
    char str[len];
    
    do {
        cnt--;
        str[cnt] = (char)(tmp % 10 + 48);
        tmp /= 10;
    } while (tmp != 0);
    
    DrawSprite(x, y, len, 1, str);
}

int NumLen(int num) {
	
	int tmp = num;
	short len = 0;
	
	while(tmp != 0) {
    
        tmp /= 10;
        len++;
    }
    
    return len;
}

int StrLen(char str[]) {
	
	int len = 0;
	
	for(int i = 0; str[i]; i++)
        len ++;
    
    return len;
}

 

처음 실행시 이름을 받는 scanf가 생겼습니다. 이름을 입력하면, 게임이 시작됩니다.

나중에 선택지로 성향이나 초기스탯을 설정해볼까 생각하고 있습니다.

 

크게 바뀐것은 오른쪽 상단에 이름, 레벨, 경험치% 그리고 HP와 MP를 보여주는 간략한 상태창이 생긴 것입니다.

그에 맞추어 Character구조체에 멤버들이 잔뜩 추가되었습니다.

일부 멤버들과 함수들의 이름이 이전 코드와 달라진 점 참고해 주세요.

 

이번에 숫자(int)값을 화면에 그리기 위한 함수가 추가되었는데요 다른건 거의 노가다나 마찬가지니 이놈만 집고 넘어가도록 하겠습니다.

 

void DrawNumber(int x, int y, int num) {
	
	int tmp = num;
	short len = NumLen(tmp);
    short cnt = len;
    char str[len];
    
    do {
        cnt--;
        str[cnt] = (char)(tmp % 10 + 48);
        tmp /= 10;
    } while (tmp != 0);
    
    DrawSprite(x, y, len, 1, str);
}

 

 이름하여 DrawNumber 함수입니다. x, y좌표와 숫자를 입력 받으면 해당 위치에 왼쪽 정렬 기준으로 숫자를 화면에 그려줍니다.

 이전 시간에 만든 DrawSprite함수를 이용하여 쉽게 구현했습니다.  숫자를 받아 자릿수 크기의 문자열을 만들어 아스키코드로 변환해 집어넣는 간단한 기능을 수행합니다.

 상태창은 이 함수와 뒤에 추가된 문자열의 길이와 숫자의 자릿수를 반환해주는 StrLen, NumLen 함수를 조합하여 만들었습니다. 그저 노가다... ㄷㄷ

 다음 글에서는 상태창을 좀더 손보고 공격 기능을 넣어볼까 합니다.

댓글