HyperCard(HyperTalk)でFlappy Birdを作る

これは、マイナー言語 Advent Calendar 2014の記事であり、拙作記事「Flappy Birdを作る」シリーズ3作目でもあります。かつてのMacintoshに無償同梱されていたHyperCardを使ってFlappy Bird風ゲームを作りながら、スクリプト言語HyperTalkを紹介します。

* [Sprite KitでFlappy Birdを作る](http://qiita.com/oinariman/items/dc0296aee10aadeb50cd)
* [Sprite KitでFlappy Birdを作る(Swift版)](http://qiita.com/oinariman/items/ef3395608fbcfb85eb99)

# HyperCard(HyperTalk)とは

HyperCardは"ハイパーテキストを実現した最初の商用ソフトウェア"です([HyperCard(Wikipedia)](http://ja.wikipedia.org/wiki/HyperCard))。HyperTalkはその上で動いたスクリプト言語です。

コンピューターの歴史上におけるHyperCardの存在意義、立ち位置については[この記事](http://wirelesswire.jp/yomoyomo/201403190930.html)をどうぞ。実際の使用感について、あえて雑なたとえで説明すると、

PowerPoint + ペイントツール + スクリプト言語(HyperTalk)

といった感じです。PowerPointにおいてのスライドのように、1画面ごとの「カード」の連なりが1ファイル(「スタック」と呼ばれます)を構成しています。カードにはペイントツールで絵を描けますし、文字を表示する「フィールド」やクリックしたら別のカードに飛ばせる「ボタン」を配置することもできます。さらに、HyperTalkを使ってボタンに別のカードに遷移する以外の振る舞いを与えることもできます。

# ゲーム開発環境としてのHyperCard

Wikipediaでも紹介されていますが、Cyan社のゲーム『MYST』はHyperCardで作られました。『MYST』は3Dグラフィックで作られた無人島を探索するアドベンチャーゲームです。発表当時の一般的なパソコンではリアルタイム・レンダリング不能な高精細な3D世界を、場面場面の止め絵を遷移させる手法で表現していました。Cyan社は80年代から同様の手法で『The Manhole』、『Cosmic Osmo and the Worlds Beyond the Mackerel』、『Spelunx』といった子供向けのアドベンチャーゲームを発表していました。これら作品群の、白黒ドットパターンを駆使したグラフィック表現には眼を見張るものがあります。(以下の動画だと、『Spelunx』が鮮明でわかりやすいと思います。)

* [The Manhole (1988)](https://www.youtube.com/watch?v=YyOTq1EpV5o)
* [Cosmic Osmo - Gameplay](https://www.youtube.com/watch?v=ThU1QLD21n4)
* [Spelunx and the Caves of Mr. Seudo](https://www.youtube.com/watch?v=pqNJCgnohWE)

場面遷移によるゲームブック的なアドベンチャーゲームは、HyperCardではスクリプトなしで制作できます。カードに場面場面の絵を描き、クリック可能な箇所に透明なボタンを配置します。ボタンにはほかのカードへ遷移をマウス操作のみで設定できます。

次に、Flappy Birdのような2Dゲームを作ることを考えてみます。アドベンチャーゲームとちがって、飛んだり跳ねたりするキャラクターが必要です。スプライトの代わりのゲームオブジェクトとして、ボタンが使用できます。ボタンには32x32ピクセルのアイコンを設定できます。アイコンはスクリプトで変更可能で、アニメーションさせることができます。スコアなどの表示にはフィールドが利用できます。カードに描いた絵をゲームの背景として利用できます。さらに、複数カード間でボタン・フィールド・背景を共有できる、「バックグラウンド」という概念も存在します。

![hypercard_game.png](https://qiita-image-store.s3.amazonaws.com/0/2503/6338836b-4d4a-1f8c-65c2-2f268fdd759d.png)

# Flappy Birdを作る

本題に入ります。Flappy Birdゲームの構成の仕方については、拙作記事([Sprite KitでFlappy Birdを作る](http://qiita.com/oinariman/items/dc0296aee10aadeb50cd))をご参考ください。今回はHyperCardなので、鳥の代わりにスタックのアイコンを飛ばせてみます。以下のような仕上がりになりました: 

![2014-12-19 09_54_36.gif](https://qiita-image-store.s3.amazonaws.com/0/2503/5a91192f-8bc7-151f-b531-e4f76449b047.gif)

## ソース

ゲーム全体のスクリプトです。右下の「Play」ボタン内に仕込みます。クリックしたらゲームを開始し、ゲームオーバーになったら停止します。

```
on mouseUp
  -- initialization
  put 0 into vy
  put 1.5 into ay
  put -10 into impact
  put -8 into pillar_vx
  put 0 into count
  put false into mouse_down
  put 16 into interval
  put 5 into pmax
  put 2 into w
  put false into game_over
  repeat with i = 2 to pmax
    hide btn i
    set rect of btn i to 0,0,0,0
  end repeat
  put 0 into card field 1
  set loc of btn "stack" to 100,10
  
  repeat
    -- pillar
    if game_over is not true then
      if count mod interval is 0 then
        repeat with i = 2 to pmax
          if visible of btn i is false then
            show btn i
            if random(10) > 5 then
              set rect of btn i to 512,160,512+32,288
            else
              set rect of btn i to 512,0,512+32,128
            end if
            exit repeat
          end if
        end repeat
      end if
      repeat with i = 2 to pmax
        if visible of btn i is true then
          if item 1 of loc of btn i < -32 then
            hide btn i
            put card field 1 + 1 into card field 1
            play "boing" "a"
          else
            get loc of btn i
            put item 1 of it + pillar_vx into item 1 of it
            set loc of btn i to it
          end if
        end if
      end repeat
    end if
    
    -- user input
    if game_over is not true then
      if the mouse is down then
        if mouse_down is not true then
          put true into mouse_down
          put impact into vy
        end if
      else
        put false into mouse_down
      end if
    end if
    
    -- gravity
    put vy + ay into vy
    get loc of btn "stack"
    put item 2 of it + trunc(vy) into item 2 of it
    set loc of btn "stack" to it
    
    -- collision
    repeat with i = 1 to pmax
      put left of btn "stack" into l
      put top of btn "stack" into t
      put right of btn "stack" into r
      put bottom of btn "stack" into b
      repeat with j = 1 to 4
        if j is 1 then
          get l&","&t
        else if j is 2 then
          get r&","&t
        else if j is 3 then
          get r&","&b
        else if j is 4 then
          get l&","&b
        end if
        if it is within rect of btn i then
          if game_over then
            if i is 1 then
              exit mouseUp
            end if
          else
            play "boing"
            put true into game_over
            put 0 into vy
          end if
        end if
      end repeat
    end repeat
    
    wait w
    put (count + 1) mod 360 into count
  end repeat
end mouseUp
```

## 舞台・登場人物

今回はカード1枚のみを使用しました。画面下部のパターンで塗ってあるところが地面です。今回は、前進を表現する地面のスクロールは省略しました。その他、以下の部品を用意しました。

|名前|種類|番号|役割|
|:---|:---|:---|:---|
|-|ボタン|1|地面の衝突判定用|
|-|ボタン|2|柱|
|-|ボタン|3|柱|
|-|ボタン|4|柱|
|-|ボタン|5|柱|
|"Play"|ボタン|6|ゲーム開始ボタン|
|"stack"|ボタン|7|鳥|
|-|フィールド|1|スコア表示|

ボタンやフィールドは、新しく作られたものほど番号が大きくなります。番号が大きいほど前面に表示されるので、鳥は最大番号にしました。地面の衝突判定用の1番のボタンは透明です。2〜5の柱のボタンには土管の絵を割り当てたいですが、32x32ピクセル以上の画像は表示できないので(※)、妥協策としてただの四角いボタンにしました。

※ResEditというユーティリティを用いてスタックにPICTリソースを埋め込み、ボタンに貼り付けて表示する方法も裏ワザ的に存在します。(参考: [HyperCardのスピードアップ](http://hp.vector.co.jp/authors/VA019519/hcspd.html))

## シナリオ

ゲームループの概要をBASIC風に書くと、以下のようになります:

```
10 初期化
20 ループ開始
30 柱を作る
40 柱を動かす
50 柱が画面外に行ったら+1点
60 マウスクリックされたら鳥を跳ね上げる
70 鳥に重力をかける
80 衝突判定。ぶつかっていたらループを抜ける
90 20に戻る
```

処理の一部をかいつまんで説明します。

### 鳥に重力をかける

下記が鳥に重力を与える箇所です。HyperTalkの文の区切りは改行です。

```
    -- gravity
    put vy + ay into vy
    get loc of btn "stack"
    put item 2 of it + trunc(vy) into item 2 of it
    set loc of btn "stack" to it
```

#### 1行目
"--"はコメント行を意味します。

#### 2行目
HyperTalkは英語に近い文法と言われています。変数への代入には=演算子ではなく、put〜intoを使います。vyは鳥のY方向速度、ayはY方向重力加速度です。`put vy + ay into vy`で毎フレームの重力加速度加算をしています。日本語で書くと、`vy+ayをvyに入れろ`といったところでしょうか。

#### 3行目
さらに英語らしい要素として"it"があります。`get loc of btn "stack"`は何をしているのでしょうか。"loc"はボタンのプロパティである"location"の省略形、"btn"は"button"の省略形で、略さずに書けば`get the location of button "stack"`となります。`"stack"という名前のボタンの位置を得よ`といったところですが、「得て」どうするのでしょうか。この文は、得た値を暗黙的に"it"という変数に代入します。続く2行で、このitを使ってボタンの変位を行います。

#### 4,5行目
ボタンの位置(location)は、X座標とY座標のカンマ区切りデータで表されます。カンマ区切りデータは「アイテムリスト」と呼ばれ、各要素には"item"キーワードでアクセスできます。3行目で「得た」ボタンの位置情報は`X座標,Y座標`形式になっているので、`item 2 of it`でY座標を取り出せます。これにY方向速度を加算してさらにitに入れなおしています。座標は整数である必要があるので`trunc()`でキャストしています。5行目の"set"キーワードで、ボタンの位置を変更しています。

### 衝突判定

HyperCardには衝突判定に使える"is within"キーワードがあります。これは、2次元の点が矩形領域に入っているかどうかを判定します。ボタンのプロパティ"location"はボタン矩形領域の中央の座標です。鳥の中央の点だけで判定すると、鳥が壁にめり込んでしまってイマイチなので、ボタンの四つ角について判定処理を行います。

![collision____.png](https://qiita-image-store.s3.amazonaws.com/0/2503/4f222bd3-2990-6a53-8bc3-6a5f4feac0af.png)

障害物(地面、柱)との衝突判定部分部分は以下です。

```
    repeat with i = 1 to pmax
      put left of btn "stack" into l
      put top of btn "stack" into t
      put right of btn "stack" into r
      put bottom of btn "stack" into b
      repeat with j = 1 to 4
        if j is 1 then
          get l&","&t
        else if j is 2 then
          get r&","&t
        else if j is 3 then
          get r&","&b
        else if j is 4 then
          get l&","&b
        end if
        if it is within rect of btn i then
          if game_over then
            if i is 1 then
              exit mouseUp
            end if
          else
            play "boing"
            put true into game_over
            put 0 into vy
          end if
        end if
      end repeat
    end repeat
```

あらかじめ鳥のボタンの四つ角の座標を変数に入れておき、`repeat`ループで各障害物との判定を行います。

```
      put left of btn "stack" into l
      put top of btn "stack" into t
      put right of btn "stack" into r
      put bottom of btn "stack" into b
```

`get l&","&t`は、座標のカンマ区切りデータを作って"it"変数に代入しています。衝突を検知したら、"play"で音を鳴らしてゲームオーバー状態にします。"boing"はデフォルトで使用できるサウンドリソースです。第2引数に音階を与えてメロディを鳴らすことも可能です。このゲームでは点数が入る際にも使用しています: 

```
play "boing" "a"
```

これは「A」の音、「ラ」でボインを鳴らします。

# おわりに

やってみたら、わりとさくっとFlappy Birdが作れました。みなさんも、直観的にインタラクティブなコンテンツの創作ができるHyperCardでゲームなりなんなり作ってみましょう！実はまだAppleのサイトでHyperCardが入手できますから、古い実機かエミュレーターで動かすことができますよ！

・・・という話をするつもりでしたが、AppleサイトからHyperCard 2.2Lite-Jのリンクが消えていました。

https://discussionsjapan.apple.com/thread/10150354
>HyperCard 2.2 Lite-Jをダウンロードしたいのですが・・・
>現在でもダウンロードできるのでしょうか？

>7 年位前まではダウンロード可能だった様ですが、今ではダウンロード出来なくなっていますね。