2011年12月15日木曜日

キー同時押し対応: Processingでゲームを作るときに使うキーの状態を管理するクラス

課題

Processingでは、キーを押したり、離したりといったイベントに対応したプログラムを簡単に書くことができる(※1)。 しかし、どのキーが押されていて、離されているのかを調べることは、素直にはできない。 これはキーの同時押しなどに、ナイーブには対応できないということだ。

ゲームでは、キーの同時押しなどにも対応する必要がある。 そこで、キーの状態を管理するクラスを作ってみた。

※1: それぞれkeyPressed()とkeyReleased()メソッドをオーバーライドする。


原因

Processingには、イベントが発生したキーの値を調べるために、char型のkeyとint型のkeyCodeという変数が用意されている(※2)。 この変数には、最後にイベントが起こったキーが入っている。 それは、一般に現在押されているキーとは異なる。

※2: キーがASCIIコード表にあるものなら、keyにその文字が入る。ない場合は、keyの値がCODEDになり、keyCodeにUPなどの対応した値が入る。詳しくはリファレンスを参照。


解決方法

キーのクラスを作り、押されているか否かという状態をフィールドに用意する。

キーが押されたり、離されたりしたイベントが発生したら、どのキーでそのイベントが起こったか調べ、押されているか否かという状態を更新する。


実装例

クラス図は次のようになる。

キー
キーの名前: String
keyの値: char
keyCodeの値: int
キーが押されているか否か: boolean
引数で指定されたキーと一致するか(keyの値: char, keyCodeの値: int)
引数で指定されたキーが押された(keyの値: char, keyCodeの値: int)
引数で指定されたキーが離された(keyの値: char, keyCodeの値: int)
キーの絵を描画(表示位置のx座標: int, 表示位置のy座標: int, 横幅: int, 高さ: int, フォント: PFont, フォントサイズ: int)

キーの絵を描画するメソッドは、下の方に書いた使用例で使うために用意した。 これは必要なければ、用意しなくてもいい。

具体的なコードは以下である。

// Processingでキーの状態を管理するクラス
class Key {
  String name; // キーの名前
  char key; // keyの値
  int keyCode; // keyCodeの値
  boolean isPressed; // 押されているか否か

  // コンストラクタ
  Key(String tempName, char tempKey, int tempKeyCode) {
    name = tempName;
    key = tempKey;
    keyCode = tempKeyCode;
    isPressed = false;
  }

  // 引数で指定されたキーと一致するかどうかを判定
  boolean equals(char tempKey, int tempKeyCode) {
    if (tempKey != CODED) {
      if (key == tempKey) {
        return true;
      } else {
        return false;
      }
    } else {
      if (keyCode == tempKeyCode) {
        return true;
      } else {
        return false;
      }
    }
  }

  // 引数で指定されたキーが押された
  void pressed(char tempKey, int tempKeyCode) {
    if (equals(tempKey, tempKeyCode) == true) {
      isPressed = true;
    }
  }

  // 引数で指定されたキーが離された
  void released(char tempKey, int tempKeyCode) {
    if (equals(tempKey, tempKeyCode) == true) {
      isPressed = false;
    }
  }

  // キーの絵を描画する
  void draw(int xOffset, int yOffset, int width, int height, PFont font, int fontSize) {
    int textColor, backgroundColor;

    if (isPressed == true) {
      textColor = 255;
      backgroundColor = 0;
    } else {
      textColor = 0;
      backgroundColor = 255;
    }

    stroke(0);

    fill(backgroundColor);
    rect(xOffset, yOffset, width, height);

    textAlign(CENTER);
    textFont(font);
    fill(textColor);
    text(name, xOffset + width / 2, yOffset + height / 2 + fontSize / 2);
  }
}

使用例

ゲームでよく使うキー(上下左右スペース)の状態を画面に表示するプログラムを作ってみた。

ゲームのキーのコントローラー

まず、ゲームでよく使うキーをコントロールするコードを、メインプログラムに直接書くとぐちゃぐちゃする。 よって、ゲームのキーのコントローラーのクラスを用意した。 このクラスは、ゲームに必要なキー(この例では上下左右スペース)によって、変更を受ける。 クラス図は以下のようになる。

ゲームに使うキーのコントローラー
上キー: キー
下キー: キー
左キー: キー
右キー: キー
スペースキー: キー
使用するすべてのキー: キー[]
キーを描画する横幅: int
キーを描画する高さ: int
キーを描画するフォント: PFont
キーを描画するフォントのサイズ: int
引数で指定されたキーが押された(keyの値: char, keyCodeの値: int)
引数で指定されたキーが離された(keyの値: char, keyCodeの値: int)
キーのコントローラーを描画(表示位置のx座標: int, 表示位置のy座標: int)

具体的なコードは以下である。

// ゲームに使うキーのコントローラーのクラス
class KeyController {
  Key upKey, downKey, leftKey, rightKey, spcKey; // 使用するキー
  Key[] keys; // キーを入れる配列
  int keyWidth, keyHeight; // キーの描画サイズ
  PFont keyFont; // キーの描画フォント
  int fontSize; // キーの描画フォントのサイズ

  KeyController() {
    // キーの描画の設定
    keyWidth = 50;
    keyHeight = 50;
    fontSize = 10;
    keyFont = createFont("MS Gothic", fontSize);

    // 上下左右スペースキーを作る
    upKey    = new Key("UP",    (char)CODED, UP);
    downKey  = new Key("DOWN",  (char)CODED, DOWN);
    leftKey  = new Key("LEFT",  (char)CODED, LEFT);
    rightKey = new Key("RIGHT", (char)CODED, RIGHT);
    spcKey   = new Key("SPC",   ' ',         0);

    // 作ったキーを登録する
    keys = new Key[5];
    keys[0] = upKey;
    keys[1] = downKey;
    keys[2] = leftKey;
    keys[3] = rightKey;
    keys[4] = spcKey;
  }

  // 引数で指定されたキーが押された
  void pressed(char tempKey, int tempKeyCode) {
    // 全キーに引数で指定されたキーが押されたことを通知
    for (int i = 0; i < keys.length; i++) {
      keys[i].pressed(tempKey, tempKeyCode);
    }
  }

  // 引数で指定されたキーが押された
  void released(char tempKey, int tempKeyCode) {
    // 全キーに引数で指定されたキーが離されたことを通知
    for (int i = 0; i < keys.length; i++) {
      keys[i].released(tempKey, tempKeyCode);
    }
  }

  // キーの状態を描画する
  void draw(int left, int top) {
    /*
                  -------
                  | UP  |
      -------------------------
      | SPC |LEFT |DOWN |RIGHT|
      -------------------------
    */
    upKey.draw(   2 * keyWidth + left,             top, keyWidth, keyHeight, keyFont, fontSize);
    downKey.draw( 2 * keyWidth + left, keyHeight + top, keyWidth, keyHeight, keyFont, fontSize);
    leftKey.draw(     keyWidth + left, keyHeight + top, keyWidth, keyHeight, keyFont, fontSize);
    rightKey.draw(3 * keyWidth + left, keyHeight + top, keyWidth, keyHeight, keyFont, fontSize);
    spcKey.draw(                 left, keyHeight + top, keyWidth, keyHeight, keyFont, fontSize);
  }
}

メインプログラム

ゲーム用のキーのコントローラーのクラスを作って、そちらにコードをまとめたので、メインプログラムは以下のようにすっきり書ける。

// ゲームに使うキーのコントローラーのオブジェクト
KeyController keyController;

void setup() {
  size(400, 400);
  keyController = new KeyController();
}

void draw() {
  keyController.draw(100, 100);
}

void keyPressed() {
  keyController.pressed(key, keyCode);
}

void keyReleased() {
  keyController.released(key, keyCode);
}

メモ

この実装は、Casey Reas、Ben Fry著、『Processingをはじめよう』、オライリー、2011年程度の知識がある人が見ることを想定している。 このため、カプセル化などは行なっていないし、拡張性のある柔軟な設計にもしていない。

たとえば、KeyControllerクラスとKeyクラスには、デザインパターンのObserverパターンの一部を使っている。 しかし、KeyControllerに、柔軟にKeyを追加したり、削除したりするようなメソッドを用意していない。

Javaに関してより高度な知識があれば、KeyやKeyControllerクラスのフィールドを外部から隠蔽し、必要なsetterやgetterを付けてもいいかもしれない。 また、KeyやKeyControllerから、画面表示に関連するコードを分離してもいいかもしれない。

2011年11月22日火曜日

オブジェクト指向がわかっている人向けのProcessing(Java)の配列

前提


1.配列の宣言

Processing(Java)の配列は、実はオブジェクトだ。 だから、使うには、配列の変数を宣言して、newで配列のインスタンスを用意してそれに代入する必要がある。

たとえば、整数型で100個の要素を持った配列xを使いたければ、次のように書く。

int[] x; // 配列の変数xを宣言
x = new int[100]; // 整数型で100個の要素を持った配列のインスタンスを用意してxに代入

このように、配列の変数を宣言するには、「型[] 配列の名前」と書く。 また、インスタンスを用意して代入するには「配列の名前 = new 型[個数]」と書く。

これは、1行で書くこともできる。

int[] x = new int[100];

※Cで、同様の配列を用意するには「int x[100];」である。なお、Cの配列はオブジェクトではない。


2.配列の要素

配列の要素は0番目からはじまる。 よって、10個の要素を持った配列xを用意した場合、使えるのはx[0]〜x[9]である。


3.配列の要素の数

Processing(Java)の配列はオブジェクトなので、フィールドやメソッドを持つ。

よく使うフィールドに、配列の要素の数lengthがある。 たとえば、配列xの要素の数は、「x.length」で取得することができる。

これを使うと、整数型の要素10個持った配列xを用意して、0から9の値を代入するプログラムは、以下のように書ける。

int[] x;
x = new int[10];
for (int i = 0; i < x.length; i++) {
    x[i] = i;
}

※Cの配列はオブジェクトではないので、フィールドがない。Cの場合は、sizeof(x)/sizeof(x[0])のように、sizeof演算子を使って、「配列全体のサイズ/配列の要素1個分のサイズ」で求める。

※Cの文字列の場合、実際に入っている文字数と入れられる最大文字数は異なる。sizeof演算子で求められるのは最大文字数の方である。実際に入っている文字数はstrlen()関数で求める。また、関数を使わないで、文字列の終わりであるヌル文字「\0」が入っている位置を見つけてもいい。


4.配列の初期化

Processing(Java)の配列は、以下のように初期化することができる。

int[] x = { 31, 20, -9 };

この場合、newでインスタンスを用意するという手続きは、明確に書かない。

※Cの場合、初期化は「int x[] = { 31, 20, -9 };」でほとんど同じ。


5.配列の要素のコピー

Processing(Java)の配列は、オブジェクトであり、配列の要素のコピーを生成するメソッドclone()が用意されている。 以下のように配列yに、配列xのコピー生成して代入することができる

int[] y = x.clone();

ちなみに、事情はここで説明しないが、cloneした結果には、以下のようにキャストを行なった方が安全である。

int[] y = (int[])x.clone();

※この方法は、浅いコピーと言われているもので、ある種の場合、完全なコピーを生成してくれるわけではない。

※Cの場合、次のようにfor文などでくり返し代入する処理を書く必要がある。Processing(Java)でもこの方法は有効である。

for (i = 0; i<配列の要素数; i++) {
    y[i] = x[i];
}

Todo?

浅いコピーと深いコピー、オブジェクトを要素に持った配列、etc.

2011年11月19日土曜日

CSS3 3D Transformsの表示テスト

2013年7月31日追記: transform-styleの指定を適切なセレクタに移動。Firefox、Opera、IE用の記述も追加。

Safari、Chrome、Firefox、Opera、IE用のCSS3 3D Transforms Module 3の表示テスト。 2013年7月31日現在、ChromeとFirefoxではZバッファがおかしい、Operaは3次元回転しない、IEはチェックしていないがIE10では動くという情報もある。

ちなみに、Safariでも、Windowsだとグラフィックカードなどによっては3D表示に対応しない: 「CSS3の3D表示機能が、実はSafariでも使えないことがある件 」

コードは以下。

CSS

#wrapper {
 position: relative;
 width: 200px;
 height: 200px;
 padding: 50px;
 border: 1px solid black;
 transform-style: preserve-3d;
 -webkit-transform-style: preserve-3d;
 -moz-transform-style: preserve-3d;
 -o-transform-style: preserve-3d;
 -ms-transform-style: preserve-3d;
}
#panel1, #panel2 {
 position: absolute;
 width: 200px;
 height: 200px;
 animation-iteration-count: infinite;
 animation-timing-function: linear;
 animation-duration: 8s;
 -webkit-animation-iteration-count: infinite;
 -webkit-animation-timing-function: linear;
 -webkit-animation-duration: 8s;
 -moz-animation-iteration-count: infinite;
 -moz-animation-timing-function: linear;
 -moz-animation-duration: 8s;
 -o-animation-iteration-count: infinite;
 -o-animation-timing-function: linear;
 -o-animation-duration: 8s;
 -ms-animation-iteration-count: infinite;
 -ms-animation-timing-function: linear;
 -ms-animation-duration: 8s;
}
#panel1 {
 background-color: red;
 animation-name: rotate1;
 -webkit-animation-name: rotate1;
 -moz-animation-name: rotate1;
 -o-animation-name: rotate1;
 -ms-animation-name: rotate1;
}
#panel2 {
 background-color: blue;
 animation-name: rotate2;
 -webkit-animation-name: rotate2;
 -moz-animation-name: rotate2;
 -o-animation-name: rotate2;
 -ms-animation-name: rotate2;
}
@keyframes rotate1 {
 0% { transform: perspective(100px) translateZ(-100px) rotateY(90deg) }
 50% { transform: perspective(100px) translateZ(-100px) rotateY(270deg) }
 100% { transform: perspective(100px) translateZ(-100px) rotateY(450deg) }
}
@keyframes rotate2 {
 0% { transform: perspective(100px) translateZ(-100px) rotateY(0deg) }
 50% { transform: perspective(100px) translateZ(-100px) rotateY(180deg) }
 100% { transform: perspective(100px) translateZ(-100px) rotateY(360deg) }
}
@-webkit-keyframes rotate1 {
 0% { -webkit-transform: perspective(100px) translateZ(-100px) rotateY(90deg) }
 50% { -webkit-transform: perspective(100px) translateZ(-100px) rotateY(270deg) }
 100% { -webkit-transform: perspective(100px) translateZ(-100px) rotateY(450deg) }
}
@-webkit-keyframes rotate2 {
 0% { -webkit-transform: perspective(100px) translateZ(-100px) rotateY(0deg) }
 50% { -webkit-transform: perspective(100px) translateZ(-100px) rotateY(180deg) }
 100% { -webkit-transform: perspective(100px) translateZ(-100px) rotateY(360deg) }
}
@-moz-keyframes rotate1 {
 0% { -moz-transform: perspective(100px) translateZ(-100px) rotateY(90deg) }
 50% { -moz-transform: perspective(100px) translateZ(-100px) rotateY(270deg) }
 100% { -moz-transform: perspective(100px) translateZ(-100px) rotateY(450deg) }
}
@-moz-keyframes rotate2 {
 0% { -moz-transform: perspective(100px) translateZ(-100px) rotateY(0deg) }
 50% { -moz-transform: perspective(100px) translateZ(-100px) rotateY(180deg) }
 100% { -moz-transform: perspective(100px) translateZ(-100px) rotateY(360deg) }
}
@-o-keyframes rotate1 {
 0% { -o-transform: perspective(100px) translateZ(-100px) rotateY(90deg) }
 50% { -o-transform: perspective(100px) translateZ(-100px) rotateY(270deg) }
 100% { -o-transform: perspective(100px) translateZ(-100px) rotateY(450deg) }
}
@-o-keyframes rotate2 {
 0% { -o-transform: perspective(100px) translateZ(-100px) rotateY(0deg) }
 50% { -o-transform: perspective(100px) translateZ(-100px) rotateY(180deg) }
 100% { -o-transform: perspective(100px) translateZ(-100px) rotateY(360deg) }
}
@-ms-keyframes rotate1 {
 0% { -ms-transform: perspective(100px) translateZ(-100px) rotateY(90deg) }
 50% { -ms-transform: perspective(100px) translateZ(-100px) rotateY(270deg) }
 100% { -ms-transform: perspective(100px) translateZ(-100px) rotateY(450deg) }
}
@-ms-keyframes rotate2 {
 0% { -ms-transform: perspective(100px) translateZ(-100px) rotateY(0deg) }
 50% { -ms-transform: perspective(100px) translateZ(-100px) rotateY(180deg) }
 100% { -ms-transform: perspective(100px) translateZ(-100px) rotateY(360deg) }
}

HTML

<div id="wrapper">
<div id="panel1">
</div>
<div id="panel2">
</div>
</div>

2011年11月16日水曜日

Fate/stay nightでわか(ったような気がす)るProcessing(Java)のオブジェクト指向:1

それなりに需要があるかもしれないので、まとめてみました。


前提

  1. Casey Reas, Ben Fry著、船田巧訳、『Processingをはじめよう』、オライリージャパン、2011年の「9章オブジェクト」以前の内容は理解している。
  2. Fate/stay nightを知っていて、ネタバレがあっても構わない

1.オブジェクト指向プログラミング

オブジェクト指向のプログラムは、「コンピュータの中にオブジェクトというものがあって、これがお互いにメッセージを送り合うことで動いている」といった感じで作ります。

なお、メッセージを送ることを、メッセージ・パッシング(message passing: メッセージ送信)といいます。

オブジェクトというのは、Fate/stay night(以下Fate)を例にすると、サーヴァント(使い魔)のようなものです。オブジェクトにメッセージを送ることは、サーヴァントに指示を出して使役することに対応します。

Fateと違うのは、いくらでもサーヴァントと契約できることです。いろいろなサーヴァントを召喚して、使役して、プログラムを組み立てます。


2.クラス

Processingは、実際にはJavaだったりします。Javaはクラス・ベースのオブジェクト指向言語で、オブジェクトを使うために、クラスを用意する必要があります。
クラスは、オブジェクトの設計図のようなものです。

このクラスは、Fateのクラスのようなものだと思ってください。実際にはあんまりちゃんと対応しないけど、取りあえず。

クラスには、そのクラスのサーヴァントが持つべきパラメータと、そのクラスのサーヴァントを使役するのに使う指示を用意します。
このパラメータのことをフィールド(field データを記入する欄)、指示をメソッド(method 処理方法)と言います。

たとえば、セイバー(剣の騎士)のクラスのサーヴァントには、真名と魔力というフィールドを持たせ、攻撃しろというメソッドを用意するとします。他にもいろいろあると思いますが、簡単のためにこの3つにしておきます。

これをクラス図というもので書くと以下のようになります。

セイバー
真名
魔力
攻撃しろ()

プログラム的には、フィールドは変数の宣言、メソッドは関数の定義みたいに書きます。
というわけで、具体的には、以下のようになります。

/* セイバーのクラスの定義 */
class Saber {
    /* フィールドの宣言 */
    String trueName; // 真名
    int magicalEnergy; // 魔力

    /* メソッドの定義 */
    void attack() {
        println("剣で攻撃しました。");
    }
}

3.オブジェクトの生成と使用

ここまでは、まだ、クラス(設計図)を用意しただけで、実際にオブジェクトを使うところまで行っていません。

これは、セイバーというクラスはあるけれど、具体的にサーヴァントが召喚されていないし、使役もされていないということです。

それでは、召喚して使役する方法を説明します。まず、とりあえず、仮の名前を用意します。
普通の変数の宣言と同じように、型(ここではクラス名)と変数名(仮の名前)を以下のように書きます。

Saber ahoge;

次に、プログラムの中でサーヴァントを召喚し、契約します。

ahoge = new Saber();

ここで、new クラス名()で、召喚を行ないます。
仮の名前に=で代入することで、名前で縛ることで、契約がなされます。

これは、プログラム的には、クラスの定義から、オブジェクトを生成したことになります。
このオブジェクトは、設計図であるクラスではなく、具体的な実体があるので、インスタンス(instance 具体的な実体)とも呼ばれます。

Fateの現界している個々のサーヴァントがインスタンスということです。ちなみに、new(召喚)しても、=(契約)しないと、現界できなくなって、消滅し(ガーベッジ・コレクションされ)てしまいます。

召喚して契約したら、使役できます。具体的には、以下のように、「仮の名前.メソッド名()」で、サーヴァントに指示を出して使役します。

ahoge.attack();

4.オブジェクトの初期化

変数を初期化するときには、「int i = 0;」のように、宣言と同時に代入していました。
しかし、一般にオブジェクトは、複数のパラメータを持っているので、同じように初期化することはできません。

では、たとえば、Saberの場合、召喚時に真名と魔力を初期化したいとします。
次のように書くことで、真名を「アルトリア・ペンドラゴン」、魔力を「40」と、初期化するという文法にするのはどうでしょうか?

ahoge = new Saber("アルトリア・ペンドラゴン", 40);

で、実際、Javaの場合、そういう文法になっています。

これはよく見ると、Saberという関数があって、その引数が真名と魔力になっているようなものです。
また、この関数は、Saberクラスのサーヴァント(オブジェクト)を返すので、戻り値の型はSaberです。

というわけで、この関数の宣言を以下のように書いてはどうでしょう。

/* セイバーのクラスの定義 */
class Saber {
    /* フィールドの宣言 */
    String trueName; // 真名
    int magicalEnergy; // 魔力

    /* コンストラクタの定義 */
    Saber(String name, int energy) {
        trueName = name;
        println("真名を" + name + "にしました。");
        magicalEnergy = energy;
        println("魔力を" + energy + "にしました。");
    }

    /* メソッドの定義 */
    void attack() {
        println("剣で攻撃しました。");
    }
}

戻り値の型はSaberなので、わざわざ書くと、2回Saberと書くことになるので、書かないという文法にしておきます。

このように、オブジェクトを初期化するのに使う関数のようなものを、オブジェクト指向言語ではコンストラクタ(constructor 生成するのに使用されるもの)と呼びます。

文法的には、クラス名と同じ名前の関数の定義です。ただし、戻り値の型は書きません。


5.まとめ

  • オブジェクト指向のプログラムでは、サーヴァント(オブジェクト)を召喚して、使役することで、処理を実行する
  • クラスは、Fateのクラスみたいなもの
  • クラスの定義には、そのクラスのサーヴァントが持っているパラメータを変数の宣言みたいに、使役するのに使う指示を関数の定義みたいに書く
  • サーヴァントは、召喚して(new クラス名())、契約して(仮の名前に=で代入)、使役できるようになる
  • 使役するには、「仮の名前.メソッド名()」
  • サーヴァントを召喚と同時に初期化するのに、コンストラクタというものを使う
  • コンストラクタの定義は、クラス名と同じ名前の関数。ただし、戻り値の型を書かない

では、最後にProcessingのプログラムの全体を書いておきます。

Saber ahoge; // 仮の名前を用意

void setup() {
    /* セイバーのサーヴァントを召喚して、契約します */
    ahoge = new Saber("アルトリア・ペンドラゴン", 40);

    /* セイバーを使役します(攻撃するように命じます) */
    ahoge.attack();
}

/* セイバーのクラスの定義 */
class Saber {
    /* フィールドの宣言 */
    String trueName; // 真名
    int magicalEnergy; // 魔力

    /* コンストラクタの定義 */
    Saber(String name, int energy) {
        trueName = name;
        println("真名を" + name + "にしました。");
        magicalEnergy = energy;
        println("魔力を" + energy + "にしました。");
    }

    /* メソッドの定義 */
    void attack() {
        println("剣で攻撃しました。");
    }
}

Todo?

カプセル化とアクセス制御、継承、多態性、抽象クラスとインターフェイス。
同じクラスのサーヴァントでも、インスタンスによって、いろいろな宝具とか、攻撃が使えるようにする設計。

2011年5月31日火曜日

"9leap" Game Programming Camp @仙台参戦記:参加を終えて

戦場から帰ってきたら、待っていたのは、PerlでCGI書きだった(久しぶりだったので、CGI.pmの使い方を思い出すのに時間がかかった。いや、年のせいか?)。
EncodeでUTF-8とShift_JISと半角カタカナとiso-2022-jpと銃撃戦をしている間に、この果てしない宇宙のどこかでは、JavaとJavaScriptが戦争になっていた。なんかJava(Script)まで現れていたようだが。

就職活動中の女子大生にJavaとJavaScriptの違いを説明してみる
JavaとJavaScriptの20年戦争

ぼくからは、これを。

Douglas Crockford、「JavaScript: 世界で最も誤解されたプログラミング言語」

今回のキャンプはよかった。自分のコーディングのクセや欠点がすごくよくわかった。そして、考え方も。
食事をする時間を惜しんで、コンビニに走って、甘いモノを買ってきて、口に高速で詰め込んで、コードを書くのは楽しかった。

UEIから来ていた若い人たちも、おもしろかった。9leapのゲームを提供するシステムも、自分で登録してプレイしてみて、改めてすごくうまくできていると思った。才能を感じた。

参加者の人たちも、クセがあってよかった。コーディングするのに熱中していたので、他の人のゲームで遊んだり、あんまり話す余裕がなかったのが残念だ。

週アスプラスには、もう翌日には詳しい記事が載っているし驚いた(合図若松と人口知能は修正してあげて下さい)。
ゼミで学生たちに、週アスの記事を見せて、アトラスXを紹介したら、ウケていた。今度ゼミで作ってみてもいいかもしれない。


今回、若い人たちを育てるのに役に立つといいなという気持ちもあって、ゲームプログラミングキャンプに参加した。

もちろん、中身もすごくおもしろいことばかりだった。いろいろヒントも得られた。

でもね、本気で集中してガンガンやる。しかも、義務とかと一切関係なく。自分の持っているものと、限られた制約の中でいろいろやってみる。とにかくこれがすごく楽しい。

きみもやってみないか?


スタッフ、参加者のみなさんどうもありがとうございました!!


それはともかく、清水社長が風邪をひいたのは、キャンプの会場が寒かったのに、Tシャツ1枚だったからではないか。

2011年5月30日月曜日

"9leap" Game Programming Camp @仙台参戦記:#3

そして、当日がやってきた。前日も、遅くまで、オンライン、オフラインの打ち合わせをしていた。

というわけで、新青森駅始発の新幹線で、仙台へ向かった。今回の大震災で、新幹線は大きなダメージを受けた。復旧したのはやっと連休前。会場には予定通り開始時間直前に到着する。

参加者は、ぼく@akokuboのほか、@shi3z、@9leap、@hidemy、@Shocurry、@ricktsukida、@oldcadie、@sivakichだ。

ブログそのままの調子の清水社長の名講義がはじまる。

ゲームとは何か。
ゲームを作る時に注意すること。
大塚英志の『物語の体操』で解説されている、ウラジミール・プロップの「昔話の形態学」。

『ネットワークゲームデザイナーズメソッド』『ゲームデザイン誇大妄想狂』を読んだときの感覚が蘇る。

講義内容は、@sivakichさんが、週アスにとても詳しい記事を書いているので、そちらを見て欲しい。

Twitterがゲームになる!? 9leap開発キャンプ@仙台レポート

そして、今日のテーマは、Twitter。今回、enchant.jsに、簡単にTwitterから情報を取ってくるAPIがついた。それを使って、プログラムを書こうというわけだ。

なんといいタイミング。ぼくはちょうど前日まで、Twitterを使った研究の提案を作っていたところだった。


それでも、プログラミング部分の講義は、はなかなかハードだ。雰囲気を簡単に紹介して、後はサンプルを読めだ。

いや、これはいい。昔のプログラミング雑誌の感覚だ。

こんなことは、参加者を信頼しないとできない。そして、参加者もその信頼に応えた。今回の参加者は、JavaScriptに慣れていない人が多かった。それでもみんな自分の力でなんとかしようとがんばっていた。

今回、ぼくが決めていたことがある。
一つは、たくさんゲームを作ろう、クソゲーでも構わない。

参加者の一人@Shocurryさんは、たくさんゲームを投稿している。操作性とか、細かいことを言いだせばいろいろ欠点もあるゲームだ。でも、たくさんゲームを作り、それが実にいい味を出している。

本当におもしろいゲームって、こういう1000本ノックの向こうにあるんじゃないかと思う。

はじめて創った作品が、究極の傑作なんてことは、たぶんまずない。いや、あるのかもしれないが、それはほとんどの人とは縁がない話だ。


というわけで、今回、ぼくは未完成のものも含めて4本のゲームを作った。

1.桃太郎if(完成/未投稿)
2.ELIZA的な何か(未完成)
3.フォロワロワイヤル(完成/投稿)
4.フォロワーさんがころんだ(未完成)


「桃太郎if」は、昔、CやC#で作ったノベルゲームエンジンのサンプルデータのアトラスXへの移植版だ。アトラスXに慣れるために作ってみた。

ノベルゲームエンジンの簡単なものは、プログラミングの初歩の練習にちょうどいい。授業の開発演習で題材にした。

しかも、ノベルゲームツールがあれば、単なるノベルゲームだけではなく、クイズでも何でも意外といろいろなゲームを作ることができるので、作った後もおもしろい。

「桃太郎if」は、2回2分岐する4つのエンディングを持ったゲーム(?)だ。これを作ったことで、アトラスXに慣れることができた。



「桃太郎if」が完成したころに、アトラスXにTwitter連携機能がついた(なんと、キャンプ中に開発されていたのだ。できたてのほやほや機能!)。


そこで、次の「ELIZA的な何か」は、Twitter連携機能を使うつもりで作りはじめた。ELIZAは、ジョセフ・ワイゼンバウムが作った、ロジャーズの来談者中心療法のセラピストっぽい反応をするプログラムだ。ユーザが入力した文章を適当に解析して、おうむ返しをしたり、時折ランダムな反応をしたりする。Emacsを使っている人はM-x doctorで、相当するプログラムを起動できる。

なんだけど、希望する通りのUIを作ろうとすると、アトラスX自体を拡張しないと無理っぽいことがわかった。清水社長のゲームプログラミングで注意すべき「ないものねだりをしない」という話を思い出して、これは今回実装しないことに決めた。いつか作ってみたい。「ELIZA的な何か」のUIについては、スタッフの方にサポートしていただいた、ありがとうございます。


「フォロワロワイヤル」は、プレイヤーにジレンマ的な状況を作りたいと思って作ってみた。要するに、フォロワーから5名選び出して、2人ずつどちらを選ぶかを決めてもらうというものだ。基本はすぐにできた。作りはじめた段階では、アトラスXには重複しないようにフォロワーを抽出する機能がなかった(なんとこの機能もキャンプ中に追加された)ので、これを実装した。
そして、清水社長から、ユーザ名をIDに変え、アイコンを表示した方がわかりやすいとのアドバイスをもらって、作り直した。
これは投稿したので、是非、遊んで欲しい。

「フォロワロワイヤル」


最後の「フォロワーさんがころんだ」は、ゲームに動きを入れたくて、いろいろ考えた結果、「だるまさんがころんだ」をやろうとして作りはじめたものだ。だいたい完成したのだが、デバッグが終わらなかった。

ぼくは、けっこう、プロトタイピング的な方法でコードを書く。取りあえず、簡単なコードを書く。ちょっと機能を付け加えて、動作チェックする。それを繰り返して作っていく。

これは、簡単なものなら、うまくいく。しかし、ある程度以上、複雑なものを作ろうとすると、どこかで設計をしなおして、「とりあえず」で作った部分を整理しないとコードがぐちゃぐちゃになる。

今回はまったのはこれだ。設計しなおす時間が足りなかった。是非、完成させたい。

続く

2011年5月29日日曜日

"9leap" Game Programming Camp @仙台参戦記:#2

"9leap" Game Programming Camp@仙台の日がせまってきた。

今回、準備は何もしていない。

一応、JavaScriptの経験はある。開発もしている。しかし、ぼくが使っているのは、JavaScriptというよりはjQueryだ。

ぼくは、ずっとJavaScriptは邪悪だと思っていた。

JavaScriptが登場した頃、画面に現在時刻を表示したり、えらくセンスのない視覚効果をページに追加するために使われていた。それは、どちらかと言えば、必要ないものだ。

あるいは、フォームの入力値が適正かどうかを検証したり、ショッピングサイトで金額を計算したりするのに使われていた。

これらはもちろんあった方がいい。しかし、JavaScriptでこれらを記述してHTMLに埋め込んでいくと、ものすごくHTMLの可読性が落ちる。また、JavaScriptでDOMの操作を書くのは、はっきり言って、面倒すぎる。

システムをシンプルにしたいなら、ユーザに多少の不便を強いても、サーバ側で全部処理し、JavaScriptは使わないに限る。JavaScriptはユーザの使い勝手を向上させるための必要悪だった。

更に、デバッグしようにも、alertを使って、小ウィンドウに値を表示するだけという、信じられないくらいローテクな開発環境しかなかった。

おまけに、ブラウザによって、JavaScriptエンジンの挙動が異なり、開発はとても気がめいる。

しかしそれは過去の話だ。

FireBug、SafariのWebインスペクタに、Chromeのデベロッパーツール。JavaScriptが動的に追加したDOMの様子も確認できれば、普通のデバッガもついている。

各ブラウザ搭載のJavaScriptエンジンも、信じられないくらい高速化した。

そして、jQueryライブラリ。JavaScriptで書くと面倒なDOM操作、アニメーション、Ajaxが信じられないくらい美しく、簡単に書ける。おまけに、ブラウザ間の差異も吸収してくれる。

今や、JavaScriptというよりは、jQueryを使ってプログラムを書かない理由は何もないのだ。

そして、HTML5とCSS3だ。これらは、JavaScriptの使用が前提となっている。

今、Webで何かをはじめようとするなら、HTML5、CSS3、JavaScriptを使うべきだ。

しかし、JavaScriptは、一見、CやJavaに似ているが、かなり設計思想は異なっている。JavaScriptの本を読んでみるといい。CやJavaしか知らないプログラマには、見慣れない単語が出てくる。実は、JavaScriptは、関数型言語で、独特な変数のスコープ、関数の引数に関数を指定できたり、関数の戻り値に関数を指定できたりする(Cでも関数ポインタを使えば同じようなことはできるが)。

ぼくは、はっきり言って、C、Java、PHPのプログラマで、まだ生JavaScriptはうまく使いこなせていない。果たして大丈夫なのか?

そのとき、清水社長のこの記事を読んだ。

グッバイIVS 日本最強プレゼンバトル体験記

いろいろな意味でふっきれた。

続く