24:スプライトのアニメーション
歩く、ジャンプ、しゃがむ、攻撃する……ゲームにおいてキャラクターは様々なアクションを起こすことがあります。パラパラ漫画の要領で複数の画像を順繰りで描画していけばいいのですが、1枚(以下、フレーム)のイラストに1つのファイルを使っていては、フォルダの中があっという間に画像ファイルでいっぱいになってしまいます。そこで今回は、1枚の画像ファイル(テクスチャ)でアニメーションを実現する方法をご紹介します。
ちなみに、今回のアニメパターン作成には3Dモデリングツール「六角大王Super」を利用しています。六角大王には再利用可能サンプルが豊富に用意されているので、このソフトなら絵心のない人でも、様々な角度からとらえた戦闘機や、そのアニメーションなどを手軽に作成することができます。Direct3D専用のポリゴンファイル(x形式)の出力にも対応しているので、3Dプログラミングにチャレンジしたい人にもおすすめです。

取得した転送範囲はID3DXSprite::Draw()の第2引数にて指定します。今回はSetCenterPosition()をはじめとして、CSpriteクラスに大幅な変更が加えられていますので、CSpriteクラスの全ソースコードを掲載します。
#pragma once
#include "Texture.h"
#define CP_MIDDLECENTER 0
#define CP_LEFT 1
#define CP_CENTER 2
#define CP_RIGHT 4
#define CP_TOP 8
#define CP_MIDDLE 16
#define CP_BOTTOM 32
class DECLSPEC CSprite : public CGameObject
{
private:
CTexture *texture;
BYTE cpos; // 基点の位置
float orig_x, orig_y; // 原点の位置
int texwidth; // テクスチャの幅(縦横同一サイズが前提)
int swidth, sheight; // スプライトのサイズ
RECT drawrect; // 転送範囲
void Reset();
public:
CSprite();
CSprite(CTexture *source);
void SetTexture(CTexture *source);
void SetCenterPosition(BYTE pos);
BYTE GetCenterPosition();
// アニメーションのフレーム当たりのキャラクターサイズの設定・取得
void GetSpriteSize(int *width, int *height);
void SetSpriteSize(int width, int height);
// フレーム単位ではなく特定の範囲のグラフィックを転送したときには
// SetSpriteRect()で直接範囲を指定できるようにする
void SetSpriteRect(int left, int top, int right, int bottom);
// アニメーションのフレームより描画範囲を計算する
void SetFrame(int frame);
void Draw(float x, float y, int alpha = 255);
void Draw(float x, float y, float r, int alpha = 255);
void Draw(float x, float y, float ex, float ey, int alpha = 255);
void Draw(float x, float y, float ex, float ey, float r, int alpha = 255);
};
#include "Sprite.h"
CSprite::CSprite()
{
Reset();
}
CSprite::CSprite(CTexture *source)
{
Reset();
SetTexture(source);
}
void CSprite::Reset()
{
texture = NULL;
cpos = CP_MIDDLECENTER;
orig_x = orig_y = 0.0f;
texwidth = 0;
swidth = sheight = 0;
SetRect(&drawrect, 0, 0, 0, 0);
}
void CSprite::SetTexture(CTexture *source)
{
if(source == NULL) return;
texture = source;
int h;
texture->GetTextureSize(&texwidth, &h);
SetSpriteSize(texwidth, h);
}
void CSprite::SetSpriteSize(int width, int height)
{
// スプライトのサイズを変更
swidth = width;
sheight = height;
// 原点位置の更新
SetCenterPosition(cpos);
// フレームのリセット
SetFrame(0);
}
void CSprite::GetSpriteSize(int *width, int *height)
{
*width = swidth;
*height = sheight;
}
void CSprite::SetSpriteRect(int left, int top, int right, int bottom)
{
swidth = right - left;
sheight = bottom - top;
drawrect.left = left;
drawrect.right = right;
drawrect.top = top;
drawrect.bottom = bottom;
SetCenterPosition(cpos);
}
void CSprite::SetCenterPosition(BYTE pos)
{
// 原点の位置を計算
cpos = pos;
if(pos & CP_LEFT){
orig_x = 0.0f;
}else if(pos & CP_RIGHT){
orig_x = (float)swidth;
}else{
orig_x = (float)swidth / 2.0f;
}
if(pos & CP_TOP){
orig_y = 0.0f;
}else if(pos & CP_BOTTOM){
orig_y = (float)sheight;
}else{
orig_y = (float)sheight / 2.0f;
}
}
BYTE CSprite::GetCenterPosition()
{
return cpos;
}
void CSprite::SetFrame(int frame)
{
// 指定したフレーム値より転送範囲を計算
int d = texwidth / swidth;
if(d == 0) return;
int xpos = frame % d;
int ypos = frame / d;
drawrect.left = xpos * swidth;
drawrect.right = drawrect.left + swidth;
drawrect.top = ypos * sheight;
drawrect.bottom = drawrect.top + sheight;
}
void CSprite::Draw(float x, float y, int alpha)
{
Draw(x, y, 1.0f, 1.0f, 0.0f, alpha);
}
void CSprite::Draw(float x, float y, float ex, float ey, int alpha)
{
Draw(x, y, ex, ey, 0.0f, alpha);
}
void CSprite::Draw(float x, float y, float r, int alpha)
{
Draw(x, y, 1.0f, 1.0f, r, alpha);
}
void CSprite::Draw(float x, float y, float ex, float ey, float r, int alpha)
{
if(texture == NULL){
DXTRACE_MSG(_T("テクスチャが読み込まれていません"));
return;
}
D3DXMATRIX mtrx1, mtrx2;
// 原点を重ね合わせる平行移動
D3DXMatrixTranslation(&mtrx1, -orig_x, -orig_y, 0.0f);
// 拡大行列と合成
if(ex != 1.0f || ey != 1.0f){
D3DXMatrixScaling(&mtrx2, ex, ey, 1.0f);
D3DXMatrixMultiply(&mtrx1, &mtrx1, &mtrx2);
}
// 回転行列との合成
if(r != 0.0f){
D3DXMatrixRotationZ(&mtrx2, r);
D3DXMatrixMultiply(&mtrx1, &mtrx1, &mtrx2);
}
// 指定の場所へ移動する行列との合成
D3DXMatrixTranslation(&mtrx2, x, y, 0.0f);
D3DXMatrixMultiply(&mtrx1, &mtrx1, &mtrx2);
pSprite->Begin(NULL);
pSprite->SetTransform(&mtrx1);
pSprite->Draw(texture->GetTexture(), &drawrect, NULL, NULL, 0x00FFFFFF | ((BYTE)alpha << 24));
pSprite->End();
}
class CAnimationTest : public CGameObject
{
private:
CTexture tex;
CSprite sprite;
protected:
virtual void Init();
virtual void Exec();
};
void CAnimationTest::Init()
{
tex.Load(_T("anime.png"));
sprite.SetTexture(&tex);
sprite.SetSpriteSize(128, 128);
}
void CAnimation::Exec()
{
static int frame = 0, count = 0;
if(++count >= 12){
count = 0;
if(++frame >= 8) frame = 0;
sprite.SetFrame(frame);
}
sprite.Draw(320.0f, 240.0f);
}
