読者です 読者をやめる 読者になる 読者になる

生産するブログ

プログラミング・デザイン等の備忘録。

CoronaSDKでタワーディフェンスゲームアプリを作りました

久しぶりにブログを更新します。

CoronaSDKで「脇役ファイターズ -タワーディフェンスゲーム-」という2D横スクロール型のタワーディフェンスゲームを制作しました。
かっぱまきやら家庭科の糸通しやらよくある日用品やらをファイターとした変なゲームです。

youtu.be

コンセプトは、地味なやつらに光を!という感じでしょーか。
図らずも、Unityに押されているCoronaという存在にも若干近いかもしれません…。

よろしければ、一度遊んで頂けると幸いです!

android版
脇役ファイターズ-タワーディフェンスゲーム- - Google Play の Android アプリ
[対応環境]Android 4.0以上
[リリース日]2015/08/13

iPhone版

[対応環境]iOS 7.0以上
[リリース日]2015/08/22

[レビューしてくださったサイト]
脇役ファイターズ - スマホゲームならアプリゲット
大トロ、飛車、そんな奴ら目じゃないぜ。今までの鬱憤、ここで晴らさん | 脇役ファイターズ-タワーディフェンスゲーム-攻略コミュニティ -アプリヴ大事典
【脇役ファイターズ】レビュー 「脇役だって活躍するんです」 | APP JUDGE(アップジャッジ)

CoronaSDKでのアプリ制作について

アプリのレビューは前述サイトさんに譲るとして、当ブログでは主にCoronaでの制作やディフェンスゲーム作りで苦労したことをテーマにしたいと思います。

技術面において最も苦労した点は、バトル中に処理が重くなるとショートフリーズ(バトル中に画面がカクッカクッてなるやつ)を引き起こすことです。

displayオブジェクトのnewの負荷を分散する

ショートフリーズの原因の一つは、displayオブジェクトのnewが一つのenterFrame内で同時に起こることです。

Runtime:addEventListener("enterFrame", refresh)

バトルが始まると、ランタイムリスナーが画面を更新し続けます。
フレームレートはデフォルトの30fpsです。

この時に、1回のenterFrame内に、displayオブジェクトがいくつもnewされてしまうと、ショートフリーズを引き起こします。
例えばユーザーがキャラを出撃させた時などがそうです。
fpsがもれなく低下します。
(あらかじめすべての画像を一気に読み込んでおくという手段は途中から気付いたわけであって、それが王道の作り方というのを知ったのも後の祭りでした…)

どうするか、作り直すか、と思案していたのですが、Luaにはcoroutineという機能があることを思い出しました。

Lua のコルーチンの使い方〜基本編〜 : torus solutions!

coroutine.wrapを使って、displayオブジェクト生成関数を分割することに成功しました。
これにより、例えば通常1回のenterFrameで12コオブジェクトを生成しなきゃいけない場合は、enterFrame1回目4コ、2回目4コ、3回目4コと負担を分散させることができました。

f:id:Pink_Flamingos:20150826164827p:plain

衝突判定の負荷を減らす

原因の二つ目は衝突リスナーです。
物理エンジンにおける衝突判定ですね。
使ったのは、Global Collision というやつで、特定のオブジェクトに付与するわけではなく、すべてのオブジェクトの衝突を拾う形式です。

Runtime:addEventListener("collision", onCollision)

味方キャラと敵キャラがぶつかった、敵の攻撃が当たった、というような場合です。
まずは、オブジェクトをカテゴリビットとマスクビットに振り分けて衝突リスナーの呼び出しを大幅削減しました。
カテゴリビットとマスクビットは、コイツとコイツは反応するけどコイツとコイツは反応しないみたいに衝突をフィルタリングする機能です。

Corona Docs — Guides | Physics

そのあと、1回の衝突リスナー呼び出しで膨大なif分岐を通る必要があったのを一掃し、テーブルリスナーによる呼び出しに変更。

こーなっていたのが、

local function onCollision(event)

	if(event.phase == "began") then

		local obj1_body = event.object1
		local obj2_body = event.object2

		if(obj1_type == "aaa" and obj2_type == "bbb") then
			--なにか処理
		elseif(obj1_type == "bbb" and obj2_type == "aaa") then
			--なにか処理
		elseif(obj1_type == "aaa" and obj2_type == "ccc") then
			--なにか処理
		elseif(obj1_type == "ccc" and obj2_type == "aaa") then
			--なにか処理
		elseif(obj1_type == "bbb" and obj2_type == "ccc") then
			--なにか処理
		elseif(obj1_type == "ccc" and obj2_type == "bbb") then
			--なにか処理

		elseif..以下永遠に続く

		end
	end
end

こう!(スッキリ!)

local function onCollision(event)

	if(event.phase == "began") then

		local obj1_body = event.object1
		local obj2_body = event.object2

		lsn[obj1_body.type][obj2_body.type](obj1_body, obj2_body)
	end
end

おかげでショートフリーズはなくなりました。
このenterFrameの負荷を減らすというチューニングは常時行っていました。

一緒に使用したツール

最後に、お世話になったツールのご紹介です。

TexturePacker
画像をパッキング。

ParticleDesigner
爆発とか雨のパーティクルを生成。

Devas
フォルダ内のテキストを検索/置換してくれるソフト。超重宝してる。

Corona Profiler
コードのパフォーマンス、ボトルネックのチェック。

DeployGate
複数台の実機ビルドなどに。便利すぎ。

最後に

一度遊んで頂けると幸いです!


大事なことなので2回言いました。
よろしくどうぞです。

android版
脇役ファイターズ-タワーディフェンスゲーム- - Google Play の Android アプリ

iPhone版



[環境]
・windows7 64bit
・Corona SDK ver 2015.2700

Composerとシーンエフェクトについての覚え書き -Corona SDK-

改めてComposerを使い始めているのですが、色々とハマったので忘れないように覚え書きです。

特に戸惑ったのが、画面遷移時のエフェクトの種類によっては、エフェクト中にシーン内のオブジェクトのタッチやタップ操作を受け付けてしまうことです。
"fromLeft"や"slideRight"などの移動系のエフェクトがそれに当たります。

storyboardはそれらを受け付けることはありませんでしたので、どうせ削除されるオブジェクト(シーンにinsertされてるから)のイベントリスナーをわざわざ削除や無効化する、というようなことはしていませんでした。
Composerはその辺の変更点を留意しておく必要があるようです。

どうするのがベストプラクティスなのかはまだ試行錯誤中ですが、うまくいった(と思われる)実装をメモっておきます。

続きを読む

ロングタッチやダブルタップを検出する -Corona SDK-

スマートフォンは画面が小さいので、一つの空間に複数のジェスチャーとそれに対する処理を含ませたいと思うことがあります。
色々なアプリを見てると素晴らしいUIがたくさんあって、それをいざCoronaでパク実装しようとすると、コトのほか難しくて難しくて…。
ガクゼンとします。

ロングタッチ

EventListener long touch and tap - General Questions/Discussion - Corona Labs Forums
[iOS][Xcode 4.2] シングルタップ、ダブルタップの区別とロングタップ(長押し) - b r o c k e n r e c o r d s

例えば、ロングタッチ。
オブジェクトに指を置いた時、普通のボタンタッチ処理以外に、1秒間置きっぱなしにすると特別な処理が呼ばれるとかそーゆー処理。
モンストで、キャラをロングタッチした場合にだけ詳細画面が出るような、アレです。

続きを読む

画像やスプライトを反転させる -Corona SDK-

左右対称や上下対称の画像を使用したい場合、いちいち反転させた画像を用意するのは容量的にも非合理的ですので、プログラム的に反転できないものかと調べていたのですが、xScale=-1(もしくはyScale=-1)で代用できるようです。
なるほどなるほど。

x方向なら、こんな感じで反転します。

local panda = display.newImageRect("img/panda.png", 100, 100)
panda.xScale = -1

f:id:Pink_Flamingos:20140610153434p:plain

続きを読む