<aside> 💡
GameLoop
<aside> 💡
플레이어 객체, 적 객체, 배경이미지 등의 리소스들은 초기화 단계에서 생성
</aside>
Tick 처리
60FPS란 초당 60번 게임 로직이 업데이트된다는 뜻
1초는 1000밀리초(ms)이고, 이를 60으로 나누면 약 16.67ms
한 게임 프레임에 할당되는 시간 = 16.67ms
만약 프레임이 그보다 더 잘나오면 그 사이의 위치를 보간
InterpolatePosition(fAlpha)
경과시간(Delta Time)
QCGetCounter()
QCMeasureElapsedTick(CurCounter, m_PrvCounter) 로 DeltaTime 계산GetTickCount64()
QCGetCounter() 는 카운터값이므로 ms 단위가 아닌데 GetTickCount64() 는 ms단위를 바로 리턴하므로 전체 게임 실행 시간이나 타임스탬프를 기록할 때 유용OnGameFrame(CurTick) 에서 CurTick을 통해 언제 로직처리가 발생했는지 활용프레임이 60fps보다 높을 시 위치값을 선형보간
fAlpha = fElpasedTick / m_fTicksPerGameFrame
선형보간 수식
fAlpha = 0 이면 $P_{old}$ 와 동일fAlpha = 1 이면 $P_{current}$ 와 동일0 < fAlpha < 1 이면 $P_{old}$ 와 $P_{current}$ 사이의 선형 보간된 중간 값매 프레임마다 플레이어, 적, 배경이미지 등의 위치값 업데이트
매 프레임마다 Collision 처리
매 프레임마다 그 외 게임 로직들 처리
화면 그리기
m_pDDraw->BeginDraw() 에서 Lock 수행m_pDDBack->Lock(nullptr, &ddsc, DDLOCK_WAIT | DDLOCK_WRITEONLY, nullptr);&ddsc에 Access 할 수 있는 메모리 주소가 저장되고, 해당 메모리 주소를 m_pWriteBuffer 멤버 변수에 저장DrawBitmap(int sx, int sy, int iBitmapWidth, int iBitmapHeight, char* pBits) 에 의해 배경이미지를 Draw (Buffer에 픽셀값을 넣음)
char* pBits
m_pBackImage->GetRawImage() 로 얻어온 배경이미지 데이터에 대한 포인터m_pWriteBuffer
char* pSrc
pSrc = pBits + (ivSrcStart.x + ivSrcStart.y * iBitmapWidth) * 4ivSrcStart는 Clipping 이후 이미지 좌표계에서의 이미지의 좌상단 좌표pSrc는 픽셀이미지가 저장된 메모리상에서의 Clipping 이후 시작주소char* pDest
pDest = m_pWriteBuffer + (ivDestStart.x * 4) + ivDestStart.y * m_dwWriteBufferPitch
pDest는 화면(BackBuffer) 상에서 이미지가 그려지기 시작하는 시작주소
ivDestStart는 화면(BackBuffer)에서 이미지가 그려지기 시작하는 좌표
수식이 pSrc 와 다른 이유는 Surface 메모리 구조에서의 Pitch개념 때문
배경이미지의 경우, 전체화면에 다 그려지므로 버퍼좌표계상에서 시작좌표는 (0, 0)
이후 Loop를 돌며 pSrc 의 각 픽셀값들을 pDest 에 저장하여 BackBuffer에 Draw
투명이 없는 배경 이미지이므로 모든 픽셀들에 대해 전부 하나씩 Draw수행
BackBuffer에 Draw하는 코드분석
투명이 있는 이미지는 전부 그릴필요가 없으므로 0번압축을 통해 필요한 데이터만 저장
0번압축 시킨 데이터에 해당하는 부분만 Draw하면됨
Clipping하는 부분에 대한 설명은 DirectDraw 문서의 Clipping부분 참고
먼저 0번 압축된 데이터는 COMPRESSED_LINE 구조체들의 배열에 저장됨
배열의 원소마다 각 Line들의 압축된 픽셀데이터와 메타데이터가 존재
pLineDesc는 포인터배열로 증감연산자 수행 시 배열의 다음 인덱스(구조체)로 넘어감
struct COMPRESSED_LINE
{
DWORD dwStreamNum;
DWORD dwLineDataSize;
PIXEL_STREAM* pPixelStream;
};
struct PIXEL_STREAM
{
WORD wPosX;
WORD wPixelNum;
DWORD dwPixel;
};
// sx, sy : DirectX 스크린 좌표계상에서의 위치
BOOL CDDrawDevice::DrawImageData(int sx, int sy, const CImageData* pImgData)
{
BOOL bResult = FALSE;
#ifdef _DEBUG
if (!m_pWriteBuffer)
__debugbreak();
#endif
int iScreenWidth = (int)m_dwWidth;
int iBitmapWidth = (int)pImgData->GetWidth();
int iBitmapHeight = (int)pImgData->GetHeight();
INT_VECTOR2 ivSrcStart = {};
INT_VECTOR2 ivDestStart = {};
INT_VECTOR2 ivPos = { sx, sy };
INT_VECTOR2 ivImageSize = { iBitmapWidth, iBitmapHeight };
INT_VECTOR2 ivDestSize = {};
if (!CalcClipArea(&ivSrcStart, &ivDestStart, &ivDestSize, &ivPos, &ivImageSize))
goto lb_return;
const COMPRESSED_LINE* pLineDesc = pImgData->GetCompressedImage(ivSrcStart.y);
// pDestPerLine는 화면(백버퍼)에서 복사할 행의 시작 주소
// m_dwWriteBufferPitch는 한 행의 바이트 크기이므로 ivDestStart.y행부터 그리기 시작하게 됨
// ivDestStart는 화면(BackBuffer)에서 이미지가 그려지기 시작하는 좌표
char* pDestPerLine = m_pWriteBuffer + (ivDestStart.y) * m_dwWriteBufferPitch;
for (int y = 0; y < ivDestSize.y; y++)
{
// 한 Line씩 차례대로 처리
// dwStreamNum = 해당 Line의 압축된 Color 구간(PIXEL_STREAM) 개수
// PIXEL_STREAM은 여러 개 연속된 Color 정보를 압축한 구조체
for (DWORD i = 0; i < pLineDesc->dwStreamNum; i++)
{
PIXEL_STREAM* pStream = pLineDesc->pPixelStream + i;
// dwPixelColor = 현재 픽셀 색상
// iPixelNum = 연속된 픽셀 개수
DWORD dwPixelColor = pStream->dwPixel;
int iPixelNum = (int)pStream->wPixelNum;
/*
wPosX = 각 PIXEL_STREAM의 시작 x좌표 (이미지 좌표계)
= 연속픽셀이 시작한 x좌표
sx = 화면(BackBuffer) 상에서의 이미지의 좌상단 x좌표
dest_x = 화면(BackBuffer) 상에서의 wPosX의 x좌표
= sx + wPosX
*/
int dest_x = sx + (int)pStream->wPosX;
// 화면상에서의 wPosX의 x좌표가 화면밖으로 나갔을 시 Clipping 처리
if (dest_x < 0)
{
// dest_x가 음수인 경우이므로, 그려야할 픽셀을 줄이는 효과
// 화면밖으로 나간 픽셀수만큼은 Draw하면 안됨
iPixelNum += dest_x;
dest_x = 0;
}
// 화면상에서의 wPosX의 x좌표가 화면밖으로 나갔을 시 Clipping 처리
if (dest_x + iPixelNum > iScreenWidth)
{
// 만약 이걸 주석처리할 시 그 다음 Line의 픽셀로 넘어가서 그림
// 우하단에서 넘어갈 시 메모리범위를 넘어가게 됨
iPixelNum = iScreenWidth - dest_x;
}
// pDest = 화면(BackBuffer) 상에서 이미지가 그려지기 시작하는 시작주소
// 픽셀당 4Byte
char* pDest = pDestPerLine + (DWORD)(dest_x * 4);
for (int x = 0; x < iPixelNum; x++)
{
*(DWORD*)pDest = dwPixelColor;
pDest += 4;
}
}
// 다음 Line에 대한 구조체로 이동
pLineDesc++;
// 한 행의 크기(m_dwWriteBufferPitch)만큼 증가시켜 다음 행의 시작 주소로 이동
pDestPerLine += m_dwWriteBufferPitch;
}
}