RealBasic University

このコラムはStone Table Softwareのオーナーであり、またREALbasic Developerの編集者でもあるMarc Zeedar氏により書かれたものを、著者の許可を得て翻訳したものです。この翻訳はHREM Researchにより提供されています。この日本語版へのご意見はRBU-Jまでご連絡下さい。

URL: http://www.applelinks.com/rbu/020/
INDEXに戻る

REALbasicアニメーション;スプライト IV

我々のプログラムは順調に進行していますが、今度は最も複雑なNextFrameイベントを書かなければなりません。これはプログラムの核心です:ここのコードのすべては毎秒30フレームで実行されます(spriteSurface1のframeSpeedを2にセットした場合)。

複雑なコードを示す前に、我々がこのルーチンで何を行う必要があるかについて少し説明しましょう。我々の基礎的なゴールは下記の通りです:

うわー! 多くのことをしなければなりませんね。もっと複雑なゲームではどれほど複雑になりえるか想像できるでしょう!

しかしながら、我々が各ステップをひとつひとつ行なえば、それはそれほど困難でもありません。最初の部分のコードは次のようでしょう:

  
dim i, j, x, y as integer

//
// これはこのプログラムのメインルーチンです。ここで、鴨の移動、
// ユーザによるミサイルの発射、戦艦の移動等が行われます。
//

j = 0
for i = 1 to numberOfDucks
// 鴨が生きているか(可視状態か)のチェック
if duckSprite(i).visible then
j = j + 1

// ここで、我々はスプライトを右(+)あるいは左(−)に移動し、
// もし、スクリーンからはみ出したら反対側に移す
if duckSprite(i).direction then
duckSprite(i).theSprite.x = duckSprite(i).theSprite.x + duckSprite(i).speed
if duckSprite(i).theSprite.x > me.width then
duckSprite(i).theSprite.x = 0
end if
else
duckSprite(i).theSprite.x = duckSprite(i).theSprite.x - duckSprite(i).speed
if duckSprite(i).theSprite.x < 0 then
duckSprite(i).theSprite.x = me.width
end if
end if // duckSprite(i).direction
else
// スプライトが不可視の状態の場合:
// 爆発中かを調べ、そうであれば爆発の計数(exploding)を増加させる。
// 爆発の計数が30以上であれば、スプライトを終了(close)する。
//
if duckSprite(i).exploding > 0 then
duckSprite(i).exploding = duckSprite(i).exploding + 1
if duckSprite(i).exploding > 30 then
duckSprite(i).exploding = 0
duckSprite(i).theSprite.close

drawScore
end if
end if
end if // visible
next // i

この最初の部分はすべての鴨に対する大きなループです。我々は鴨が可視の状態(生きているか)かどうかをチェックし、そして、鴨が生きている場合にはそれを移動させます。鴨の移動方向はdirection属性によって、その移動量はspeed値(ピクセルの数)で決まります。

鴨を移動させた後、遠くに移動させすぎてスクリーンからはずれたかをチェックします。もしそうならば、それを反対側に移動させ、そして「まわりに込んで」反対の端からスタートするようにします。

我々の鴨が「死んでいる」(隠された状態)ならば、我々はそれが爆発しているかどうかチェックします。爆発しているならば我々はexploding属性を増加させます(exploding属性が1以上である場合、我々はそれが爆発していることが判ります) 。それが30を越えた場合は、爆発が見られる時間が十分に経過しましたので、我々はスプライトを終了(close)し、そのexploding属性を0に設定します。

我々の最終ステップは爆発が終了した後、スコアを表示することです。

次のコードは上記のコードの後に続きます:

  
if j = 0 then // 全ての鴨が撃たれたので、ゲームは終了
gameOver = gameOver + 1
if gameOver > 30 then
me.close
end if
end if

// ここではミサイルを移動させる。移動は5ピクセル毎で、鴨よりも速い
// ことに注意。
if missileLaunched then
shot.y = shot.y - 5

// ミサイルはスクリーンを外れたか?そうであれば、それを終了さす(close)
if shot.y < 0 then
shot.close
missileLaunched = false
end if
end if

最初に、我々は鴨がすべて撃たれたかどうかをチェックします。そうならば、我々は、spriteSurface1の実行を止めます(それはme.closeラインです)。撃つ鴨がいない場合に継続することには意味はありませんから。

次に、ちょうどアニメーションの各フレームで鴨を移動させたように、今度はミサイルを移動させます(もし存在していれば)。鴨は水平だけに移動しますが、ミサイルは垂直に移動します。それで、鴨ではx属性へ数を加えたか、引きましたが、ミサイルでは垂直のy属性を変化させます。そして、ミサイルは単に上方に移動しますので、我々は引き算だけをします。我々は、ミサイルを速く移動させたいので、それを一度に5ピクセル移動させます。(この値を色々と変更して、それがどのようにゲームを変えるか確かめてみましょう。)

最後に、我々はミサイルがスクリーンの上端を越えたか(何にも当たることがなく)かどうかチェックします。もし、そうであれば、我々はミサイルを削除し、missileLaunchedfalseに設定します。

このルーチンは長いルーチンです。しかし、幾つかのステップへそれを分解すれば簡単になります。この最終部分はプレーヤーの相互作用を取り扱います。

スプライトサーフェイスオブジェクトは少し奇妙です:それはそれ自身の小さな世界の中で動いているので、ユーザがキーを押したかどうか確かめるために、普通のウィンドウのKeyPressedのイベントを使用することができません。その代わりに、スプライトサーフェイスにユーザが特別のキーを押したかどうかを尋ねなければなりません。しかし、そのときでさえ、スプライトサーフェイスは少し異なっています:特別のASCII文字が押されたかどうかを尋ねることができません--その代りに、16進法のkeycodeを渡さなければなりません。

Keycodeはキーボードにより異なります:フランスのキーボードは、ある文字のために英語のキーボードよりわずかに異なるkeycodeを使用します!

次の図表は英語のキーボード用の16進法のkeycodeを示しています:


[拡大図]

つぎはユーザの相互作用のためのコードです:

    
//
// ここではユーザの相互作用を処理します
//

// 左矢印が押された場合
if spriteSurface1.keytest(&h7B) then
if direction = 1 then //左向き

// Buildup は加速のために使われています:ユーザが
// キーを長く押せば押すほど、戦艦は速く移動します
// (最高速度10まで)
buildup = buildup + 1
if buildup / 10 = buildup \ 10 then
speed = speed + 1
if speed > 10 then
speed = 10
end if
end if
else
// 反対方向に行こうとしているので、speedとbuildupの
// 値をリセットする
speed = 1
buildup = 1
end if

// 戦艦を左に移動し、スクリーンからはみ出せば反対側にセットする
shipSprite.x = shipSprite.x - speed
if shipSprite.x < 0 then
shipSprite.x = me.width
end if
direction = 1

// 右矢印が押された場合
elseif spriteSurface1.keytest(&h7C) then
if direction = 2 then //右向き

// Buildup は加速のために使われています:ユーザが
// キーを長く押せば押すほど、戦艦は速く移動します
// (最高速度10まで)
buildup = buildup + 1
if buildup / 10 = buildup \ 10 then
speed = speed + 1
if speed > 10 then
speed = 10
end if
end if
else
// 反対方向に行こうとしているので、speedとbuildupの
// 値をリセットする
speed = 1
buildup = 1
end if

// 戦艦を右に移動し、スクリーンからはみ出せば反対側にセットする
shipSprite.x = shipSprite.x + speed
if shipSprite.x > me.width then
shipSprite.x = 0
end if
direction = 2

// スペースバーが押された場合で、ミサイルが空中になければ
elseif spriteSurface1.keytest(&h31) and not missileLaunched then

// 新しいミサイルを生成し、移動を始めさせる
missileLaunched = true

x = shipSprite.x + (shooter.width / 2) - (missile.width / 2)
y = shipSprite.y - missile.height
shot = SpriteSurface1.NewSprite(missile, x, y)
shot.group = -1
if soundOn then
zap2.play
end if
score = score - 25
drawScore

// return または Esc が押された場合
elseif spriteSurface1.keytest(&h24) or spriteSurface1.keytest(&h35) then
me.close
end if

このコードのほとんどは、我々が以前に行ったことがあるものに非常に似ています:我々は、そのxyの属性を加減することによりスプライトを移動させます。そして、それが行きすぎた場合には、スクリーンの反対側に移します。

新しいものはbuildUp変数です。これはまた私がこのゲームに取り込んだちょっとした工夫です。基本的に私は、プレーヤーの戦艦(あるいは銃)の移動が加速するようにしたかったのです:プレーヤーが長く矢印キーを下げておくほど、戦艦は速く移動する(ある速度まで)。

今、speed変数を増加することも可能でしょう。しかし、これはnextFrameが呼ばれる度(毎秒少なくとも30回)に起こりますので、それはほとんど瞬間に(1秒未満で)起こるでしょう。ですから、その代わりに、私は別の値(buildUp)を使用して、アニメーションの各フレームの間にそれを増加させます。buildUpが10で丁度割れる場合、私はそのとき速度を1段階上げるます。したがって、1秒で速度は3段階上がります(毎秒30フレームのアニメーションと仮定して)。最高速度が10ですので、最高速度を達成するのに3.3秒かかります。これで結果はプレーヤーの戦艦の速度は徐々に加速されます。

プレーヤーが方向を変更すれば、速度buildUpが1に戻されます。こうすることの欠点(バグ?)は、上記のコードでは矢印キーが放された場合にユーザの速度をリセットするための処置がされていないということです。したがって、プレーヤーは最高速度に設定できますが、プレーヤーが方向を変更しないかぎり、常に最高速度で移動します。

追記

REALbasicでは、スラッシュが、割り算の役割をします。ですから、5 / 10 = 0.5。バックスラッシュもまた割り算ですがそれは整数の除算です:それは、小数点の右へのすべてを無視して、結果の整数部分だけを返します。したがって、5 \ 10 = 0です。

したがって、buildUpが10で丁度割り切れるかどうかは、次のようなif文で確かめることができます:

 if buildup / 10 = buildup \ 10 then 

我々の次の短いコードはプレーヤーがミサイルを発射するためにスペースを押したかどうか確かめます。これはミサイルが既に飛んでいない場合にのみ許されます:より複雑なゲームでは、ユーザが同時に複数のミサイルを放つことを可能にするできるでしょう。そのとき、あなた(プログラマ)は複数のミサイルを追跡しなければならないでしょう(おそらく、現在の鴨の配列に似たミサイルの配列を使って)。

ミサイルを発射するためには、我々は単に新しいスプライトを作り、その初期の位置をセットします。それが生成されれば、我々の前のコードがアニメーションの次のフレームから自動的にそれを移動させるでしょう。

最後に、我々はユーザがEsc または Returnキーを押すことによりゲームを終了したかどうかチェックします。どちらのキーもプログラムを終了させません--それらはspriteSurface1を停止させて、進行中のゲームを止めるだけです。(明らかに、実際のゲームでは、終了させる前にユーザに確認したいでしょう。)

さて、次は何でしょうか? これで終わりです! これでShooting Galleryの全部です。私は、あなたがそれを楽しみ、スプライトについて少し勉強されたと思います。私が約束しましたように、我々はいつかより複雑なものを取り扱うでしょう。しかし、これはあなたにとっての入門であり、自分のアニメーションゲームを書こうという動機を与えたことでしょう。

全ての必要な画像やサウンドを含んだ完全な、完成したプロジェクトファイルがダウンロード可能です。

それで実験してみて下さい:変数のいくつかを変更して、異なることを試してみてください。それを改良してみて下さい、それをよりよく遊べるようにして下さい。例えば、プレーヤーの「戦艦」(銃)を横と同時に垂直に移動させてみるのはどうでしょうか? 鴨に爆弾を落とさせ、そして下方へ急降下させるのはどうでしょうか? 異なる種類の鴨はどうですか? 鴨がすべて撃たれた時にゲームが終了する代わりに、より困難なレベルにプレーヤーを進めてはどうですか?

さらに多くのやることがあります:私は正しい方向への第一歩をあなたに示しただけなのです!

次週

我々はREALbasicの基本を勉強するでしょう。我々のチュートリアルのうちの一部は少し上級者用でした。それで、私は読者の中の初心者の方のために特別のものを用意しました。

Letters

先週、スプライトサーフェイスのアニメーションの代替の方法として、タイマー・コントロールによってその更新メソッドを繰り返し呼ぶということを私が言ったのを覚えていますか? さて、Sarahさんはそれを試みて、以下のように書いてくれました:

Marcさん、

私は、ただそれを走らせる代わりにスプライトサーフェイスを更新するためのタイマーを実装しようとしましたが、それはなんの助けにもなりませんでした。それはスプライトサーフェイスを実行するよりずっと遅くなりました。私は、スプライトサーフェイスの更新を恐らくミリセカンド毎に呼んでいるタイマーを用いてゲームを行なえるようにするためには鴨および発射の速度を2倍にしなければなりませんでした! 何匹かの鴨が撃たれた場合(全ての鴨ではありませんが)、OS Xはまだクラッシュします。それは衝突が起ったときで、その処理部に行く前に起こるように見えます。プログラムは、私が戦艦を移動させる場合にもクラッシュします。

さて、私の質問:どのようにして、衝突の処理部でミサイルがs1で鴨がs2であるとわかるのですか? s1とs2の両方のグループをテストしてみるべきではないでしょうか?

面白い情報ですね、Sarahさん。新しい情報を有り難う。私はREAL software社がその問題を解決するだろうと確信していますが、今のところはMac OS Xでのスプライトには問題があると思っただけです。 私はいつか更新を行なう方法を私自身で試してみなければならないでしょう:私は優れた制御性という考えは好きですが、タイマーは正確なタイミングを行なうにはまったく信頼性に欠けます。

(私が何を言いたいか知るには、文字列を移動させるようなことを毎秒行なわせるようにしてタイマーをスタートさせてみると良いでしょう。そして、動かしているプログラムの編集メニューをプルダウンします。タイマーは動作しません。わかりましたか? マックが他の事で忙しすぎるとき、タイマーは呼ばれないのです。もちろん、これはMac OSのときのみかもしれません――OS Xでは異なるでしょう。)

あなたの後半の質問は当を得たものです! 2つのスプライトのどれがどれであるかを確かに知ることはできません:理論上、どちらもミサイルでありえます。しかし、実際上、それは常にs1なのです。なぜって? 私にはわかりません。しかし、あなたが2つを入れ替えて、s2がミサイルであるかを確かめても、それは決して起りません。それはミサイルはより後で作られ、それでリストの最初にあるというようなことから起るのでしょう。

私はそのバグに気づき、その変更について考えました、しかし、締切が迫っていましたし、誰かが気付くだろうといい加減に考えました:私はあなたの鋭い目に賞品を与えるべきですね! 私が行った方法はうまく作動していますが、それはよいプログラミングの仕方ではありません。それが一貫して作動しても、あなたのプログラミングの環境に依存してはいけません。バグや奇妙な動作はREALbasicの新しいリリースで直されるかもしれません。その時には、あなたのプログラムは急にクラッシュし始めるでしょう。そして、あなたはその理由を理解するために苦労することでしょう。

Christoph Schererさんもスプライトの問題について書いてきてくれています:

今日は、

スプライトサーフェイスの「更新」メソッドは私のゲームの習作中のエラーを無くすると思いますか? 複数のミサイルが敵に向かって発射され、いくらかが残っている時に、それらは次の目標(期待されるように)に向かいますが、どれも衝突しません。私はスプライトが衝突するべき時にそれらが正確にどうなっているのかを見るために"nextframe"イベントの"special action/tab"チェックにデバッグのブレークポイントをセットしました。さて、どうなっていたでしょう:すべての値、グループ、プライオリティおよびその他のものは間違いありませんでした。ですから、それらは技術的には衝突するはずです...

私は本当に絶望的になりつつあります。これらの馬鹿げたエラーは苛立たしいものです。特にあちこち探したときには...

Christophさん、お役に立てればと思い、あなたのプロトタイプのゲームをダウンロードしてみました(グラフィックスは非常に印象的です)。しかし、私はそれからはあまり多くのことは解りませんでした。何かが判るためには、恐らくあなたの実際のソース・コードを捜し回る必要があるでしょう。そのゲームは私のマックOS 9では極度にクラッシュしがちであることに気づきました。これはそれがCarbonバージョンであるからでしょう(Carbonはその問題の一因でありえます)。単純なPPCバージョンも同じ問題を持っていますか? (RBのCarbonアプリケーションはより遅くよりバグが多いというのがこれまでの私の経験です。)あなたのアプリケーションはメモリ割当てを大きくすることが出来ませんでしたので、問題が少ないメモリ割当てにあるのかを調べることが出来ませんでした。

それはあなたのゲームが余りにも複雑であるからともいえるでしょう:同時に動くいくつのスプライトを使っていますか? 恐らく、あまりにも多いと、REALbasicは衝突問題を上手く処理しないでしょう。

Christophさんの問題に対して考えある方は、私まで連絡して下さい 。素晴らしいREALbasicのアニメーションゲームを幾つか紹介することは楽しいでしょう:あなたが書いたものか、あなたの知っているゲームがあれば、私までそのURLへ送ってくだい。そうすれば次のコラムでそれへのリンクを公開します。


RBU-Jの通知サービス!コラムが発表されるたびに日本語版REALbasic Universityのお知らせの emailがあなたに届きます。登録・削除は ここ から。

INDEXに戻る