SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

ゲームプログラミング入門

本格的なシューティングゲームを実現するタスクシステム

複数の処理を管理・実行する仕組み


  • X ポスト
  • このエントリーをはてなブックマークに追加

タスクシステムを動かす

 タスクシステムを動かすために呼び出すメンバ関数をまとめます。

プログラム実行する時期
TaskEx::InitTaskList();ゲーム起動時
Task::RunTask();フレームごと
TaskEx::DrawTask(g_pSprite);描画する時
Task::ReleaseTaskList();ゲーム終了時

自機タスク

 自機タスクを例に、タスクのクラス、およびタスクシステムの使い方を見てみましょう。

 メンバ関数DrawMainは必須です。これらを定義しないと抽象クラスになってしまいます。

#pragma once

#include"TaskSystemEx.h"

// 自機
class Player : public TaskEx
{
    DWORD m_timer;
public:
    Player(float cx,float cy,float speed,DWORD life);
    ~Player(void);
    void Draw(ID3DXSprite *pSprite);
    void Main(void);
    void Move(float add_x,float add_y);
    void Damage(DWORD damage);
};

定数

 タスクごとにファイルを分けるので、各タスクに固有の情報は定数として定義しておけば十分です。

#include"task.h"

#define TEXFILENAME _T("player.png")
#define TEXWIDTH    64
#define TEXHEIGHT   64
#define COLORKEY    0

const float W05=TEXWIDTH*0.5f;
const float H05=TEXHEIGHT*0.5f;

// 当たり領域 機体の中心からの距離
const float HIT_CW=TEXWIDTH*0.25f;
const float HIT_CH=TEXHEIGHT*0.25f;

#define MARGIN 0        // 画面外に移動できるピクセル数

#define SHOT_SPEED  -5  // ショットの速度
#define SHOT_WAIT  250  // ショットの間隔[ms]

コンストラクタ

 メンバ変数の初期化、画像の読み込み、などを行います。

Player::Player(float cx,float cy,float speed,DWORD life)
{
    m_id=PLAYER;
    m_cx=cx; m_cy=cy;
    m_hit_cw=HIT_CW; m_hit_ch=HIT_CH;
    m_speed=speed;
    m_life=life; m_max_life=life;

    m_timer=0;

    D3DXCreateTextureFromFileEx(g_pD3DDevice,TEXFILENAME,
        TEXWIDTH,TEXHEIGHT,1,0,
        D3DFMT_A8R8G8B8,
        D3DPOOL_MANAGED,D3DX_FILTER_LINEAR,D3DX_FILTER_LINEAR,
        COLORKEY,NULL,NULL,&m_pTexture);
}

デストラクタ

 メモリ領域の解放、画像の破棄、などを行います。

Player::~Player(void)
{
    SAFE_RELEASE(m_pTexture);
}

描画関数

 描画するだけにしましょう。

void Player::Draw(ID3DXSprite *pSprite)
{
    RECT rect={0,0,TEXWIDTH,TEXHEIGHT};
    D3DXVECTOR2 trans(m_cx-W05,m_cy-H05);
    D3DXVECTOR2 rot_cen(W05,H05);
    D3DCOLOR color=D3DCOLOR_ARGB(m_alpha,255,255,255);

    pSprite->Draw(m_pTexture,&rect,NULL,&rot_cen,m_rot,&trans,color);
}

処理関数

 状況によって処理を分岐させる場合は、switchを用いる、または実際に処理するメンバ関数を別に複数用意し、メンバ変数に追加した関数ポインタを用いて呼び分ける、ことにより実現できます。

 [SPACE]キーを押している時の処理を見てください。規定のショット間隔を経過している場合は「弾タスク」を生成・追加しています。優先度は省略しているので、デフォルト値0.5fに設定されます。省略せず0.75fに設定したい場合は

new(0.75f) MyShot(m_cx,m_cy,SHOT_SPEED);

 と書きます。

void Player::Main(void)
{
    BYTE state[256];
    GetKeyboardState(state);

    if(state[VK_LEFT] & 0x80)
    {
        m_cx=(m_cx-m_speed >= -MARGIN)?
            m_cx-m_speed : -MARGIN;
    }
    if(state[VK_RIGHT] & 0x80)
    {
        m_cx=(m_cx+m_speed < WND_W+MARGIN)?
            m_cx+m_speed : WND_W+MARGIN;
    }
    if(state[VK_UP] & 0x80)
    {
        m_cy=(m_cy-m_speed >= -MARGIN)?
            m_cy-m_speed : -MARGIN;
    }
    if(state[VK_DOWN] & 0x80)
    {
        m_cy=(m_cy+m_speed < WND_H+MARGIN)?
            m_cy+m_speed : WND_H+MARGIN;
    }

    if(m_timer<SHOT_WAIT) m_timer+=SPF;

    if(state[VK_SPACE] & 0x80)
    {
        if(m_timer>=SHOT_WAIT)
        {
            m_timer=0;
            new MyShot(m_cx,m_cy,SHOT_SPEED);
        }
    }
}

移動させる

 外部から自機を移動させたい場合に呼び出します。

void Player::Move(float add_x,float add_y)
{
    if(add_x<0)
    {
        m_cx=(m_cx+add_x >= -MARGIN)? m_cx+add_x : -MARGIN;
    }
    else if(add_x>0)
    {
        m_cx=(m_cx+add_x < WND_W+MARGIN)? m_cx+add_x : WND_W+MARGIN;
    }

    if(add_y<0)
    {
        m_cy=(m_cy+add_y >= -MARGIN)? m_cy+add_y : -MARGIN;
    }
    else if(add_y>0)
    {
        m_cy=(m_cy+add_y < WND_H+MARGIN)? m_cy+add_y : WND_H+MARGIN;
    }
}

耐久力を減らす

 外部から耐久力を減らしたい場合に呼び出します。アイテム取得によるパワーアップも同様のメンバ関数を追加することにより実現できます。なお当たり判定は「弾タスク」で行うことにしました。

 耐久力が無くなった場合は「爆発タスク」を生成・追加し、自滅します。自滅した後は何もせず直ちに関数から抜けましょう。この様に、ゲームの流れを自然な形で表現できるのもタスクシステムの魅力です。

void Player::Damage(DWORD damage)
{
    m_life=(damage<m_life)? m_life-damage : 0;

    if(m_life==0)
    {
        new Explosion(m_cx,m_cy);
        delete this;
    }
}

まとめ

 サンプルファイルのシューティングゲームでは、前述の「自機」の他に、「敵機」「自機の弾」「敵機の弾(狙い撃ち)」「爆発」「自機の制御」「敵の出現制御」「ステージ制御」「ライフバー管理」「スコア管理」「タイトル画面」「ゲームオーバー画面」をタスクとしました。これらがゲームを構成する要素の全てであり、他のプログラムは、メインループやDirectXの初期化といった定型処理に過ぎません。

 本稿のタスクシステムは、メモリ効率を重視しています。どこが、というと、タスクに可変長のメモリを割り当てたり、ダミータスクを用いていないところが、です。一方、多くの参考文献では、タスクリストのためのメモリ領域を、タスクを要素とする配列として確保しています。配列の要素は固定長なため、無駄が生じてしまいます。私にはこれがどうしても納得できなかったため、本稿の仕様になったというわけです。プログラムは複雑になってしまいましたが、一度作ってしまえばそうそう触る部分ではないため、頑張っただけの価値はあります。

 タスクシステムは、シューティングゲームやアクションゲームに特に相性が良いとされていますが、もちろん他のジャンルのゲームも作ることができるので、是非とも挑戦してください。

参考文献

  1. ゲームのためのタスクシステム松浦健一郎 著、C MAGAZINE 2004年12月号、pp.64-78、ソフトバンククリエイティブ
  2. 私がタスクシステムを知り、本稿の執筆に最も参考とした記事です。
  3. C++によるタスクシステムの実現松浦健一郎 著、C MAGAZINE 2006年1月号、pp.135-144、ソフトバンククリエイティブ
  4. 1.でもC++によるタスクシステムは解説しているが、増補版といったところ。
  5. White Paper
  6. タスクシステムとその様々な機能を解説しています。ただし、プログラムはありません。
修正履歴

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
ゲームプログラミング入門連載記事一覧

もっと読む

この記事の著者

ひよこ(ヒヨコ)

職業ゲームプログラマーを志す学生です。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/297 2006/07/24 19:47

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング