1.なにをつくる?
やはりスマホアプリを作る場合、「ゲーム」が作りたいですよね。ということで、今回はモグラたたきっぽいゲームを作ろうと思います。あくまで勉強用の「それっぽいゲーム」ですからね。多大な期待はしないで、「あーー、そうやって作るんだ」くらいの気持ちでお願いします。
2.仕様
まずは仕様です。とりあえず今回のバージョン(Ver. 1.0)では画面に円を表示し、それをタップすると消える。というものにします。ただ、消えるだけではタップできたかよくわからないので、得点表示をするようにします。
- 一定時間ごとに円を表示する
- 表示された円をタップすると消える(タッチではなくタップとする)
- 表示する円の位置はランダムとする
- 表示した円は一定時間後消える(消え方は徐々に消えるようにする)
- 表示した円とタップできた円の数を表示する
- 円を表示した数を表示する
- 円をタップできた数を表示する
タッチではなくタップにする理由は、タッチだと画面を適当に撫でるだけで反応するからです。きちんと狙ってもらうためにタップのみとします。
さて、ゲームになると少し仕様が複雑になってきますね(今回は単純なので、少ないですけど)。きちんとしたゲームの場合、もっと複雑になるので、仕様書を作った方がいいと思います。仕様書があれば、後で修正するときにとても楽なので作ることをお勧めします。そして、仕様書を作成することで色々と必要な機能が見えてきますので。
3. 本アプリで使うCorona SDK の機能
モグラたたきっぽいゲームを作成するにあたり使用する機能です。必要に応じてそれぞれ確認してください。
3.1 本サイトで説明したCorona SDKの機能
3.2 Lua言語
乱数生成
- math.randomseed()
- math.random()
文字列処理
- string.format()
Lua言語の標準関数などは以下の書籍を参考にしてください。今回使用する関数については・・・ちょっと少ないですが載っています。
4.プログラミング
それではプログラムを作成していきましょう。全てのソースコードは本ページの最後にあるので、そちらを確認してください。プログラムは他の方々の書き方も参考にして、自分なりの書き方を行ってください。
今回は単純なプログラムなので、「分かりやすく簡単」をモットーに作ったつもりです。
4.1 共通利用の変数宣言
まずは共通利用する変数の宣言を行います。
-- 名前が長いので、変数に代入 local centerX = display.contentCenterX local centerY = display.contentCenterY local contentHeight = display.contentHeight local contentWidth = display.contentWidth -- 乱数(毎回違う数値にするため) math.randomseed(os.time()) -- 点数用 local scoreHeight = 30 -- 点数の表示高さ local allCnt = 0 -- 全表示数 local tapCnt = 0 -- タップできた数
11~15行目
画面のサイズや中心位置を毎回長い変数で呼び出すのはちょっと面倒なので、今回は変数に入れて使用します。
18行目
円の表示位置をランダムにするために、乱数を生成します。その際、常に違う乱数にするために、初期値を時刻に設定しています。
20~23行目
画面上部に点数表示を行うための変数です。表示した数、タップした数を保存するための変数です。
4.2 モグラオブジェクトの作成関数
モグラ(円)を作成する関数です。ここでは円ではなくモグラと表現します。
モグラ関数に必要な機能は、モグラの描画、フェードアウト処理、タップ処理の3つになります。それらの処理をまとめて実行できる関数にしています。なので、この関数は少し大きいです。しかし、それほど難しいことはしていないので、ソースと下の解説を確認してください。
----------------------------------------------------------------------------------------- -- 関数名 : MakeMogura -- 内 容 : モグラオブジェクトの作成 -- 戻り値 : 無し ----------------------------------------------------------------------------------------- local function MakeMogura() print("MakeMogura") allCnt = allCnt + 1 -- モグラのサイズ local size = math.random(20, 30) -- モグラの表示位置 local x = math.random(size, contentWidth - size) local y = math.random(scoreHeight + size, contentHeight - size) -- 描画 local mogura = display.newCircle(x, y, size) ----------------------------------------------------------------------------------------- -- 関数名 : Timeout -- 内 容 : モグラオブジェクトのタイムアウト処理(削除) -- 戻り値 : 無し ----------------------------------------------------------------------------------------- local function Timeout() print("Timeout") if (mogura ~= nil) then print("delete") display.remove(mogura) mogura = nil end end -- フェードアウト処理 local fadeout = transition.fadeOut(mogura, {time = 2000, onComplete = Timeout}) ----------------------------------------------------------------------------------------- -- 関数名 : Tap -- 内 容 : モグラオブジェクトをタップしたときの処理 -- 戻り値 : true ----------------------------------------------------------------------------------------- local function OnTap() print("Tap") tapCnt = tapCnt + 1 -- フェードアウトの削除 if (fadeout ~= nil) then transition.cancel(fadeout) end -- オブジェクトの削除 if (mogura ~= nil) then display.remove(mogura) mogura = nil end return true end mogura:addEventListener("tap", OnTap) end
33行目
モグラを作成する関数が呼ばれたので、モグラを作成するのは確定です。なので、全モグラの表示数を1増やします。
35~41行目
モグラ(円)オブジェクトを作成します。モグラのサイズと描画位置をランダムにするため、乱数取得関数を使用しています。位置を取得する際、sizeを足したり引いたりしています。さて、なぜそうするかわかりますか?
それは、描画したオブジェクトを画面内に収めるためです。もし、その足したり引いたりをしないと、画面には半分だけ表示されたり、ほとんど表示されないなんてことが発生します。ユーザからすると、画面に表示されてないのにカウントが上がった、これはバグだ!となる可能性があります。
ここまででサイズ、表示位置が確定したので、41行目では円オブジェクトを作成しています。
43~58行目
モグラがタップされなかった(できなかった)時の処理、タイムアウト処理です。まず、イベントに追加するための関数を作成します。Lua言語ではこのように、関数の中に関数を作成することができます。
タイムアウトした際、必要になる処理はモグラオブジェクトの削除です。なので、この関数ではモグラオブジェクトの存在確認後に描画エリアから削除し、完全に開放するためにnilを代入しています。
今回は徐々に消えるということで、Corona SDKに用意されているフェードアウト処理を使用しています。2000ミリ秒で消え、フェードアウトが完了したら先ほど作成したタイムアウト用の関数を呼び出すようにしています。
さて、58行目のフェードアウト関数の戻り値を変数に入れていますね。なぜわざわざ変数に入れているのでしょうか?わかりますか?これは次のタップイベントの処理で必要になるので、詳しくはそちらで説明します。がしかし、自分でも考えてみてくださいね。
60~81行目
モグラをタップしたときの処理です。タップした際、まずはタップしたので、タップできた数を1加算します。
続いて、フェードアウトの削除とありますね。タップしたその瞬間もフェードアウト処理は動作しています。もしここでフェードアウトを止めないと、
- この後行うオブジェクトの削除処理
- フェードアウト完了時のタイムアウト処理で行うオブジェクトの削除処理
の2回、削除処理が動くことになります。オブジェクトが削除されてもフェードアウト自体は継続され、2000ミリ秒後にタイムアウト処理を実行してしまいます。なので、フェードアウトを止めます。
先ほどなぜフェードアウト関数の戻り値を変数に入れるのか質問しましたよね。答えはわかりますよね。ここで不要になるフェードアウト処理を停止させるためです。
続いて、オブジェクトを削除します。ここまででタップ時の関数は完了です。
81行目でモグラオブジェクトにタップイベントを追加し、モグラオブジェクトの作成は完了です。
さて、タップイベント時に呼ばれる関数の戻り値が「true」になっているということを説明していませんでしたが、大丈夫ですか?過去のタップイベントについての説明を確認してもらえれば分かるのですが、どうですか?
答えは「タップイベントを貫通させないため」ですよね。後ろのオブジェクトにはタップイベントを発生させないために、「true」を返しています。
4.3 点数表示用のテキストオブジェクトの作成と点数更新処理
点数(タップできたモグラ数と表示した全モグラ数)を画面の上に表示する処理です。点数を更新する関数を作成し、一定間隔で呼ぶように作っています。
-- スコア表示 local textScore = display.newText("0 / 0", centerX, scoreHeight * 0.5, native.systemFont, 30) ----------------------------------------------------------------------------------------- -- 関数名 : UpdateScore -- 内 容 : スコア表示更新 -- 戻り値 : 無し ----------------------------------------------------------------------------------------- local function UpdateScore() textScore.text = string.format("%d / %d", tapCnt, allCnt) end
86行目
とりあえず初めの点数は0なので、「0 / 0」とし画面に描画します。
88~95行目
タップした数と表示した数の変数から、点数のテキストオブジェクトを更新しています。
疑問ありませんか?
さて、なぜタップしたときや、モグラオブジェクト作成時に、テキストオブジェクトを変更しないの?といった疑問でてきませんか?今回の処理であれば、そこで行っても問題なく動作すると思います。ただ、将来複雑な処理を行うようになった際、そのようにしてしまうと不具合につながる可能性があります。
さて、何が問題なのでしょうか?それは「タップイベントの更新」と「モグラオブジェクト作成時の更新」が同時に行われたらどうなるの?というのが問題になります。今回の処理は一瞬で終るような処理なので、まず同時に動作するという確率はないといえるでしょう。
しかし、将来ある程度処理に時間がかかる関数を同時に実行させた場合、どうなるでしょうか。予想もつかない動きをする可能性があります。もしそのような不具合が発生した場合、調査するのは至難の業です。なので、そのようなことが起きないように作ることを常に考えることをお勧めします。
複雑な処理になる場合、当然セマフォなどロック機構を搭載する必要がありますが、そもそも呼ばれるのが1か所だけであれば問題は発生しません。なので、タップとオブジェクト作成時の2タイミングで動作させるのではなく、別のタイマで更新させれば問題は起きない。ということです。
4.4 動作開始
ここまでで準備完了です。メインの動作開始です。とはいえ、関数を一定時間ごとに呼ぶだけなので、簡単な処理しかありませんが。
MakeMogura() timer.performWithDelay(3000, MakeMogura, -1) timer.performWithDelay(300, UpdateScore, -1)
98行目
起動直後に1回、モグラ作成関数を呼び出します。そうしないと、タイマで呼び出される3秒後まで何も動きがないからです。
99行目
一定時間ごとにモグラ作成関数を永遠に呼び出すようにしています。
100行目
点数を更新する関数を一定間隔で呼び出しています。300ミリ秒周期であれば、タップして数が増えているように感じます(ちょっと反応悪いかな?くらいは感じますが・・・まあ、気になるようであれば呼び出す間隔を短くすればいいだけです)。
5.動作確認
さて、プログラムが完成したら動作確認が必要です。仕様通り動くか確認してみましょう。確認内容は以下の通りです。
- 円が一定間隔で表示される
- 表示された円は一定時間後、消える
- 円をタップすると円が即座に消える
- 円が表示されると、表示回数のカウントが1増える
- 円をタップすると、タップ回数のカウントが1増える
6. チャレンジ
さて、せっかくなのでぜひ挑戦してもらいたい追加機能があります。それは以下の2点です。
- 円の表示間隔をランダムにする(500ミリ秒から3000ミリ秒くらいの間隔)
- 表示する物を円、四角形および三角形にする
答えは処理を追加したソースコードを最後に掲載しておきます。
6.1 課題1:円の表示間隔をランダムにする(500ミリ秒から3000ミリ秒くらいの間隔)
課題1では、円を表示する関数を呼び出す間隔を常に変更する必要があります。なので、
timer.performWithDelay(3000, MakeMogura, -1)
これではダメですよね?これだと3秒間隔で関数を呼び出し続けます。では、どうしましょうか?
ヒントは「円を作ったら、次の予約をしよう!!」といったところでしょうか。
6.2 課題2:表示する物を円、四角形および三角形にする
課題2は課題1よりも簡単だと思います。ヒントというか答えになってしまう気がしますが、分岐処理を追加し、円だけではなく四角形や三角形を作るようにするだけですね。参考までにリンクを張っておきます。
7.あとがき
さて、モグラたたきっぽいゲームはどうでしたか?「こんなのゲームじゃないよ」といった声が聞こえてきそうですが、まあ基本なんで。ちなみに少し装飾すれば立派なゲームになりますよ。例えば、描画するのを円ではなく爆弾にする、タイムアウトで爆発し、タップで停止するようにする。よくありそうなカジュアルゲームの完成です。
Corona SDKでは少ないソースで色々なことができます。今回のモグラたたきっぽいゲームのソースは100行くらいです。実行行数で言えば50行程度でしょうか。せっかくなので、色々と改造して楽しんでもらえたらと思います。
最後にソースコードの書き方についてなのですが、私はC言語から学んできたので、クセでLua言語では不要な括弧などつけてしまっています(個人的には条件文には括弧は必須だろうと思ってますが)。そこら辺はご自分で良いようにしてください。スペースのあけ方も同様です。
8.ソースコード
プログラムの全文を掲載します。
<注意>
- プログラムにリンクしているファイルを使用する場合、ファイル名をmain.luaとしてください
- アイコンや設定ファイルなどはCorona SDKが自動で生成したものです
- 文字コードがUTF-8になっています(Corona SDKではUTF-8が標準の為)
- ファイル名は必要に応じて変更し利用してください
- 本サイトの注意事項を確認してください
ソースコードを使用する場合、上記注意とともに、自己責任でお願いします。
9. 更新履歴
版 | 更新日 | Corona SDKのバージョン |
新規作成 | 2016/9/26 | v2016.2951 |