9
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

HyperCard(HyperTalk)でFlappy Birdを作る

Last updated at Posted at 2014-12-21

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

HyperCard(HyperTalk)とは

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

コンピューターの歴史上におけるHyperCardの存在意義、立ち位置についてはこの記事をどうぞ。実際の使用感について、あえて雑なたとえで説明すると、

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』が鮮明でわかりやすいと思います。)

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

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

hypercard_game.png

Flappy Birdを作る

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

2014-12-19 09_54_36.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のスピードアップ)

シナリオ

ゲームループの概要を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

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

    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のリンクが消えていました。

HyperCard 2.2 Lite-Jをダウンロードしたいのですが・・・
現在でもダウンロードできるのでしょうか?

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

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?