2019/12/10에 해당하는 글 4

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

댓글()