테트리스 강좌 글 보고 따라서 만들었습니다
근데 2인용 3인용하는부분을 따라하지않고
더블버퍼링사용에대해서 수정하다보니
따라했는데
뭔가문제가있는지
떨림현상이 그대로있습니다 ㅠㅠ
소스좀 봐주시면감사하겠습니다...ㅠㅠ
// 2013msTetrisView.cpp : CMy2013msTetrisView 클래스의 구현
//
#include "stdafx.h"
#include "2013msTetris.h"
#include "2013msTetrisDoc.h"
#include "2013msTetrisView.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
int g_BoardInfo[BOARD_Y_SIZE+1][BOARD_X_SIZE+2];
int g_NowBlockInfo[7][4][4][4]={
// 0번 째 벽돌
{
{
{1,1,0,0},
{1,1,0,0},
{0,0,0,0},
{0,0,0,0}
},
{ {1,1,0,0},
{1,1,0,0},
{0,0,0,0},
{0,0,0,0}
},
{ {1,1,0,0},
{1,1,0,0},
{0,0,0,0},
{0,0,0,0}
},
{
{1,1,0,0},
{1,1,0,0},
{0,0,0,0},
{0,0,0,0}
}
},
// 1번 째 벽돌
{
{
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
{0,1,0,0}
},
{
{0,0,0,0},
{1,1,1,1},
{0,0,0,0},
{0,0,0,0}
},
{
{0,1,0,0},
{0,1,0,0},
{0,1,0,0},
{0,1,0,0}
},
{
{0,0,0,0},
{1,1,1,1},
{0,0,0,0},
{0,0,0,0}
}
},
// 2번 째 벽돌
{
{
{1,0,0,0},
{1,1,0,0},
{1,0,0,0},
{0,0,0,0}
},
{
{0,1,0,0},
{1,1,1,0},
{0,0,0,0},
{0,0,0,0}
},
{
{0,1,0,0},
{1,1,0,0},
{0,1,0,0},
{0,0,0,0}
},
{
{1,1,1,0},
{0,1,0,0},
{0,0,0,0},
{0,0,0,0}
}
},
// 3번 째 벽돌
{
{
{1,1,0,0},
{1,0,0,0},
{1,0,0,0},
{0,0,0,0}
},
{
{1,0,0,0},
{1,1,1,0},
{0,0,0,0},
{0,0,0,0}
},
{
{0,1,0,0},
{0,1,0,0},
{1,1,0,0},
{0,0,0,0}
},
{
{1,1,1,0},
{0,0,1,0},
{0,0,0,0},
{0,0,0,0}
}
},
// 4번 째 벽돌
{
{
{1,0,0,0},
{1,0,0,0},
{1,1,0,0},
{0,0,0,0}
},
{
{0,0,1,0},
{1,1,1,0},
{0,0,0,0},
{0,0,0,0}
},
{
{1,1,0,0},
{0,1,0,0},
{0,1,0,0},
{0,0,0,0}
},
{
{1,1,1,0},
{1,0,0,0},
{0,0,0,0},
{0,0,0,0}
}
},
// 5번 째 벽돌
{
{
{0,1,0,0},
{1,1,0,0},
{1,0,0,0},
{0,0,0,0}
},
{
{1,1,0,0},
{0,1,1,0},
{0,0,0,0},
{0,0,0,0}
},
{
{0,1,0,0},
{1,1,0,0},
{1,0,0,0},
{0,0,0,0}
},
{
{1,1,0,0},
{0,1,1,0},
{0,0,0,0},
{0,0,0,0}
}
},
// 6번 째 벽돌
{
{
{1,0,0,0},
{1,1,0,0},
{0,1,0,0},
{0,0,0,0}
},
{
{0,1,1,0},
{1,1,0,0},
{0,0,0,0},
{0,0,0,0}
},
{
{1,0,0,0},
{1,1,0,0},
{0,1,0,0},
{0,0,0,0}
},
{
{0,1,1,0},
{1,1,0,0},
{0,0,0,0},
{0,0,0,0}
}
}
};
// CMy2013msTetrisView
IMPLEMENT_DYNCREATE(CMy2013msTetrisView, CFormView)
BEGIN_MESSAGE_MAP(CMy2013msTetrisView, CFormView)
ON_WM_PAINT()
ON_WM_KEYDOWN()
ON_WM_TIMER()
ON_WM_SIZE()
ON_WM_GETMINMAXINFO()
ON_COMMAND(ID_GameStart, &CMy2013msTetrisView::OnGamestart)
END_MESSAGE_MAP()
// CMy2013msTetrisView 생성/소멸
CMy2013msTetrisView::CMy2013msTetrisView()
: CFormView(CMy2013msTetrisView::IDD)
, m_iNowBlockY(0)
, m_iOneBlockSize(22)
, m_iNowBlockShape(0)
, m_iNowBlockDir(0)
, m_iNextBlockShape(0)
, m_iGameID(0)
, m_iBoardX(0)
, m_iBoardY(0)
, m_iScore(0)
, m_item(1)
, m_Level(1)
, m_Sec(1000)
{
m_iNowBlockX = BOARD_X_SIZE/2;
// TODO: 여기에 생성 코드를 추가합니다.
// 보드의 좌측 벽
for(int i=0;i<BOARD_Y_SIZE;i++){
g_BoardInfo[i][0]=50;
}
// 보드의 우측 벽
for(int i=0;i<BOARD_Y_SIZE;i++){
g_BoardInfo[i][BOARD_X_SIZE+1]=50;
}
// 보드의 하단 벽
for(int i=0;i<BOARD_X_SIZE+2;i++){
g_BoardInfo[BOARD_Y_SIZE][i]=50;
}
}
CMy2013msTetrisView::~CMy2013msTetrisView()
{
}
void CMy2013msTetrisView::DoDataExchange(CDataExchange* pDX)
{
CFormView::DoDataExchange(pDX);
}
BOOL CMy2013msTetrisView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: CREATESTRUCT cs를 수정하여 여기에서
// Window 클래스 또는 스타일을 수정합니다.
return CFormView::PreCreateWindow(cs);
}
void CMy2013msTetrisView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();
GetParentFrame()->RecalcLayout();
ResizeParentToFit();
SetTimer(1, 1000, NULL);
}
// CMy2013msTetrisView 진단
#ifdef _DEBUG
void CMy2013msTetrisView::AssertValid() const
{
CFormView::AssertValid();
}
void CMy2013msTetrisView::Dump(CDumpContext& dc) const
{
CFormView::Dump(dc);
}
CMy2013msTetrisDoc* CMy2013msTetrisView::GetDocument() const // 디버그되지 않은 버전은 인라인으로 지정됩니다.
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMy2013msTetrisDoc)));
return (CMy2013msTetrisDoc*)m_pDocument;
}
#endif //_DEBUG
// CMy2013msTetrisView 메시지 처리기
void CMy2013msTetrisView::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: 여기에 메시지 처리기 코드를 추가합니다.
// 그리기 메시지에 대해서는 CFormView::OnPaint()을(를) 호출하지 마십시오.
CClientDC pd(this);
int tempX,tempY;
CDC cdc;
CDC* pDC = this->GetDC(); // 모니터에 출력할 정보
CDC* pDrawDC=pDC; // 모니터에 출력하기 전에 사용할 메모리
CBitmap bitmap;
CBitmap* pOldBitmap;
CRect m_rectDraw; // 갱신할 (모니터의 )구역
m_rectDraw = CRect(m_iBoardX,m_iBoardY,(BOARD_X_SIZE+2)*m_iOneBlockSize+m_iBoardX,(BOARD_Y_SIZE+1)*m_iOneBlockSize+m_iBoardY+80);
CRect client;
pDC->GetClipBox(client);
CRect rect=client;
bitmap.CreateCompatibleBitmap(pDC,rect.Width(),rect.Height());
CBrush brush;
// 배경그림
if(!brush.CreateSolidBrush(RGB(230,200,250)))
return;
brush.UnrealizeObject();
pDrawDC->FillRect(client,&brush);
// 현재 벽돌 그리기
for(tempY=0;tempY<4;tempY++)
{
for( tempX=0; tempX<4; tempX++)
{
if (0 != g_NowBlockInfo[m_iNowBlockShape][m_iNowBlockDir][tempY][tempX])
{
DrawOneBlockbyCDC( m_iNowBlockY+tempY, m_iNowBlockX+tempX,m_iOneBlockSize,pDrawDC,GetColorByNum(m_iNowBlockShape+1));
}
}
}
for(tempY = 0;tempY<BOARD_Y_SIZE+1;tempY++){
for(tempX=0; tempX<BOARD_X_SIZE+2; tempX++)
{
if(0 != g_BoardInfo[tempY][tempX]){
DrawOneBlockbyCDC((tempY),tempX,m_iOneBlockSize,pDrawDC,GetColorByNum(g_BoardInfo[tempY][tempX]));
}
}
pDrawDC->Rectangle(270,0,430,150);
pDrawDC->Rectangle(270,160,430,270);
pDrawDC->Rectangle(270,280,430,350);
// 점수 표시
CString strKeyValue;
pDrawDC->TextOut(m_iBoardX+((BOARD_X_SIZE+3)*m_iOneBlockSize),m_iBoardY,_T("SCORE"));
strKeyValue.Format(_T("%d "),AddScore(0));
pDrawDC->TextOut(m_iBoardX+((BOARD_X_SIZE+3)*m_iOneBlockSize),m_iBoardY+(0+1)*m_iOneBlockSize,strKeyValue);
// Item 표시
pDrawDC->TextOut(m_iBoardX+((BOARD_X_SIZE+3)*m_iOneBlockSize),m_iBoardY+((BOARD_Y_SIZE+0)*m_iOneBlockSize-50),_T("Clear 아이템 "));
strKeyValue.Format(_T("%d "), m_item);
pDrawDC->TextOut(m_iBoardX+((BOARD_X_SIZE+3)*m_iOneBlockSize),m_iBoardY+((BOARD_Y_SIZE+1)*m_iOneBlockSize-50),strKeyValue);
// 레벨 표시
pDrawDC->TextOut(m_iBoardX+((BOARD_X_SIZE+3)*m_iOneBlockSize),m_iBoardY+((BOARD_Y_SIZE)*m_iOneBlockSize-250),_T("현재레벨 "));
strKeyValue.Format(_T("%d "), m_Level);
pDrawDC->TextOut(m_iBoardX+((BOARD_X_SIZE+3)*m_iOneBlockSize),m_iBoardY+((BOARD_Y_SIZE+1)*m_iOneBlockSize-250),strKeyValue);
// 화면 갱신
if(pDrawDC!=pDC)
{
pDC->SetViewportOrg(0,0);
pDC->SetWindowOrg(0,0);
pDC->SetMapMode(MM_TEXT);
dc.SetViewportOrg(0,0);
dc.SetMapMode(MM_TEXT);
pDC->BitBlt(m_rectDraw.left,m_rectDraw.top,m_rectDraw.Width(),m_rectDraw.Height(),pDrawDC,m_iBoardX,m_iBoardY,SRCCOPY);
dc.SelectObject(pOldBitmap);
}
}
//다음 벽돌 그리기
pDrawDC->TextOut(m_iBoardX+((BOARD_X_SIZE+6)*m_iOneBlockSize),m_iBoardY,_T("Next Block"));
DrawNextBlock(&pd);
}
void CMy2013msTetrisView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
switch(nChar){
case VK_LEFT:
moveLeft();
break;
case VK_RIGHT:
moveRight();
break;
case VK_UP:
PlaySound(IDR_WAVE_TURN);
moveUp();
break;
case VK_DOWN:
moveDown();
break;
case VK_CONTROL:
clear(m_item);
break;
case VK_SPACE:
PlaySound(IDR_WAVE_DIRECT);
while(CanMove(m_iNowBlockDir,m_iNowBlockY+1,m_iNowBlockX))
moveDown();
}
// TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
CFormView::OnKeyDown(nChar, nRepCnt, nFlags);
}
void CMy2013msTetrisView::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
if(CanMove(m_iNowBlockDir,m_iNowBlockY+1,m_iNowBlockX)){
m_iNowBlockY +=1;
}else{
if (IsGameEnd())
{
SetGameEnd();
}else{
CheckNRemove();
srand( (unsigned)time( NULL ) );
m_iNowBlockX = BOARD_X_SIZE/2;
m_iNowBlockY = 0;
m_iNowBlockShape = m_iNextBlockShape;
m_iNextBlockShape = rand()%7;
}
}
OnPaint();
CFormView::OnTimer(nIDEvent);
}
bool CMy2013msTetrisView::moveLeft(void)
{
if(CanMove(m_iNowBlockDir,m_iNowBlockY,m_iNowBlockX-1)){
m_iNowBlockX -= 1;
OnPaint();
}
return true;
}
bool CMy2013msTetrisView::moveRight(void)
{
if(CanMove(m_iNowBlockDir,m_iNowBlockY,m_iNowBlockX+1)){
m_iNowBlockX += 1;
OnPaint();
}
return true;
}
bool CMy2013msTetrisView::moveDown(void)
{
if(CanMove(m_iNowBlockDir,m_iNowBlockY+1,m_iNowBlockX)){
m_iNowBlockY +=1;
OnPaint();
}
return true;
}
bool CMy2013msTetrisView::moveUp(void)
{
if(CanMove((m_iNowBlockDir+1)%4,m_iNowBlockY,m_iNowBlockX)){
m_iNowBlockDir = (++m_iNowBlockDir)%4;
OnPaint();
}
return true;
}
/*
void CMy2013msTetrisView::DrawOneBlock(int y, int x, int iSize, CClientDC *pDC, COLORREF crRGB)
{
pDC->FillSolidRect((x)*iSize, (y)*iSize, iSize, iSize, crRGB);
pDC->Draw3dRect((x)*iSize, (y)*iSize, iSize, iSize, RGB(255,255,255), RGB(255,255,255));
}*/
bool CMy2013msTetrisView::CanMove(int iDir, int iCheckY, int iCheckX) // 움직일수 있는지 여부를 검사
{
for (int iTempBlockY = 0;iTempBlockY<4;iTempBlockY++){
for (int iTempBlockX = 0;iTempBlockX<4;iTempBlockX++){
if(0 != g_BoardInfo[iCheckY+iTempBlockY][iCheckX+iTempBlockX] && 0 != g_NowBlockInfo[m_iNowBlockShape][iDir][iTempBlockY][iTempBlockX]){
return FALSE;
}
}
}
return TRUE;
}
void CMy2013msTetrisView::CheckNRemove(void)
{
int iRemoveLineCount = 0;// 연속 삭제 줄
// 벽돌을 보드에 저장하기
for (int iTempBlockY = 0;iTempBlockY<4 && (m_iNowBlockY+iTempBlockY<BOARD_Y_SIZE);iTempBlockY++)
{
for (int iTempBlockX = 0;iTempBlockX<4;iTempBlockX++)
{
if(0 == g_BoardInfo[m_iNowBlockY+iTempBlockY][m_iNowBlockX+iTempBlockX] ){
if( 0 !=g_NowBlockInfo[m_iNowBlockShape][m_iNowBlockDir][iTempBlockY][iTempBlockX]){
g_BoardInfo[m_iNowBlockY+iTempBlockY][m_iNowBlockX+iTempBlockX] = m_iNowBlockShape+1;
}
}
}
}
// 한줄 없애기
BOOL bGoodOneLine = TRUE;
for (int iTempBlockY = 0;iTempBlockY<4 && (m_iNowBlockY+iTempBlockY<BOARD_Y_SIZE);iTempBlockY++)
{
bGoodOneLine = TRUE;
for (int iTempBlockX = 0;iTempBlockX<BOARD_X_SIZE+2;iTempBlockX++)
{
if(0 == g_BoardInfo[m_iNowBlockY+iTempBlockY][iTempBlockX] ){ // 한칸이라도 빈칸이 있으면.
bGoodOneLine = FALSE; // FALSE로
iTempBlockX = BOARD_X_SIZE + 2;
}
}
if(TRUE == bGoodOneLine){
PlaySound(IDR_WAVE_DEL);
iRemoveLineCount++;
int iRemoveLineNum = m_iNowBlockY+iTempBlockY;
for(int i=iRemoveLineNum ; i>0 ; i--){
for(int j=1;j<BOARD_X_SIZE+2;j++){
g_BoardInfo[i][j] =g_BoardInfo[i-1][j] ; // 줄삭제
}
}
}
}
if (iRemoveLineCount>0)
{
AddScore(iRemoveLineCount*iRemoveLineCount); // 점수추가
}
LevelUpCheck(); // 레벨업체크
/*if(IsGameEnd()){
KillTimer(1);
if(IDYES == MessageBox(_T("게임이끝났습니다."))){
GameStart();
}
}*/
}
bool CMy2013msTetrisView::IsGameEnd(void) // 게임이끝나는경우
{
for(int tempY = 0;tempY<2 ;tempY++){
for(int tempX=3; tempX<7; tempX++)
{
if(0 != g_BoardInfo[tempY][tempX]){
return TRUE;
}
}
}
return false;
}
void CMy2013msTetrisView::GameStart(void) // 재시작
{
SetTimer(1,1000,NULL); // 1번 타이머를 1000mS(즉,1초)마다 동작
srand( (unsigned)time( NULL ) );
m_iNowBlockShape = rand()%7;
m_iNextBlockShape = rand()%7;
m_iNowBlockDir = 0;
m_iNowBlockX = BOARD_X_SIZE/2;
m_iNowBlockY = 0;
m_iOneBlockSize = 22;
m_item = 1;
m_iScore=0;
for(int tempY = 0;tempY<BOARD_Y_SIZE+1;tempY++){
for(int tempX=0; tempX<BOARD_X_SIZE+2; tempX++)
{
g_BoardInfo[tempY][tempX] = 0;
}
}
// 보드의 좌측 벽
for(int i=0;i<BOARD_Y_SIZE;i++){
g_BoardInfo[i][0]=50;
}
// 보드의 우측 벽
for(int i=0;i<BOARD_Y_SIZE;i++){
g_BoardInfo[i][BOARD_X_SIZE+1]=50;
}
// 보드의 하단 벽
for(int i=0;i<BOARD_X_SIZE+2;i++){
g_BoardInfo[BOARD_Y_SIZE][i]=50;
}
}
void CMy2013msTetrisView::DrawNextBlock(CClientDC* pDC) // 다음 벽돌 표시
{
CDC cdc;
CDC* bufferdc = this->GetDC(); // 모니터에 출력할 정보
CDC* pDrawDC=bufferdc; // 모니터에 출력하기 전에 사용할 메모리
CBitmap bitmap;
for(int tempY = 0;tempY<4;tempY++){
for(int tempX=0; tempX<4; tempX++)
{
if(0 != g_NowBlockInfo[m_iNextBlockShape][0][tempY][tempX]){
DrawOneBlockbyCDC((BOARD_Y_POS+tempY),BOARD_X_POS+BOARD_X_SIZE+3+tempX,m_iOneBlockSize,pDrawDC,RGB(50,200,200));
}
}
}
}
COLORREF CMy2013msTetrisView::GetColorByNum(int iNum)
{
COLORREF returnColor;
if (0 == iNum) returnColor = RGB(50,0,0);
else if (1 == iNum) returnColor = RGB(100,0,0);
else if (2 == iNum) returnColor = RGB(150,0,0);
else if (3 == iNum) returnColor = RGB(200,0,0);
else if (4 == iNum) returnColor = RGB(250,0,0);
else if (5 == iNum) returnColor = RGB(50,50,0);
else if (6 == iNum) returnColor = RGB(100,50,0);
else if (7 == iNum) returnColor = RGB(150,50,0);
else if (8 == iNum) returnColor = RGB(200,50,0);
else if (9 == iNum) returnColor = RGB(250,50,0);
else if (10 == iNum) returnColor = RGB(50,100,0);
else if (50 == iNum) returnColor = RGB(200,200,200);
else returnColor = RGB(0,0,0);
return returnColor;
}
void CMy2013msTetrisView::DrawOneBlockbyCDC(int y, int x, int iSize, CDC* pDC, COLORREF crRGB)
{
pDC->FillSolidRect(m_iBoardX+(x)*iSize, m_iBoardY+(y)*iSize, iSize, iSize, crRGB);
pDC->Draw3dRect(m_iBoardX+(x)*iSize, m_iBoardY+(y)*iSize, iSize, iSize, RGB(255,255,255), RGB(255,255,255));
}
void CMy2013msTetrisView::OnSize(UINT nType, int cx, int cy) // 화면크기에따라 테트리스 크기 조정
{
CFormView::OnSize(nType, cx, cy);
int iBlockSizeX = cx/((BOARD_X_SIZE+3)*3);
int iBlockSizeY = (cy-60)/(BOARD_Y_SIZE+1);
int iBlockSize = (iBlockSizeX<iBlockSizeY)? iBlockSizeX :iBlockSizeY;
SetBlockSize(iBlockSize);
SetBoardXY(0,0);
// TODO: 여기에 메시지 처리기 코드를 추가합니다.
}
void CMy2013msTetrisView::SetBlockSize(int iData)
{
m_iOneBlockSize = iData+8;
}
void CMy2013msTetrisView::SetBoardXY(int x, int y)
{
m_iBoardX = x;
m_iBoardY = y;
}
void CMy2013msTetrisView::OnGetMinMaxInfo(MINMAXINFO* lpMMI)
{
// TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
/* lpMMI->ptMinTrackSize.x =300;
lpMMI->ptMinTrackSize.y =300;
*/
CFormView::OnGetMinMaxInfo(lpMMI);
}
int CMy2013msTetrisView::AddScore(int iData) // 점수추가
{
m_iScore +=iData;
return m_iScore;
}
void CMy2013msTetrisView::SetGameEnd(void)
{
int tempX,tempY;
// 모든 벽돌을 회색으로 변경.
for(tempY = 0;tempY<BOARD_Y_SIZE+1;tempY++){
for(tempX=0; tempX<BOARD_X_SIZE+2; tempX++)
{
if(0 != g_BoardInfo[tempY][tempX]){
g_BoardInfo[tempY][tempX] = GetColorByNum(0);
}
}
}
}
void CMy2013msTetrisView::clear(int iItem) // clear아이템 사용시
{
if(iItem == 1){
PlaySound(IDR_WAVE_CLEAR);
srand( (unsigned)time( NULL ) );
m_iNowBlockShape = rand()%7;
m_iNextBlockShape = rand()%7;
m_iNowBlockDir = 0;
m_iNowBlockX = BOARD_X_SIZE/2;
m_iNowBlockY = 0;
m_iOneBlockSize = 22;
for(int tempY = 0;tempY<BOARD_Y_SIZE+1;tempY++){
for(int tempX=0; tempX<BOARD_X_SIZE+2; tempX++)
{
g_BoardInfo[tempY][tempX] = 0;
}
}
// 보드의 좌측 벽
for(int i=0;i<BOARD_Y_SIZE;i++){
g_BoardInfo[i][0]=50;
}
// 보드의 우측 벽
for(int i=0;i<BOARD_Y_SIZE;i++){
g_BoardInfo[i][BOARD_X_SIZE+1]=50;
}
// 보드의 하단 벽
for(int i=0;i<BOARD_X_SIZE+2;i++){
g_BoardInfo[BOARD_Y_SIZE][i]=50;
}
m_item-=1;
}
}
void CMy2013msTetrisView::OnGamestart() // 메뉴에서 시작을 누른경우
{
GameStart();
// TODO: 여기에 명령 처리기 코드를 추가합니다.
}
void CMy2013msTetrisView::LevelUpCheck(void) // 레벨업 검사
{
if(m_iScore < (m_Level*5)) return;
PlaySound(IDR_WAVE_LEVELUP);
m_Level++;
m_Sec -= 50;
SetTimer(1, m_Sec, NULL);
}
필요하시면 프로젝트 전체를 보내드리겠습니다 ㅠㅠ
OnPaint() 이 더블버퍼링과 관련 있습니다.
먼저 OnPaint() 내부에서 필요한 것만 남겨두시고,,
코드를 하나하나 추가해 가면서
화면 떨리는 코드를 찾으셔야 합니다.
추가적으로 말씀드리자면
더블버퍼링은 임의의 메모리를 할당하여 사용하고 반납하는 구조라서
잘못 사용하면 메모리 누수(leak) 현상이 발생할 수 있으니 주의하셔야 합니다.