C++ Sqrt 알고리즘 함수

C++|2020. 1. 10. 03:29

'C++' 카테고리의 다른 글

Console Project/ 잠시 쉬어갑니다.  (0) 2019.12.15
JSON for Modern C++  (0) 2019.12.14
Console Project/7. TileMap  (0) 2019.12.14
Console Project/6. 키 입력  (0) 2019.12.12
Console Project/5. 문자 출력  (0) 2019.12.10

댓글()

Console Project/ 잠시 쉬어갑니다.

C++|2019. 12. 15. 14:31

다른 더 재미난 것을 하기 위해 이 프로젝트는 잠시 쉬어갑니다.

'C++' 카테고리의 다른 글

C++ Sqrt 알고리즘 함수  (0) 2020.01.10
JSON for Modern C++  (0) 2019.12.14
Console Project/7. TileMap  (0) 2019.12.14
Console Project/6. 키 입력  (0) 2019.12.12
Console Project/5. 문자 출력  (0) 2019.12.10

댓글()

JSON for Modern C++

C++|2019. 12. 14. 22:15

https://github.com/nlohmann/json

'C++' 카테고리의 다른 글

C++ Sqrt 알고리즘 함수  (0) 2020.01.10
Console Project/ 잠시 쉬어갑니다.  (0) 2019.12.15
Console Project/7. TileMap  (0) 2019.12.14
Console Project/6. 키 입력  (0) 2019.12.12
Console Project/5. 문자 출력  (0) 2019.12.10

댓글()

Console Project/7. TileMap

C++|2019. 12. 14. 22:12

기약없는 휴식...

작성 중...

 

'C++' 카테고리의 다른 글

Console Project/ 잠시 쉬어갑니다.  (0) 2019.12.15
JSON for Modern C++  (0) 2019.12.14
Console Project/6. 키 입력  (0) 2019.12.12
Console Project/5. 문자 출력  (0) 2019.12.10
Console Project/4.5 중간보고  (0) 2019.12.10

댓글()

Console Project/6. 키 입력

C++|2019. 12. 12. 17:47

 키의 입력은 GetAsyncKeyState()함수를 통해 감지한다. 이 함수로는 단순히 키가 눌렸는지 아닌지만 체크하는 용도로 사용할 것이다.

// Input.h
class GameEngine;
class Input 
{
private:
    static bool currentStates[256];
    static bool prevStates[256];

    static void BeginUpdate();

    friend GameEngine;

public:
    static bool IsKeyDownOnce(int keyCode);
    static bool IsKeyDown(int keyCode);
    static bool IsKeyUpOnce(int keyCode);
};

// Input.cpp
bool Input::currentStates[256];
bool Input::prevStates[256];

void Input::BeginUpdate()
{
    for (int i = 0; i < 256; i++) {
        prevStates[i] = currentStates[i];
        currentStates[i] = GetAsyncKeyState(i) & 0x8000;
    }
}

bool Input::IsKeyDownOnce(int keyCode)
{
    return currentStates[keyCode] && !prevStates[keyCode];
}

bool Input::IsKeyDown(int keyCode)
{
    return currentStates[keyCode];
}

bool Input::IsKeyUpOnce(int keyCode)
{
    return !currentStates[keyCode] && prevStates[keyCode];
}

사용방법  

 키 입력을 체크하기 전에 BeginUpdate함수를 호출하여야 한다.

void Game::Update(float deltaTime)
{
    Input::BeginUpdate();

    if (Input::IsKeyDownOnce(VK_LEFT)) {
        this->dir = 1;
        this->pos.x -= 16;
    }
    else if (Input::IsKeyDownOnce(VK_RIGHT)) {
        this->dir = 2;
        this->pos.x += 16;
    }
}

GetAsyncKeyState를 그대로 사용하므로 함수의 파라메터-키 코드는 https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes링크 참조.

'C++' 카테고리의 다른 글

JSON for Modern C++  (0) 2019.12.14
Console Project/7. TileMap  (0) 2019.12.14
Console Project/5. 문자 출력  (0) 2019.12.10
Console Project/4.5 중간보고  (0) 2019.12.10
Console Project/4. GameLoop  (0) 2019.12.10

댓글()

Console Project/5. 문자 출력

C++|2019. 12. 10. 20:30

 화면에 문자를 출력하기 위해서는 문자에 해당하는 이미지가 있어야 한다. 문자를 입력 받아 원하는 위치에 문자에 해당하는 이미지를 그려넣어야 한다.

아스키 문자 출력

한땀 한땀 직접 그려 넣었다.

 화면에 출력하는 아스키 문자는 0x20 ~ 0x7f 범위 내에 존재한다. 입력받은 문자가 범위 내에 있으면 아스키 문자 이미지에서 해당하는 문자를 그리면 된다.

// ascii code
if (c >= 0x20 && c < 0x7f) {
    int index = c - 0x20;
    int cx = index % 8;
    int cy = index / 8;

    Sprite charSpr = asciiSprite->GetSprite(cx, cy);
    charSpr.DrawTo(buffer, { x, y });
}

한글 문자 출력

초.중.종성을 조합하여 사용.

 한글의 경우에는 좀 복잡한데 유니코드에서의 한글은 0xAC00 부터 0xd7a4까지 자주 사용하는 한글의 조합을 전부 할당해 놓았다. 가갸걔거겨계... 이런식으로 말이다. 이를 출력하기 위해서는 대략 1만여개의 한글 이미지가 필요하다.

 이 대신 결과는 좀 엉성하지만 초.중.종성의 조합으로 한글을 그리는 방법이 있다.

 한글은 유니코드에서 0xac00 ~ 0xd7a4 범위 내에 있으며 초.중.종성을 분해하고 각각에 맞는 이미지를 같이 출력한다.

if (c >= 0xac00 && c < 0xd7a4) {

    // 초중종성 분해
    int in_char = c - 0xAC00;
    int cho = in_char / (0x0015 * 0x001C);
    int jung = (in_char / 0x001C) % 0x0015;
    int jong = in_char % 0x001C;

    if (jung >= 8 && jung < 20) {
        cho += 20;
    }

    koreanSprite->DrawTo(buffer, { x, y }, cho % 10, cho / 10);
    koreanSprite->DrawTo(buffer, { x, y }, jung % 10, (jung / 10) + 4);
    koreanSprite->DrawTo(buffer, { x, y }, jong % 10, (jong / 10) + 7);
}

 

아주 못 읽을 정도는 아니다.

문장 출력

 문장을 출력할 때에는 문자 하나하나가 알맞는 위치에 출력되어야 한다. 이를 위해서는 각 글자의 폭을 구한 다음 출력할 때마다 위치를 이동시키면 된다.

TCHAR* input = "안녕하세요. Waker입니다. 가나다라마바사~";
RECT rect = { 0, 0, 100, 100 };

int x = rect.left;
int y = rect.top;

int length = wcslen(text);
	
for (int i = 0; i < length; i++) {
    TCHAR c = input[i];
    
    if (IsAscii(c)) {
        
        // 입력하려는 문자가 폭을 넘어서면
        // 다음 줄로 이동한다.
        if (x + 8 > rect.right) {
            x = rect.left;
            y += 16;
        }
        
        // 문자를 입력하는 범위를 넘어서면
        // 출력을 중단한다.
        if (y > rect.bottom) {
           break;
        }
        
        // Output ascii
        Sprite s = ascii->GetTextSprite(c);
        s->DrawTo(x, y);
        
        // 다음 위치로 이동.
        x += 8;
        
    }
    
    if (IsKorean(c)) {
        ...
    }
}

'C++' 카테고리의 다른 글

Console Project/7. TileMap  (0) 2019.12.14
Console Project/6. 키 입력  (0) 2019.12.12
Console Project/4.5 중간보고  (0) 2019.12.10
Console Project/4. GameLoop  (0) 2019.12.10
Console Project/3. 이미지  (0) 2019.12.10

댓글()

Console Project/4.5 중간보고

C++|2019. 12. 10. 14:03

간단히 타일과 캐릭터의 애니메이션을 출력한다.

'C++' 카테고리의 다른 글

Console Project/6. 키 입력  (0) 2019.12.12
Console Project/5. 문자 출력  (0) 2019.12.10
Console Project/4. GameLoop  (0) 2019.12.10
Console Project/3. 이미지  (0) 2019.12.10
Console Project/2. 출력 버퍼  (0) 2019.12.09

댓글()

Console Project/4. GameLoop

C++|2019. 12. 10. 13:59

 게임 루프는 일정한 입, 출력 속도를 보장하고, 예기치 못한 렉에 유연하게 대응할 수 있어야 한다.

Game 클래스

Game클래스는 게임의 초기화 및 업데이트, 렌더링을 담당하는 게임의 최상위 클래스이다. 모든 게임에 대한 코드는 해당 클래스 내에서 작업 하게 된다.

class Game
{
private:
    int width;
    int height;

    bool exit;
public:
    Game(int width, int height);
    void Release();
    void Update(float deltaTime);
    void Render();

    inline bool IsExit() { return exit; }
};

GameLoop

void main() {
..

Game game(SCREEN_WIDTH, SCREEN_HEIGHT);
int targetFPS = 25;
int targetFrameMS = 1000 / targetFPS;

ULONGLONG lastTime = GetTickCount64();

while (true) {
    
    ULONGLONG current = GetTickCount64();
    ULONGLONG elapsed = current - lastTime;

    game.Update(elapsed / 1000.f);

    if (game.IsExit()) {
        break;
    }

    game.Render();

    if (targetFrameMS > elapsed) {
        Sleep(targetFrameMS - elapsed);
    }
    
    lastTime = current;
}

game.Release();

..

'C++' 카테고리의 다른 글

Console Project/5. 문자 출력  (0) 2019.12.10
Console Project/4.5 중간보고  (0) 2019.12.10
Console Project/3. 이미지  (0) 2019.12.10
Console Project/2. 출력 버퍼  (0) 2019.12.09
Console Project/1. 화면  (0) 2019.12.09

댓글()

Console Project/3. 이미지

C++|2019. 12. 10. 13:39

 문자로 이루어진 이미지를 불러오고 화면에 출력하는 방법을 진행한다. 이 프로젝트의 게임의 해상도가 높지 않기 때문에 아스키 아트를 확대하거나 축소하는 변형은 작업하지 않고, 16x16 사이즈의 픽셀 아트 이미지를 구해 이미지와 해상도가 1:1 대응하도록 작업할 것이다.

이미지

 아스키 아트 이미지는 다음과 같이 정의할 수 있다.

class Image {
private:
    TCHAR* image;
    bool* mask;
    int width;
    int height;
    
public:
    inline int GetWidth() { return this->width; }
    inline int GetHeight() { return this->height; }
    inline TCHAR GetPixel(int x, int y) { return this->image[y * this->width + x]; }
    inline bool GetMask(int x, int y) { return this->mask[y * this->width + x]; }
}

 이미지를 생성하는 방법

 이미지를 생성하는 방법은 여러가지가 있는데 그 중 크게 두가지가 있다. 웹에서 Ascii art generator를 통해 이미지를 아스키 아트로 변환한 후, 텍스트 파일로 저장하여 이를 불러오는 방법과 프로그램 내에서 이미지 파일을 텍스트로 직접 변환하는 방법이다.

 이 프로젝트에서는 CImg라는 라이브러리를 사용하여 직접 이미지를 불러와 아스키 아트로 변환하도록 한다.

const TCHAR map[] = TEXT(" .,:;ox%#@");
int mapSize = 10;

// create image from file
CImg<int> temp(filename);
    
int width = temp.width();
int height = temp.height();

CImg<int> grayWeight(width, height, 1, 1, 0),
  imgR(width, height, 1, 3, 0),
  imgG(width, height, 1, 3, 0),
  imgB(width, height, 1, 3, 0);

CImg<int> mask(width, height, 1, 1, 0);

// for all pixels x,y in image
cimg_forXY(temp, x, y) {
  imgR(x, y, 0, 0) = temp(x, y, 0, 0),    // Red component of image sent to imgR
  imgG(x, y, 0, 1) = temp(x, y, 0, 1),    // Green component of image sent to imgG
  imgB(x, y, 0, 2) = temp(x, y, 0, 2);    // Blue component of image sent to imgB

  // Separation of channels
  int R = (int)temp(x, y, 0, 0);
  int G = (int)temp(x, y, 0, 1);
  int B = (int)temp(x, y, 0, 2);

  // Real weighted addition of channels for gray
  int grayValueWeight = (int)(0.299 * R + 0.587 * G + 0.114 * B);

  // saving píxel values into image information
  grayWeight(x, y, 0, 0) = grayValueWeight;
  mask(x, y, 0, 0) = (R == 255 && G == 0 && B == 255) ? 0 : 1;
}

TCHAR *image = new TCHAR[width * height];
bool *m = new bool[width * height];

for (int h = 0; h < height; h++) {
  for (int w = 0; w < width; w++) {
    unsigned char v = grayWeight(w, h, 0, 0);

    image[h * width + w] = map[(v) *mapSize / 256];
    m[h * width + w] = mask(w, h, 0, 0) == 1;
  }
}

return new Image(image, m, width, height);

 이미지를 그레이스케일 이미지로 변환 후, 밝기에 따라 map의 문자를 할당하였다. 색상이 255, 0, 255일 경우 투명 처리 되도록 마스크를 적용하였다.

CImg로 그레이스케일로 변환하는 코드는 http://obsessive-coffee-disorder.com/rgb-to-grayscale-using-cimg/ 참조.

이미지를 그리는 방법

 이미지를 원하는 위치에, 또한 이미지를 자유롭게 잘라 낼 수 있어야 한다. 다만 확대 및 축소는 작업하지 않는다.

void RenderBuffer::Draw(Image* data, const POINT& leftTop, const RECT& imageRect)
{
    const int left = leftTop.x;
    const int top = leftTop.y;
    const int width = min(data->GetWidth(), imageRect.right) - imageRect.left;
    const int height = min(data->GetHeight(), imageRect.bottom) - imageRect.top;

    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int sx = x + imageRect.left;
            int sy = y + imageRect.top;
            int dx = x + left;
            int dy = y + top;

            if (dx < 0 || dx >= this->width || dy < 0 || dy >= this->height) {
                continue;
            }

            //int sIndex = sy * data.imageWidth + sx;
            int dIndex = dy * this->width + dx;

            if (!data->GetMask(sx, sy)) {
                continue;
            }

            buffer[dIndex].Char.UnicodeChar = data->GetPixel(sx, sy);
        }
    }
}

잘라낸 이미지의 왼쪽 위를 기준으로 imageRect 범위 만큼 픽셀 하나씩 그려가는 코드이다.

Sprite, TiledSprite

스프라이트는 이미지와 이미지를 그릴 범위 값을 가지고 있는 단순한 클래스이다. TiledSprite도 마찬가지로 이미지를 타일마다 잘라 원하는 타일을 그릴 수 있도록 하는 클래스이다.

class Sprite
{
private:
    Image* image;
    RECT rect;

public:
    Sprite(Image* image)
        :image(image)
    {
        rect = RECT {
            0, 0, image->GetWidth(), image->GetHeight()
        };
    }
    
    Sprite(Image* image, const RECT& rect)
        :image(image)
        ,rect(rect)
    {
    }
    
    void DrawTo(RenderBuffer* buffer, const POINT& pos)
    {
        buffer->Draw(image, pos, rect);
    }
};

class TiledSprite
{
private:
    Image* image;
    int col;
    int row;
    int cellWidth;
    int cellHeight;

public:
    TiledSprite(Image* image, int col, int row)	
        :image(image)
        ,col(col)
        ,row(row)
    {
        this->cellWidth = this->image->GetWidth() / col;
        this->cellHeight = this->image->GetHeight() / row;
    }
    
    void DrawTo(RenderBuffer* buffer, const POINT& pos, int colNum, int rowNum)
    {
        RECT rect = {
            colNum * this->cellWidth,
            rowNum * this->cellHeight,
            (colNum + 1) * this->cellWidth,
            (rowNum + 1) * this->cellHeight
        };

        buffer->Draw(this->image, pos, rect);
    }
};

 

'C++' 카테고리의 다른 글

Console Project/4.5 중간보고  (0) 2019.12.10
Console Project/4. GameLoop  (0) 2019.12.10
Console Project/2. 출력 버퍼  (0) 2019.12.09
Console Project/1. 화면  (0) 2019.12.09
Console Project  (0) 2019.12.09

댓글()

Console Project/2. 출력 버퍼

C++|2019. 12. 9. 19:22

 문자 출력이 가능한 콘솔 창에서 2D 이미지를 출력하기 위해서는 이미지를 ASCII Art로 변환하여 출력해야 한다. 하지만 이미지의 픽셀 개수 만큼의 길이를 가진 문자열을 printf 혹은 std::cout 등으로 출력하는 방식은 대체로 느리다. 여러 이미지를 하나하나 화면에 그리다 보면 그려지는 순서가 보일 지경이 될 것이다.

 이를 개선하기 위해 출력 버퍼에 여러장의 이미지를 그린 후, 버퍼에 바뀜이 있을 때 화면을 갱신(출력)하면 보다 깔끔하게 출력이 가능할 것이다.

출력 버퍼

 ASCII Art는 텍스트로 이루어져 있으므로 char 배열로 buffer를 만든다.

int width = 200;
int height = 200;
char *buffer = new char[width * height];

// clear
for (int i = 0; i < width * height; i++) {
    buffer[i] = ' ';
}

// output to console
gotoXY(0, 0);
for (int i = 0; i < height; i++) {
    char line[width + 1];
    for (int j = 0; j < width; j++) {
        line[j] = buffer[i * width + j];
    }
    line[width] = '/0'; // end of string
    std::cout << line << std::endl;
}

이제 원하는 위치에 이미지를 버퍼에 그려 화면에 출력 할 수 있다. 아래는 이미지를 버퍼에 쓰는 작업을 하는 코드이다.

int posX = 10;
int posY = 25;

int imageWidth = image.width;
int imageHeight = image.height;
char* asciiArt = imageToAscii(image);

// draw image to buffer
for (int y = 0; y < iamgeHeight; y++) {
    for (int x = 0; x < imageWidth; x++) {
    
        // 이미지 픽셀에 대한 버퍼의 위치를 구한다.
        int bufferX = x + posX;
        int bufferY = y + posY;
        
        // 버퍼 밖으로 그리게 될 경우는 무시한다.
        if (bufferX < 0 || bufferX >= bufferWidth || bufferY < 0 || bufferY >= bufferHeight) {
            continue;
        }
        
        // 이미지의 픽셀 위치와 버퍼의 픽셀 위치를 구한다.
        int imageIndex = y * imageWidth + x;
        int bufferIndex = bufferY * bufferWidth + bufferX;
        
        // 이미지의 픽셀을 버퍼의 픽셀에 대입한다.
        buffer[bufferIndex] = asciiArt[imageIndex];
    }
}

// Output to Console
...

WriteConsoleOutput

 위의 코드로 이미지를 그릴 경우 초당 몇 ~ 십몇 프레임이상 그리지 못할 것이다. 이때 빠르게 콘솔에 출력하는 함수가 WriteConsoleOutput이다. 자세한 내용은 링크 참조.

2019/12/09 - [C++] - WriteConsoleOutput - Console 화면에 빠르게 출력하는 함수.

 

WriteConsoleOutput를 사용하려면 CHAR_INFO 배열이 필요한데 버퍼를 아예 CHAR_INFO 배열로 사용할 수 있다.

...

CHAR_INFO* buffer = new CHAR_INFO[width * height];

// clear
for (int i = 0; i < width * height; i++) {
    buffer[i].Char.AsciiChar = ' ';
    buffer[i].Attribute = 7; // white
}

// output to console
COORD pos = { 0, 0 }, size = { width, height };
SMALL_RECT rect = { 0, 0, width, height };
WriteConsoleOutput(GetStdHandle(STD_OUTPUT_HANDLE), buffer, size, pos, &rect);

클래스

기능을 클래스화하여 사용한다. 자세한 내용은 링크.

https://github.com/wakeup5/Console-Project/blob/master/Project/RenderBuffer.h

https://github.com/wakeup5/Console-Project/blob/master/Project/RenderBuffer.cpp

'C++' 카테고리의 다른 글

Console Project/4. GameLoop  (0) 2019.12.10
Console Project/3. 이미지  (0) 2019.12.10
Console Project/1. 화면  (0) 2019.12.09
Console Project  (0) 2019.12.09
WriteConsoleOutput - Console 화면에 빠르게 출력하는 함수.  (0) 2019.12.09

댓글()