某BBSで「UNIX上のX Window System(Xlib)でゲーム作りたい」という話題があった。興味があったので過去の知識からベースになるプログラムを書いてみた。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/poll.h>
#include <sys/times.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>


#define EVENT (ExposureMask|KeyPressMask|KeyReleaseMask)

typedef struct {
  Display       *d;
  int            s;
  Window         w;
  GC             g;
  XImage        *osb;

  Bool           alive;

  unsigned char  vram[640 * 480];
  unsigned char  clut[256];
} AC;

static unsigned char sp1[] = {
 0,0,0,7,7,0,0,0,
 0,0,0,7,7,0,0,0,
 0,0,0,7,7,0,0,0,
 2,0,0,5,5,0,0,2,
 7,0,7,5,5,7,0,7,
 7,0,7,7,7,7,0,7,
 7,7,7,7,7,7,7,7,
 7,7,7,0,0,7,7,7
};


Bool InitApp( AC *ac, char *srv, char *title )
{
  ac->d = XOpenDisplay( srv );
  {
    Colormap c;
    XColor   p, t;

    c = DefaultColormap( ac->d, 0 );

    XAllocNamedColor( ac->d, c, "black"  , &p, &t );
    ac->clut[0] = p.pixel;
    XAllocNamedColor( ac->d, c, "blue"   , &p, &t );
    ac->clut[1] = p.pixel;
    XAllocNamedColor( ac->d, c, "red"    , &p, &t );
    ac->clut[2] = p.pixel;
    XAllocNamedColor( ac->d, c, "magenta", &p, &t );
    ac->clut[3] = p.pixel;
    XAllocNamedColor( ac->d, c, "green"  , &p, &t );
    ac->clut[4] = p.pixel;
    XAllocNamedColor( ac->d, c, "cyan"   , &p, &t );
    ac->clut[5] = p.pixel;
    XAllocNamedColor( ac->d, c, "yellow" , &p, &t );
    ac->clut[6] = p.pixel;
    XAllocNamedColor( ac->d, c, "white"  , &p, &t );
    ac->clut[7] = p.pixel;
  }
  ac->w = XCreateSimpleWindow( ac->d, DefaultRootWindow(ac->d)
                             , 0, 0, 320, 240, 0
                             , ac->clut[7], ac->clut[0]
                             );
  ac->g   = XCreateGC( ac->d, DefaultRootWindow(ac->d), 0, 0 );
  ac->osb = XCreateImage( ac->d
                        , DefaultVisual(ac->d,DefaultScreen(ac->d))
                        , 8, ZPixmap
                        , 0, ac->vram
                        , 640, 480, 8, 0
                        );

  XSelectInput( ac->d, ac->w, EVENT );
  XStoreName( ac->d, ac->w, title );
  XMapWindow( ac->d, ac->w );
  XFlush( ac->d );

  ac->alive = True;

  return True;
}

void CleanUpApp( AC *ac )
{
  XUnmapWindow( ac->d, ac->w );
  XDestroyWindow( ac->d, ac->w );
  XFreeGC( ac->d, ac->g );
  XCloseDisplay( ac->d );
}

void pset( AC *ac, int x, int y, int c )
{
  XPutPixel( ac->osb, x, y, ac->clut[c] );
}

void sp_put( AC *ac, int x, int y, unsigned char *s )
{
  int   i;
  unsigned char *b;

  b = &ac->vram[ac->osb->bytes_per_line * y + x];
  for ( i = 0; i < 64; i++ ) {
    if ( i % 8 == 0 ) {
      b += (ac->osb->bytes_per_line - 8);
    }
    if ( *s ) {
      *b = ac->clut[*s];
    }
    b++;
    s++;
  }
}

void Regulation( void )
{
#if 1
  static clock_t pre;
         clock_t now;
         clock_t diff;

  now = times(NULL);
  diff = now - pre;
  pre = now;
  if ( diff >= 2 ) {
    return;
  }
  poll( NULL, 0, (2 - diff) * 10 );
#endif
}

void refresh( AC *ac )
{
  XPutImage( ac->d, ac->w, ac->g, ac->osb, 0, 0, 0, 0, 320, 240 );
  XFlush( ac->d );
}

typedef struct {
  int x;
  unsigned char y;
  unsigned char c;
} star;

int main( void )
{
  static AC ac;
  Bool left = False;
  Bool right = False;
  star s[64];
  int  i;


  for ( i = 0; i < 64; i++ ) {
    s[i].x = rand() % 320;
    s[i].y = rand();
    s[i].c = (rand() % 6) + 1;
  }

  InitApp( &ac, NULL, "X-Game" );


  while ( ac.alive ) {
    XEvent e;

    if ( XCheckMaskEvent(ac.d,EVENT,&e) ) {
      switch ( e.type ) {
      case Expose:
	refresh( &ac );
	break;
      case KeyPress:
	switch ( XKeycodeToKeysym(ac.d,e.xkey.keycode,0) ) {
	case XK_Left:
	  left = True;
	  break;
	case XK_Right:
	  right = True;
	  break;
	}
	break;
      case KeyRelease:
	switch ( XKeycodeToKeysym(ac.d,e.xkey.keycode,0) ) {
	case XK_Escape:
	  ac.alive = False;
	  break;
	case XK_Left:
	  left = False;
	  break;
	case XK_Right:
	  right = False;
	  break;
	}
	break;
      }
    } else {
	static int x = 160;

	if ( left != right ) {
	  if ( left && x > 8 ) {
	    x--;
	  }
	  if ( right && x < 320 ) {
	    x++;
	  }
	}
	memset( ac.vram, 0, 640 * 480 );
	for ( i = 0; i < 64; i ++ ) {
	  pset( &ac, s[i].x, s[i].y++, s[i].c );
	}
	sp_put( &ac, x, 230, sp1 );
	refresh( &ac );
	Regulation();
    }
  }
  CleanUpApp( &ac );

  return EXIT_SUCCESS;
}

基本的にはWindowsのWinGを使ったゲーム作成と同じである。

関数Regulation()はゲーム速度を一定に保つためのもの。で、時間調整にusleep()の代わりにpoll()を使っているが、これは何となくpoll()の方が精度が良い気がしたからである。usleep()だと背景スクロールがガタガタした感じになった。

ちなみにusleep()はBSD由来の関数で、SYSV系には存在しない事が多かった。そんな時にpoll()を使うのは常套手段だった。

あと、このプログラムはLinuxで開発したのだが、WindowsのGetTickCount()に相当する関数はtimes()しかないようだ。これは分解能が1/100秒なので16ミリ秒でタイミングをとろうにもムリな感じである。仕方ないので、このプログラムでは20ミリ秒でタイミングを取っている。

そりゃ、gettimeofday()でミリ秒単位の時間が取得できるが、timeval構造体のtv_usecメンバはすぐに桁溢れして0に戻ってしまう。使い物にならない。一度XMAMEのオートフレームスキップ辺りのコードでも読んでみるか…。案外/dev/rtcとか直アクセスしてたりして。

後は色関係の所を説明すべきだろうが、面倒なので省略。昔雑記にWindowsでのパレットの扱いについて書いたが、その時に少し触れているので参照すべし(しても役に立たないと思うが)。変数名のclutはColor Look-Up Tableだ。PlayStationのプログラマなら身近な呼び方だろう。古来からの呼び方なら「パレット」か。paletteと書くよりタイプ量が少ないので良い。

その他注意が必要なのは、CURSESライブラリをリンクするとrefresh()がコンフリクトするくらいかな(笑)。


昔の雑記にも書いたかもしれないが、ウィンドウシステムのパレットカラー周りはグチャグチャしていて扱い辛い。って事でWindowsなプログラムでは主にハイカラー(15ビット色)を使うようになった(ちなみにローカルパレットを持っているのでパレットアニメーションも可能な予定)。

X Window Systemでは、この辺りの知識を仕入れた当時はパレットカラーなサーバしか手元に存在しなかった。で、ハイカラーやトゥルーカラーでのプログラミング方法を知らない。昔(4〜5年前だっけか?)NetscapeNevigatorのUNIX版がこれらのモードで実行するとcore吐いて死ぬという報告を読んだ事があるが、上記のプログラムも同じ運命かもしれぬ…。