view paper/dandy.tex @ 8:f953f01c58bf

expand
author Shinji KONO <kono@ie.u-ryukyu.ac.jp>
date Mon, 07 Feb 2011 16:00:55 +0900
parents 2dcc784d62e0
children d711f469cdb7
line wrap: on
line source

\chapter{Super Dandy}

\section{Super Dandy}\label{sec:dandy}
Super Dandy は我々が PlayStation でのゲーム開発を行っていた 1998 年に
開発されたシューティングゲームである。PlayStation アーキテクチャの
スプライト描画機能を用いて宇宙空間を表現しており、タイトルからゲーム本編中の
敵機の登場、攻略後のスコア一覧、エンディングなどのシーン切り替え、4 種類の
射撃と 2 種類の特殊射撃を駆使し、ステージをクリアしていくなど、ゲーム性も
高い。SuperDandy は開発する環境が変わる度に移植されており、過去には 
PlayStation2 Linux、OpenGL バージョンも作られた。(図\ref{fig:dandy})

\begin{figure}[hb]
\begin{center}
\includegraphics[scale=0.4]{images/dandy.pdf}
\end{center}
\caption{Super Dandy}
\label{fig:dandy}
\end{figure}

\newpage

Super Dandy が伝統的に移植されてきた背景には、ある程度のボリュームのある
ゲームであること、衝突判定やオブジェクト管理、ステージクリアによる
シーン切り替えなど、基本的なゲームとしての要素が入っていること、
そして動作結果を過去の環境と比較することで新たな環境のチューニングが行える
ことが挙げられる。

\section{Task Dandy(Super Dandy Task version)}\label{sec:taskdandy}
本研究を進めるにあたり、Super Dandy を Cerium の Task で書き換えた 
Task Dandy を作成した。Task Dandy はできるだけ元の Super Dandy のコード
やデータ構造を流用し、比較、テストが容易に行えるように設計した。

\subsection{データ構造}
データ構造は Super Dandy のものを流用している。Super Dandy では主に以下の
ようなデータが存在する。

\begin{itemize}
\item player:\\
  プレイヤーの操作する機体。xy 座標の他、残機数、無敵時間、
  コンテニュー回数などが存在する。
\item CHARACTER:\\
  敵キャラクターや敵の弾。xy 座標とその方向の速さ、体力、倒したときのスコア、
  オブジェクトの種類を表すキャラナンバー、死亡フラグなどがある。
  また、Move と Collision を関数ポインタで持ち、ステートパターンで
  切り替えて状態遷移する。
\item tama\_lv1〜laser\_lv3:\\
  プレイヤーが撃った弾。xy 座標と存在の有無を表すフラグを持っている。
  プレイヤーが射撃ボタンを押すと対応した弾の配列に状態が格納され、
  敵に当たるか、画面外に消えるまで存在フラグが立つ。
\end{itemize}

これらのデータは オブジェクトの情報として管理されるだけでなく、
その他のオブジェクトの移動や衝突判定時にも使用される。

\subsection{Task Dandy の Task}

Task Dandy では オブジェクトの動きや衝突判定をそれぞれ Move Task、Collision 
Task として並列に処理させることが出来るように分割している。それぞれの Task は
Super Dandy の Move や Collision が実行されるタイミングで生成され、
その後処理に必要なデータをセットして各 CPU に送られる。処理を終えた Task は 
post\_func により、計算結果をメインメモリ内のデータ領域に反映させ、全ての 
Task が終了した時点でゲームの 1 フレームが終了する。

\subsection{Property}\label{sec:property}
Task Dandy の Task は処理のために、複数のデータを set\_inData する
必要がある。特に Collision Task に使用するデータはオブジェクト自身の情報の
他にプレイヤーの機体、プレイヤーの出した弾など、種類が多く、全てを set\_in
Data するとコードが無駄に長く、煩雑になってしまう。そこで必要なパラメータを 
Property という構造体にコピーする。こうすることで多くのパラメータを 1回の
set\_inData で送ることが出来る。(図\ref{fig:property})

\begin{figure}[h]
\begin{center}
\includegraphics[scale=0.7]{images/property.pdf}
\end{center}
\caption{Property のデータ構造}
\label{fig:property}
\end{figure}

\subsection{SPE における状態遷移}
SPE では各々に固有の LS を持つ\ref{sec:spe}為、Super Dandy で使用していた
ステートパターンによる状態遷移は使用できない。これは関数ポインタに格納されて
いるアドレスが PPE 上のものであり、SPE では意味を為さないからである。

そこで SPE 上では Task の ID を変更することによりオブジェクトの状態遷移を
実現するようにした。CHARACTER 構造体に Task ID を格納する新たなパラメータを
追加した。以下のようなコードで状態が遷移する条件に入ると Task ID が
書き換えられる。

\begin{verbatim}
static int
state6(SchedTask *smanager, void *rbuf, void *wbuf)
{
    CHARACTER *p = (CHARACTER*)smanager->get_input(rbuf, 0);
    CHARACTER *q = (CHARACTER*)smanager->get_output(wbuf, 0);    
    player *jiki = (player*)smanager->get_input(rbuf, 1);

    p->y += p->vy;
    p->x += p->vx;
    if(p->y + 96 < jiki->y
       && p->y + 128 > jiki->y)
    {
        p->vy = 2;
        p->vx = ((jiki->x > p->x) ? 4 : -4);
        p->state_task = STATE0;
    }
    else p->state_task = STATE6;

    *q = *p;
    return 0;
}
\end{verbatim}

書き換えられた ID は次に Task を生成する際に使用され、別の種類の Task を
生成するようになる。

\begin{verbatim}
    int task_num = p->state_task;
    HTaskPtr state_task = tmanager->create_task(task_num);
\end{verbatim}

\subsection{SPE におけるオブジェクトの生成}
Super Dandy では敵オブジェクトが弾丸を作り出し、プレイヤーを攻撃する、
といったイベントが存在する。SPE に送られた Task 内でこのイベントが発生した時
、SPE の LS 内で弾丸オブジェクトの生成が行われる。しかし、Cerium の Rendering
 Engine はPPE 上の SceneGraph tree に登録されているオブジェクトを見て描画用 
Task を生成するので(\ref{sec:rendering} 節)、このままでは弾丸オブジェクトは
描画されない。また、複数の SPE 上の Task からこのオブジェクトのデータを参照
したい時、データを同期するためにも 1 箇所のメモリでオブジェクトを管理する方が
良い。よって SPE 内で生成されたオブジェクトデータを DMA 転送により
メインメモリへオブジェクトデータを送る必要がある。

Cerium は set\_outData と get\_output により、Task からデータを書き出すこと
ができるが、書き出すサイズと数が決め打ちである。例えば以下のコードでは
Puttama で弾丸オブジェクトを生成しているが、条件によって 0〜3 個の
弾丸オブジェクトが生成される為、オブジェクトの最大数分だけサイズをセット
しなければならない。これによって、余計な DMA 転送が発生する。

\begin{verbatim}
  if((p->dt1 > 60) && (p->dt1 <= 70))
    {
      if(p->dt1 % 2 == 1)
        {
          // Puttama は弾丸オブジェクトを生成する
          Puttama(0, rinkx - 16, rinky);
          Puttama(0, rinkx, rinky);
          Puttama(0, rinkx + 16, rinky);
        }
    }
  if((p->dt1 > 180) && (p->dt1 <= 240))
    {
      if(p->dt1 % 2 == 1)
        {
          rinkf2 = 1;
          Puttama(2, rinkx - 16, p->y - 32);
          Puttama(3, rinkx + 32 - 16, p->y - 32);
        }
      else
        {
          rinkf2 = 2;
        }
    }
\end{verbatim}

その為、Task Dandy の Task 内では set\_outputSize (\ref{sec:refine_wbuf})に
よって write buffer の大きさを再定義している。これにより無駄な DMA 転送は
抑えることができるが、メインメモリ上には予めオブジェクトの最大数分のメモリを
確保しておく必要がある。

\begin{verbatim}
    int obj_size = sizeof(ObjContainer)*DATA_LENGTH;
    HTaskPtr state_task = manager->create_task(task_id);
    ObjContainerPtr obj = (ObjContainerPtr)tmanager->allocate(obj_size);

    state_task->set_outData(0, obj, 0);
\end{verbatim}

\subsection{可変長な Output Data の定義}\label{sec:refine_wbuf}
図 \ref{fig:wbuf} にあるように write buffer の allocate は Task の実行前に
行われており、また DMA 転送により書き出されるサイズは事前に set\_outData で
指定したサイズとなるため、Task 内で書き出すデータサイズを変更することは
出来なかった。

そこで新たに{\bf set\_outputSize(int index, int size) } という API を
実装した。index にはサイズを変更したいバッファの番号を入れ、size には新たに
設定するバッファサイズを入れる。

write buffer は Task 実行前に allocate されるが、
Task 内で set\_outputSize をすることで set\_outData で設定されたサイズを書き
換える。そして事前に allocate された write buffer を free し、新たに設定
されたサイズで write buffer を allocate することで可変長な output Data を
定義している。(図\ref{fig:set_w2})
\if0
\begin{figure}[h]
\begin{center}
\includegraphics[scale=0.7]{images/set_wbuf1.pdf}
\end{center}
\caption{Task 実行前の allocate}
\label{fig:set_w1}
\end{figure}
\fi

\begin{figure}[h]
\begin{center}
\includegraphics[scale=0.8]{images/set_wbuf2.pdf}
\end{center}
\caption{output Data の再定義}
\label{fig:set_w2}
\end{figure}