ゲーム用のベースになるかもしれないアプレットをつくってみた
あしがかりか、ヒントにでもなればと思いまとめた。
よければ、どうぞ。↓
アプレット事始め(外部サイトからダウソロード)
zipには、読めテキストとjarファイルとhtmlファイルが入ってるので解凍していろいろさわってみるとよいかもしれない。
解凍場所は日本語パスが含まれない(あとスペースもかな?)場所で。
感じたことは、アプレットはパネルに相当するものだってことで、
他劣ることは何もない。
フレームの上に乗るし、キャンバスを乗せることもできる。
もちろんその他コンポーネントも乗る。
問題なのは
ブラウザ依存になってるjavaプラグインがネックなんだと思う。
あとはセキュリティホールがあるとかないとか。
あ、上のzipファイルのは悪さをするもんじゃないし、するつもりないし、そもそもできないし(^ω^)オッ
最後に、公開するためにはサーバが必要になってくる。
無料レンタルサーバで済ませる人のようのために、
アプレットで使えそうなレンタルサーバまとめてみた(ちょびっと)
※ちょっとした広告が入るのは仕方がないようだ
レンタルサーバのサービス名 |
実際のとこどうなのか |
|
|
FC2ホームページ |
データベースが使えない。けれど、FTP転送容量制限がなかったはず。アプレット単品公開ならぜんぜん使える |
忍者ホームページ |
単純なアプレットであれば、問題なし。ファイルサイズが3MBまでだった気がする。 |
@PAGES |
MySqlが使える。phpが使える。phpMyAdminが使える。てことは、ランキング機能が使える!?。が、しかし!FTP転送容量制限が1MBなのでjarファイルなどでかいのつくっちゃうと転送できないのでアウトー!!(jarファイルを細かく分けるとか、リソース直置きで実装するなどで回避することはもちろん可能だが・・) |
my-sv.net |
FTP転送容量に制限がない。MySqlが使える。phpが使える。phpMyAdminが使える。無料レンタルしてくれる。今のとこ完璧?! |
これら以外でも知ってていいとこあったらおすえてほしい。
以上。
集中線を描画するクラスをつくってみた
それっぽい集中線(1フレーム1本)を描画するようなクラスをつくってみた。
ConcentratedLineDraw.draw(g);
みたいな感じで呼び出す。
例えば、窓のサイズが500×300だったら以下の値を設定する。
centerX = 250;
centerY = 150;
lengthBase = 250;
lengthRatio = 0.6;
height = 10;
revisedDistanceRatioMax =0.6;
みたいな感じで設定すればちょうど中心に向かって集中線が描画される
以下、ソース。
public static class ConcentratedLineDraw {
private static AffineTransform at = new AffineTransform();
private static final AffineTransform AT_INIT = new AffineTransform();
private static int x[] = { 0, 0, 0 };
private static int y[] = { 0, 0, 0 };
public static int centerX; // 窓の中心座標
public static int centerY; // 窓の中心座標
public static int lengthBase = 200; // 窓幅の2分の1
public static double lengthRatio = 0.5; // ユーザ設定値
public static int height = 10;
public static double revisedDistanceRatioMax = 0.5; // 中心から範囲距離を窓の中心幅の割合で設定
public static Color colorLine = Color.BLACK;
public static void draw(Graphics2D g) {
double angdeg = Math.random() * 360; // 角度の決定
int revisedDistance = (int) (Math.random() * lengthBase * revisedDistanceRatioMax); // ばらつき距離の決定
int width = (int) (lengthBase * lengthRatio); // 線の長さ決定
int distance = lengthBase - width / 2 + revisedDistance; // 中心座標から距離を決定
int centerXLine = centerX + (int) (Math.cos(Math.toRadians(angdeg)) * distance); // 線の中心座標の決定
int centerYLine = centerY + (int) (Math.sin(Math.toRadians(angdeg)) * distance); // 線の中心座標の決定
at.setToRotation(Math.toRadians(angdeg), centerXLine, centerYLine); // 回転
g.setTransform(at);
g.setColor(colorLine); // 色の決定
x[0] = centerXLine - width / 2;
x[1] = centerXLine + width / 2;
x[2] = centerXLine + width / 2;
y[0] = centerYLine;
y[1] = centerYLine - height / 2;
y[2] = centerYLine + height / 2;
g.fillPolygon(x, y, x.length); // 三角形のポリゴン描画(集中線)
g.setTransform(AT_INIT); // 回転の初期化
}
}
スパルタクソXをつくってみて・・
まずは、お疲れ様でしたああああああああ!ヒャッホゥ(謎の開放感)。
そして、お礼を申し上げざるを得ない!
初めに、ハイカンさんの放送をみながら、あれこれどう実装しようか思案を巡らせたり、アイデアを考える際にかなり参考にさせていただきました(パクリつつ実装させていただきました)一時期は、蹴り上げて、殴るてきなのも実装してたぐらいです。オリジナリティに関しても、放送をみてなかったら普通の真面目なスパ●ルタンXになってしまうところでした(キャラぶれるところだった、あぶねー)。
続いて、ひろさんの放送から、ゲームフレームワークについてなど、実装のヒントをもらいつつ、ぱくりました(参考にさせていただきました)。
続いて、あやめさんの放送からも、画像の扱いやキー入力に関することなど参考にぱくらせていただきました。
続いて、はるさんの放送や動画から画像やゲームについてはいろいろと勉強させていただきました。
続いて、ゲーム中のBGMをあかうささんに提供していただきました(完全にもらいました)。これはすごいことでした。弦楽奏バージョンからインスピレーションをうけてできあがったのがゲームオーバ時のあの雰囲気なんですが、BGMがなかったらあぁはなってなかったでしょう。死亡時の効果音と合わせてゲームオーバー画面に移ったとき、自分のつくったゲームのデバッグではじめて吹いてもうたわけですよ。これは本当にびっくりしたことです。そっからが、なんとかおもしろいゲームになんねぇかと本気になった、今回の企画の自分の中でのターニングポイントでした。提供速度もはやくてまさにъ(゚Д゚)グッジョブ!! でした。
続いて、コミュニティ関係者、リスナーのみなさまにも感謝感謝。
あと、ニコニコモンズの素材提供です。効果音を全部そこですまさせていただきました。本来はコモンズツリーに登録すべきことだとは思うのですが、そこはすいません。あまりにずさんな管理で・・ほんとすんません!ありがとう!
&s{最後にほじほじさんのキャラに助けられたのは言うまでもない。}
ということで、関わっていただいた皆々様方を含めてーー全部まるっと含めていろいろもろもろありがとおおおおお!!!ござーした!多謝感謝感激あめあられ。
さてさて、ここからは、
ぐっらぐらな土台の上に成り立つスパルタクソX(ハイカンさん命名)ですが、どうやって実装したのかを文章にしたためておきます。ソース解析の際のちょっとしたお供メモという感じでみるとおもしろいかもしれませんが、読めたものではないかもしれません(記憶で書いたのでメソッド名が怪しいのなんの・・・)。
作成したゲームの流れを起動からループへ、そしてループでどうしてるかを記述してみる。
①アプレット
アプレット起動 |
init() |
バッファの用意とリペイント無視、リサイズ無効などのアプレット設定とゲームに使うデータの準備処理をしている |
※起動時に一度だけ呼ばれる。アプレットのサイズはHTMLタグで決まり、この時点でgetWidth()とgetHeight()などでサイズが取得できるのでここでバッファを用意している(今のとこ問題ない) |
アプレットスタート |
start() |
ゲームループ処理を行うスレッドの起動 |
※この時点でGameManagerがリソース(画像、反転画像、シーン、スプライト)を全て用意している状態となっている |
別スレッドを用意してゲームループ処理 |
run() |
シーンマネージャ※②に具体的な更新と描画を任せる |
※アプレットとゲーム用のスレッドが同時に走ることになる。プレイ中にタブ切り替えや別の窓に切り替えた場合に、ゲーム用のスレッドが止まるような仕組みをつくったときの名残でそうなった |
アプレットストップ |
stop() |
スレッドをnull参照させてゲームループを抜ける |
|
アプレット破棄 |
destroy() |
特になにもしない |
|
②シーンマネージャ
今回の場合だと、ロゴ画面、タイトル画面、スタート画面、ゲームプレイ画面、クリア画面、おまけ画面などを用意した。
シーン変化時の初期化が行えるように初期化メソッドと、毎フレーム呼び出される更新メソッドをインターフェースとしたシーンクラスを用意し、各シーンがそれを継承し、そのインスタンスをマネージャに事前登録しておく。あとは継承したクラス名をキーとしてチェンジメソッドを呼ぶことでシーン遷移が可能となる。
(シーンの用意) |
インスタンス化 |
そのシーンに必要な画像などの参照を確保するのがメイン処理となる |
※アプレット起動時に行っている |
シーン遷移 |
changeScene() |
最初はnullなので描画されない。このメソッドをトリガーにして描画が始まる。必ず、ループの初めにシーンが遷移後、初期化処理を行うので、どこで呼び出しても問題ない。間違って連続で呼んだとしても、上書きされるだけである |
|
シーン初期化 |
initScene() |
遷移する前のシーンを参照できるようにしてみたので、それらをインプットとするなどして、シーンの初期化をする。データの受け渡しができないようになっているので、public staticなデータを参照していくしかない(今回はGameDataクラスがゲーム全体のデータを保持することに収束) |
|
シーン更新 |
updateScene() |
肝な部分。用意した画像を用意しておいてひたすら描画したり、サブシーンを定義して更新処理を振り分けていく。今回、ゲーム中に描画するものに関してはSpriteManager※③が一元管理しているので、ゲームプレイ画面ではほとんど委譲してしまっている。他のシーンに関しては、ちまちまと座標を手打ちして、カウンターを用いるなどして手作業の微調整で構成している |
|
③SpriteManager
描画可能なもの(Drawableインターフェースを継承したクラス)を登録しておくと、有効設定したものだけが、毎フレーム更新され描画される。ただ、今回は自キャラ、敵キャラともに、有効無効の状態だけではあまりにも厳しい実装になってしまいそうだったので、更新方法については、※④StateDrawManagerに任せてしまっている。
更新 |
update() |
登録した描画可能なものを要素番号の若い順から更新していく。 |
|
描画 |
draw() |
要素番号が若いほど奥に描画される |
|
本来はこの二つに終始するべきだったのだが、当たり判定処理の関係と、単純に自分が分かりやすいという理由で詳細描画に関する処理も全て任せている。ゲームプレイ画面の要となっている。※本来ならば、シーンクラスに任せるべきだったかと思っている
interface Drawableについて
初期化 |
onInit() |
|
|
更新 |
onEnterFrame() |
|
アクションスクリプトのパクリの名残が(^ω^) |
描画 |
onDraw() |
|
|
今思うのが、onDrawだけにして、スプライトのinterfaceとして初期化と更新を定義したほうがよかったorz
SpriteMangerがやってることの実際は以下のようになる。
スプライトの状態に変化 |
onInit() |
状態変化時に初期化したい処理を行う。主に、座標の設定、HPの初期化 |
※1ステージ毎にすべき処理はシーンの初期化にておこなっている(例えば、出現する敵やボスの決定)また、詳細な処理はStateDrawManagerに委譲 |
スプライトのアップデート |
onEnterFrame() |
毎フレーム行う処理。その状態に収まるべき処理をして、他との状態の兼ね合いをみながら、座標を更新したり、回転角度を更新したり、描画するイメージを更新したり、他の状態への移行メソッドを呼び出したりする。敵のアルゴリズムが決まる |
詳細な処理はStateDrawManagerに委譲 |
あたり判定 |
checkCollision() |
別のクラスが当たり判定を監視 |
※⑤ |
スプライトの描画 |
onDraw() |
更新処理を記述できるが混乱するのでできるだけ避けて実装した。ほとんどg.drawImage() |
詳細な処理はStateDrawManagerに委譲 |
④StateDrawManager
仕組みはシーンマネージャと同じ。Drawableインターフェースを実装し、更新内容を決めておく。それらを事前に登録しておいて、クラス名をキーにしてchangeState()で状態を移行する。基本はREADY(待機状態、描画はしない、主にデータ初期化に使う)、ALIVE(通常状態の動きの処理と描画)、DEATH(死亡時のアクション後、READYに移行)で構成し、あとは例えば、雑魚敵だとHUG(抱きつき状態)を追加してみたり、ウルトラマンじゃヴぁだとSLUGGER(頭のやつをぶん投げる)を追加しておいて、ごまかしごまかし状態を切り替えている。
⑤Collision
衝突判定ができるようになる。
中心座標とサイズを持ち、有効無効を切り替えて、判定するかどうかを決定。
矩形の当たり判定を行う。
何と衝突するかはgroupIndexとtargetIndexをビットでみている。
例えば
Aの物体 groupIndex:0001 targetIndex:0010
Bの物体 groupIndex:0010 targetIndex:0000
としたものを登録すると、
AとBが衝突(重なる)した際に、AはBをターゲットとしているので、Bで実装したnotifyCollide(ターゲットの物体)が呼ばれる。
一方、Bは衝突相手を設定していないので、衝突判定はTRUEとなるが処理は発生しない。
具体的に落とし込むと、トーマスの攻撃のあたり判定がAだとすると、Bは敵のボディーとなる。トーマスの攻撃判定が有効の場合で、敵のボディーと重なっていた場合は、敵のボディーで設定したnotifyCollide()が呼ばれる。もし、HPが0となれば、死亡状態へ移行・・・ということになる。
ターゲットはつまり、監視される相手となる。ややこい。この辺は実装ミスってると思ってる。
監視される相手は、スプライトインスタンス化時、(コリジョン追加時)、つまるところシーンのインスタンス化時に決めうちしている。なので、ターゲットを設定しなければ、そもそも当たり判定処理は行わない。一番最初に使うかどうかもわからない(プレイヤーの腕次第によっては使わないスプライトも出てくる)スプライトを全て初期化しているのであたまでっかちになっているのがちょい残念。とはいっても、そこまでリソースを食わないので問題ないと思ってる。毎フレームあたり判定する相手を探すアルゴリズムが実装できなかっただけなんですけどね。
⑥StateThomas
プレイヤーの入力をトーマスに反映する、トーマスの挙動を再現するためだけのクラス。めちゃめちゃ苦しんだクラスで、ちょっと触ると壊れる。実際今も壊れてる(↓キー入れた状態で左右キー同時押しで荒ぶる、ある状態においてダメージを受けると攻撃状態で硬直してしまう、など)。
列挙型クラスとしてRoughStateとDetailStateを持つ。大雑把な状態として、DAMAGE(被ダメージ状態)、COLLIDE(抱きつき状態のつもり)、NORMAL(それら以外)を定義し、詳細な状態として、ジャンプ中の○○、しゃがみ中の○○、立ちでの○○という感じで、事細かに定義。
前回の状態とキー入力をインプットとして、次の状態と座標更新やジャンプカウンターの更新をアウトプットする。
各状態にはupdate()メソッドを定義するように義務づけている。毎フレーム状態によるupdate()処理をして、入力しだいでは状態を切り替えたり、また、外部からの入力により、ラフ状態を切り替えている。外部要因で、ダメージなのか抱きつき硬直かに強制的に変化させる必要があるものがラフ状態で定義されている。詳細状態は「動ける状態における詳細」ということになり、ラフ状態が「抱きつき」でもジャンプ以外の動ける状態になるっていうややこいことを実装するためにこんな感じになった。行き当たりばったり感が満載。一番振り返りたくない、みたくないと思えるクラスに仕上がった。
この更新はゲームループ全体から見たときに、以下のタイミングで行われておる。
(スプライト状態変化があった場合)スプライト初期化→スプライト更新(衝突するものとしては一番最初に登録されているトーマスクラス(Playerクラス)において、ALIVEの中でStateThomasクラスの更新をかける、次に順々に敵のスプライトクラスを更新)→当たり判定処理→スプライト描画→(最初に戻る)
初期化を必ず頭にもってきて、わかりやすいようにし、入力によるゲームへのアプローチを最初にもっていくことで、割と安定した動作をするようになった(最初はいろいろ前後左右してぐちゃぐちゃして描画がうまくいかなかった)。入力に関しての処理が1フレームの処理の頭にくるべきだと思うが、トーマス関連の処理をPlayerクラスに収めたいという理由でこんな感じになってしまったが、今思うと頭に持ってきたほうが分かりやすかったのかもしれないと思ってきている次第である。そしてこの顔である(^ω^)。
後書きてきな
プレイヤーの情報はどの敵も欲しがったので、最初はスプライト全体からプレイヤークラスのオブジェクトを探すようにしていたのを、public staticに参照できるようにした。すっきりした分どっからでもいじれるようになったので、自分ルールで気をつけた。
音楽や効果音を使うことは今回の参加時における目標だったので、3月終わりまでほぼほぼ放置してたのをなんとか重い腰を上げて実装した。いまだにエフェクト再生をストップできるように実装していないのが残念なところだが、わりかし上手くいったほうだともう。遅延がどうしてもでてしまうのところも残念なところか。ただ、音楽と動く絵、動かせる絵が出来上がってくるとゲームっぽくなってきてモチベーションあがったので、次回もしこういう機会があるならばソースコードとにらめっこする前にして全て準備してしまうのもよいかなと思った。ということで絵の練習を始める。目標はみさきめいちゃんのSDキャラ、ということで。
コメントありがとうきびうんこ!
最終更新:2012年04月18日 21:54