3.비트맵을 이용한 에니메이션 출력

본장에서는 비트맵 그림을 이용하여 에니메이션을 출력하는 방법에 대해서 설명합니다. 본장은 게임을 만들거나,멀티미디어 프로그램을 제작할때 유용하게 사용할수 있는 방법입니다. 본장을 이해하기 위해서는 비트맵 출력에 대한 기본적인 지식이 있어야 합니다. 비트맵 출력에 대한 내용은 PART1 의 3장에서 설명하였습니다. 만일 비트맵 출력에 대해서 모르신다면 PART1의 3장을 참조하시기 바랍니다.

대상자: 중급,고급

Chapter 등급: 선택

공부방법:

본항목에서는 꼭 배워야 할 이론이 없습니다. 필요한 부분이 있을경우 나중에 참조하는 부분으로 이용하시기 바랍니다.

1. 배경에 투영되는 비트맵 출력 방법

배경에 투영 되는 비트맵 출력을 하는 방법은 그림 2-3-1과 같습니다.

(그림 파일)

(그림 2-3-1)배경에 투영되는 비트맵 출력

그림 2-3-1의 방법이  에니메이션 출력의 가장 기본이 되는 방법입니다. 그림 2-3-1의 ⓐ그림은 배경 비트맵 그림입니다. 이 배경에 ⓓ와 같이 배경에 투영되는 비트맵을 출력하고자 할경우 한개의 그림당 두개의 이미지가 필요합니다. 그림 2-3-1의 ⓒ와 같이 출력될 비트맵 이미지를 그리고 배경그림이 출력될부분은 검정색으로 설정합니다. 여기에서 검정색이란 RGB 값으로 0,0,0 에 해당됩니다. 이와 쌍으로 ⓑ와 같이 그려질 그림 객체는 검정색(RGB로 0,0,0) 배경에 해당하는 부분은 흰색(RGB로 255,255,255)의 값을 설정합니다. 이렇게 설정된 2개의 그림에서 먼저 ⓑ의 그림을 BitBlt함수를 이용하여 배경위에 출력합니다. 이때 BitBlt 함수의 마지막 인자 ROP 옵션을 SRCAND로 설정합니다. 이렇게 설정하면 배경에 AND 연산으로 ⓑ그림을 출력하게 됩니다. AND연산을 하게 되면 출력하고자 하는 ⓑ그림의 배경은 모두 1이기 때문에 배경이  ⓐ의 값이 그대로 출력이 되고 그림 객체(그림 ⓑ에서 검정색 부분)는 모두 검정색으로 출력됩니다. 즉 객체가 출력될 부분만 모두 검정색으로 지워지게 되는 것이니다. 이상태에서 ⓑ그림을 SRCPAINT로 출력하면 객체는 모두 검정색위에 있기 때문에 출력이되고 ⓒ의 검정색부분은 모두 무시되게 됩니다. 이렇게 하여 깨끗하게 배경에 투영되는 비트맵을 만들수가 있는 것입니다.

<1>배경에 투영되게 하기 위한 DC설정

BitBlt 함수를 사용하여 bitmap을 출력하고자 한다면 먼저 비트맵을 메모리 DC를 만들고 이에 설정한다음 이 DC를 이용하여 BitBlt 함수를 구동하여야 합니다. 따라서 비트맵을 출력하기 위해서는 비트맵과 쌍을 이루는 DC를 하나씩 만들어 두는 것이 좋스니다.

그림 2-3-2는 그림 2-3-1의 3개의 그림을 DC에 설정하고 3개의 DC를 이용하여 배경에 투영되는 비트맵을 설정하는 예입니다.

(그림 파일0

(그림 2-3-2)비트맵과 쌍으로 DC를 설정하여 출력

그림 2-3-2의 ①은 그림 2-3-1의 ⓐ항목을 DC로 설정한 것이며, ②는 그림 2-3-1의 ⓑ항목을 DC로 설정한것이며, ③항목은 그림 2-3-1의 ⓒ항목을 DC로 설정한것입니다. 이렇게 각각 backDC,maskDC,objectDC로 설정하고 backDC에 maskDC를 그림 2-3-2의  ⓐ처럼 BitBlt를 사용하여 출력하고 , objectDC를 이용하여 2-3-2의 ⓑ처럼 출력합니다. 이렇게 되면 backDC에 설정된 비트맵 그림은 그림 2-3-1의 ⓓ와 같은 형태가 됩니다. 이형태를 화면 DC에 출력하는 것입니다. 그림 2-3-2의 ⑤항목이 화면에 backDC를 출력한 예입니다.

이렇게 하면 배경에 투영되는 비트맵 이미지를 만들수가 있는 것입니다.

2. 에니메이션 방법

에니메이션 방법은 여러개의 장면을 그림 2-3-2와 같은 방법으로 연속적으로 출력하면 됩니다. 그림 2-3-3의 방법은 한개의 그림을 에니메이션으로 출력하는 예입니다.

(그림 파일)

(그림 2-3-3) 에니메이션 방법

그림 2-3-3은 그림 2-3-1과 연계해서 보시면 됩니다. 그림 2-3-3의 ⓐ와 같이 배경이미지가 있고 한개의 비트맵에 4개의 방향으로 움직이는 8개의 그림 마스크와(그림 2-3-3의 ⓑ)와 객체 이미지(그림 2-3-3의 ⓒ) 가 있습니다. 만일 그림 2-3-3의 ⓓ처럼 8개의 장면을 연속하여 출력하고자 한다면 그림 2-3-2와 같은 방법으로 배경이미지에 객체가 설정된 형태의 그림을 연속적으로 만들어 DC에 출력하는 것입니다. 즉 ⓓ의 첫번째 그림을 화면에 출력하고 그리고 다시 두번째 그림을 다시 3번재 그림을 (일정한 시간간격으로) 연속적으로 출력을 하게 되면 8장을 출력할때 깜빡임없이 에니메이션을 느낄수가 있습니다. 배경에 그림을 설정하여 배경 전체를 출력하기 때문에 매우 느릴것으로 예상할수 있으나 그렇지 않습니다. 배경그림 ⓐ는 이미 메모리에 로드된 상태이고 에니메이션 객체 이미지 ⓑ와 ⓒ또한 메모리에 로드된 상태에서 ROP연산을 이용하여 BitBlt를 사용하면 무척 빠르게 화면에 에니메이션을 출력할수가 있습니다.(오히려 너무 빨라서 보기가 힘들게 될정도입니다.)

그림 2-3-3의 형태는 그림 2-3-2의 ④번 항목까지의 내용을 8번 반복한것과 같습니다. 0.1초마다 그림 2-3-3의 ⓓ의 8장을 출력한다면 그림 2-3-4와 같은 로직을 이요할수가 있습니다.

(그림 파일)

(그림 2-3-4) 0.1초 간격으로 연속적으로 에니메이션 출력

그림 2-3-4의 ①항목은 그림 2-3-3의 ⓑ와 ⓒ 두개의 비트맵을 로드한 형태입니다. 이렇게 한후에 m_nFrame번호를 0으로 설정하고 타이머를 가동합니다. 이렇게 타이머가 가동되면 그림 2-3-4의 ③항목이 수행됩니다.  ③항목에서는 그림 2-3-3의 ⓓ와 같은 비트맵을 만드는 작업을 합니다. 이때 ⓐ와 ⓑ를 확인해 보시기 바랍니다.ⓐ와 ⓑ에서 비트맵 이미지의 특정 위치의 그림만 배경에 도시하였습니다. 이렇게 하는 이유는 그림 2-3-3의ⓑ나 ⓒ가 여러개의 에니메이션 그림이 한개의 비트맵으로 설정되었기 때문입니다. 이렇게 하여 프레임을 만든후에 OnDraw함수에서 이프레임 전체를 화면에 출력하는 것입니다.

한개의 에니메이션을 출력하는데 배경 전체를 다시 출력하는 것이 낭비라고 생각할수가 있을것입니다. 그러나 게임을 만들고자 할때는 배경에 에니메이션이 발생하는 부분이 단한개가 아니라는 것입니다. 그림 2-3-5는 를 보시면 굉장히 많은 에니메이션을 구동해야 하는 것을 보실수가 있을것입니다.

(그림 파일)

(그림 2-3-5) 여러 에니메이션 객체가 있는 경우

그림 2-3-5의 화면을 보듯이  화면에 출력하는 에니메이션들 총 5개입니다. 이 5개의 에니메이션들이 전부 각개 동작을 하고자 한다면 그위에서 설명한 방법이 5번 정도 수행이 되는 경우가 됩니다. 이럴경우은 배경에 모든 에니메이션을 설정하고 이배경을 일정 시간마다 출력하는 것이 가장 빠른방법입니다. 이렇기 때문에 에니메이션 이나 동영상들을 출력할때 프레임 개념을 사용하기도 합니다.

<1>에니메이션 예제 ExAnimation

ExAnimation은 그림 2-3-3과 2-3-4에서 설명한 내용을 예제로 작성한경우 입니다. 0.1초 간격으로 총 8개의 프레임을 화면에 출력하는 형태입니다. 이예제는 View의 소스를 분석하시면 이해하실수 있을것입니다. 다음은 ExAnimation의 View클래스 소스입니다.

(프로그램 소스)

// ExAnimationView.h : interface of the CExAnimationView class

//

/////////////////////////////////////////////////////////////////////////////

#if !defined(AFX_EXANIMATIONVIEW_H__E1EAEB76_3DAA_11D2_8B94_006097AD9DDC__INCLUDED_)

#define AFX_EXANIMATIONVIEW_H__E1EAEB76_3DAA_11D2_8B94_006097AD9DDC__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CExAnimationView : public CView

{

protected: // create from serialization only

CExAnimationView();

DECLARE_DYNCREATE(CExAnimationView)

// Attributes

public:

CExAnimationDoc* GetDocument();

// Operations

public:

CBitmap m_bCurImage;

CBitmap m_bAniBack;

CBitmap m_bAniCur;

int m_nFrame;

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExAnimationView)

public:

virtual void OnDraw(CDC* pDC);  // overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

virtual void OnInitialUpdate();

protected:

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CExAnimationView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

//{{AFX_MSG(CExAnimationView)

afx_msg void OnTimer(UINT nIDEvent);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in ExAnimationView.cpp

inline CExAnimationDoc* CExAnimationView::GetDocument()

{ return (CExAnimationDoc*)m_pDocument; }

#endif

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_EXANIMATIONVIEW_H__E1EAEB76_3DAA_11D2_8B94_006097AD9DDC__INCLUDED_)

// ExAnimationView.cpp : implementation of the CExAnimationView class

//

#include "stdafx.h"

#include "ExAnimation.h"

#include "ExAnimationDoc.h"

#include "ExAnimationView.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CExAnimationView

IMPLEMENT_DYNCREATE(CExAnimationView, CView)

BEGIN_MESSAGE_MAP(CExAnimationView, CView)

//{{AFX_MSG_MAP(CExAnimationView)

ON_WM_TIMER()

//}}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CExAnimationView construction/destruction

CExAnimationView::CExAnimationView()

{

// TODO: add construction code here

}

CExAnimationView::~CExAnimationView()

{

}

BOOL CExAnimationView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

/////////////////////////////////////////////////////////////////////////////

// CExAnimationView drawing

void CExAnimationView::OnDraw(CDC* pDC)

{

CExAnimationDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

CDC cdc;

//현재 DC와 호환성이 있는 DC로 설정

cdc.CreateCompatibleDC(pDC);

//이 DC에 비트맵을 설정한다.

cdc.SelectObject(&m_bCurImage);

//cdc를 화면 DC pDC로 출력

pDC->BitBlt(0,0,640,480,&cdc,0,0,SRCCOPY);

}

/////////////////////////////////////////////////////////////////////////////

// CExAnimationView printing

BOOL CExAnimationView::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CExAnimationView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CExAnimationView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

/////////////////////////////////////////////////////////////////////////////

// CExAnimationView diagnostics

#ifdef _DEBUG

void CExAnimationView::AssertValid() const

{

CView::AssertValid();

}

void CExAnimationView::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CExAnimationDoc* CExAnimationView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CExAnimationDoc)));

return (CExAnimationDoc*)m_pDocument;

}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////

// CExAnimationView message handlers

void CExAnimationView::OnInitialUpdate()

{

CView::OnInitialUpdate();

m_bCurImage.LoadBitmap(IDB_BACKIMAGE);

m_bAniBack.LoadBitmap(IDB_CARBACK);

m_bAniCur.LoadBitmap(IDB_CARPAN);

m_nFrame=0;

SetTimer(1,100,NULL);

}

//그림 2-3-4의 ③ 항목

void CExAnimationView::OnTimer(UINT nIDEvent)

{

//현재 출력된 장면비트맵을 삭제하고

m_bCurImage.DeleteObject();

//배경판을 로드한다.

m_bCurImage.LoadBitmap(IDB_BACKIMAGE);

//배경 DC,객체 DC 마스크 DC를 설정

CDC *pDC,aniDC,anibkDC,curDC;

pDC=GetDC();

//각 DC를 만들고 이 DC에 비트맵 설정

aniDC.CreateCompatibleDC(pDC);

anibkDC.CreateCompatibleDC(pDC);

curDC.CreateCompatibleDC(pDC);

aniDC.SelectObject(&m_bAniCur);

anibkDC.SelectObject(&m_bAniBack);

curDC.SelectObject(&m_bCurImage);

//객체와 마스크 이미지에서 특정 부분만 배경 DC에 출력한다.

curDC.BitBlt(300,200,90,107,&anibkDC,m_nFrame*90,0,SRCAND);

curDC.BitBlt(300,200,90,107,&aniDC,m_nFrame*90,0,SRCPAINT);

ReleaseDC(pDC);

m_nFrame++;

if(m_nFrame==8)

m_nFrame=0;

Invalidate(FALSE);

CView::OnTimer(nIDEvent);

}

(프로그램 소스끝)