Tips
- グラフィック
- フォント・テキスト
- 入力インターフェース
- ゲームオブジェクト
- 物体を動かしたい
- 移動速度の制限を設定したい
- 中心座標を取得したい
- キャラクターが動かない
- 画面外に出たら消すようにしたい
- 画面外に出ないようにしたい
- ゲームオブジェクトをまとめて管理したい
- 特定のオブジェクトグループをまとめて操作したい
- 特定の型のオブジェクトだけ別の処理をしたい
- オブジェクトグループ (FlxTypedGroupなど) を入れ子に(階層化)したい
- kill() と destroy() の違いは何?
- インスタンス数を制限してオブジェクト管理をしたい
- kill()してもキャラクターが非表示にならない
- 2つのオブジェクトの距離を取得したい
- 2つのオブジェクトがなす角度を取得したい
- 更新を止めたい(動きを止めたい)
- 時間
- 衝突
- パーティクル
- 乱数
- State制御
- カメラ
- セーブ
- ファイル制御
- 通信
- サウンド再生
- デバッグ機能
- その他
- 環境
グラフィック
矩形を描画したい
`FlxSprite.makeGraphic()`で、FlxSpriteに描画して add() します。引数の詳細はFlxSprite.loadGraphic()を参照
/// PlayState.hx override public function create():Void { // 矩形のFlxSpriteを生成する var spr = new FlxSprite(180, 230); // (x,y)=(180,230)に配置 spr.makeGraphic(40, 6, FlxColor.HOT_PINK); // 40x6の矩形をピンク色で生成 add(spr); // 登録
直接描画を行うにはFlxSpriteUtilを使用する方法もあります。
画像を表示したい
`FlxSprite.loadGraphic()`で、FlxSpriteに画像を読み込ませます。引数の詳細はFlxSprite.loadGraphic()参照
画像を左右反転で描画したい
`FlxSprite.flipX`に`true`を設定すると左右反転で描画されます。また`FlxSprite.flipY`は上下反転に使えます。
画像を回転して描画したい
`FlxSprite.angle`に角度を設定すると、指定の角度に画像が回転して描画されます
画像を点滅したい
FlxBasic.visible に true / false を交互に設定することで点滅することが可能ですが、FlxSpriteUtil.flicker() を使用すると簡単に実装できます。
var spr:FlxSprite; // スプライト var duration:Float = 1; // 1秒間点滅する var interval:Float = 0.04; // 0.04秒ごとに点滅する // 点滅開始 FlxSpriteUtil.flicker(spr, duraction, interval);
点滅中かどうかを判定して特別な処理を行う場合、FlxSpriteUtil.isFlickering() を使うことできます。
if(FlxSpriteUtil.isFlickering(spr)) { // 点滅中の処理 }
アニメーションを再生したい
以下の手順で再生ができるようになります。アニメーションの制御についてはFlxAnimationを参照します。
- `FlxSprite.loadGraphic()`の第2引数に`true`を設定
- `FlxSprite.animation.add()`でアニメパターン登録
- `FlxSprite.animation.play()`でアニメを再生
サンプルコード
class Alian extens FlxSprite { override public function create():Void { super.create(); // アニメーションとして画像を読み込む loadGraphic("assets/alien.png", true); // アニメーションを登録 0 -> 1 -> 0 -> 2 の順で再生 animation.add("Default", [0, 1, 0, 2], Math.floor(6 + FlxRandom.float() * 4)); // アニメーションを再生 animation.play("Default");
アニメーションの終了時に消滅させるようにしたい
FlxAnimation.finished プロパティで終了判定ができます。
override public function update(elapsed:Float):Void { super.update(elapsed); if(animation.finished) { // アニメーションが終わったので消す kill(); } }
ただし、アニメーションのループ再生を無効にする必要があります。FlxAnimation.addの第4引数がループ再生フラグとなります。
override public function create():Void { super.create(); loadGraphic("assets/alien.png", true); // ループ再生無効 var bLoop = false; animation.add("Default", [0, 1, 0, 2], 10, bLoop); animation.play("Default");
アニメーション画像を回転して描画したい
アニメーション画像を再生すると、FlxSprite.angleに角度を指定してもそのままでは回転できません。 有効にするには、FlxAnimationController.createPrerotatedを事前に呼び出しておきます
override public function create():Void { super.create(); // これを呼び出すと回転できる animation.createPrerotated(); loadGraphic("assets/alien.png", true); animation.add("Default", [0, 1, 0, 2], 10, true);
Tweenアニメを使用したい
FlxTweenを使用します。以下は、画面外から1秒かけて減速しながら入ってきて、加速しながら1秒かけて出て行くサンプルです。
_text = new FlxText(-200, FlxG.height/2-12, FlxG.width, 24); _text.alignment = "center"; _text.text = "test"; var cbEnd = function(tween:FlxTween):Void { // 加速しながら出て行く FlxTween.tween(_text, {x:FlxG.width}, 1, { ease:FlxEase.expoIn}); } // 画面中央に減速しながら入る FlxTween.tween(_text, {x:0}, 1, { ease: FlxEase.expoOut, complete:cbEnd});
Tweenアニメで拡大縮小したい
FlxSpriteが持つ scale プロパティを渡すことで実現可能です。
var spr = new FlxSprite(0, 0); var spr.loadGraphic("logo"); // 1秒かけて2倍に拡大する FlxTween.tween(spr.scale, {x:2, y:2}, 1, {ease:FlxEase.expoOut});
背景を無限スクロールさせたい
拡張モジュールの「FlxBackdrop」を使用すると、カメラの移動に合わせて背景を無限スクロールさせることができます。
/// PlayState.hx private var _back:FlxBackdrop; // スクロールする背景 override function create():Void { // 無限スクロールする背景の設定 _back = new FlxBackdrop("assets/images/back.png", 0.1, 0, true, true); this.add(_back); }
オフスクリーン・レンダリングをしたい
FlxSprite.makeGraphic でビットマップを作成して、bitmapCache.bitmap を、FlxSprite.pixels.copyPixels で転送します。
var spr = new FlxSprite(); // 転送する画像読み込み var bmp = FlxG.bitmap.add("assets/images/wall1.png"); // 透明なスプライトを640x480で作成 spr.makeGraphic(640, 480, FlxColor.TRANSPARENT); // 転送先の座標 var pt = new Point(64, 96); // 転送領域の作成 var rect = new Rectangle(0, 0, 32, 32); // (X, Y)=(64, 96)の座標に、(OX, OY, OW, OH)=(0, 0, 32, 32)の領域を転送する spr.pixels.copyPixels(bmp.bitmap, rect, pt); // 描画を反映 spr.dirty = true; spr.updateFramePixels();
なお、BitmapData.copyPixels はデフォルトでは「透過なし」の転送を行います。透過色を含むBitmapDataを転送する場合は、引数に「true」を追加してアルファブレンドを有効にします。
// アルファブレンドを有効にした転送 spr.pixels.copyPixels(bmp.bitmap, rect, pt, true);
実行中にフルスクリーンに切り替えたい
FlxG.stage.displayState の値を、flash.display.StageDisplayState?.FULL_SCREEN にするとフルスクリーンになります。
// フルスクリーンにする FlxG.stage.displayState = flash.display.StageDisplayState.FULL_SCREEN;
フルスクリーンでない状態に戻す場合には、flash.display.StageDisplayState?.NORMAL を指定します。
// 通常の状態に戻す FlxG.stage.displayState = flash.display.StageDisplayState.NORMAL;
以下、フルスクリーンに切り替えるボタンの実装例です。
// フルスクリーン切り替えボタン var btnFullScreen:FlxButton = null; btnFullScreen = new FlxButton(32, 32, "FULL SCREEN", function() { if(FlxG.stage.displayState == StageDisplayState.NORMAL) { // フルスクリーン FlxG.stage.displayState = StageDisplayState.FULL_SCREEN; btnFullScreen.text = "NORMAL"; } else { // 通常に戻す FlxG.stage.displayState = StageDisplayState.NORMAL; btnFullScreen.text = "FULL SCREEN"; } });
Flash環境の注意点
なおFlash環境においては、HTML側で「allowFullScreen?」の値をtrueに設定しておく必要があります。
<param name="allowFullScreen" value="true"/> <!-- この設定が必要 --> <param name=movie value="sample.swf"> <param name=loop value=true> <param name=quality value=high> <param name=bgcolor value=#aaffaa>
さらにフルスクリーンにすると、キーボードの入力がとれなくなる(マウス入力・ESCキーでフルスクリーン解除)ようです。
フォント・テキスト
テキストを表示したい
FlxTextを使用します。
/// PlayState.hx override function create():Void { var myText = new FlxText(0, 0, 500); // x, y, width myText.text = "Hello World"; // テキスト設定 myText.setFormat("assets/font.ttf", 20, FlxColor.WHITE, "center"); // フォントとサイズ、色、alignを指定 myText.setBorderStyle(FlxTextBorderStyle.OUTLINE, FlxColor.RED, 1); // 境界の描画設定 add(myText);
デフォルトフォントで充分であれば setFormat() の第一引数はnullを指定します
フォントにアウトラインを設定したい
フォントに縁取りをつけるには、FlxText.setBoarderStyle で、FlxTextBorderStyle.OUTLINE / FlxTextBorderStyle.OUTLINE_FAST を指定します。
var text = new FlxText(0, 60, FlxG.width, "Hello HaxeFlixel Community!"); // 2pxの白い縁取りをつけます text.setBorderStyle(FlxTextBorderStyle.OUTLINE, FlxColor.WHITE, 2);
日本語を表示したい
デフォルトのフォントはAscii文字のみなので、日本語フォントを別に用意します。 ここでは、フリーフォントである「たれフォント」を使用します。
ここから入手したフォントを、例えば "assets/MT_TARE.ttf" に配置したとします。
var tx:FlxText = new FlxText(0, 0, FlxG.width, 24); tx.setFormat("assets/MT_TARE.ttf", 24); tx.text = "たれフォント"; add(tx);
ただし、Neko環境では文字化けして正常に表示されないようです。(画面はFlash環境で確認しました)
現在はNeko環境でも日本語が正しく表示されるようになりました。(2015/4/16)
日本語の文字数を取得したい
HaxeのString.lengthは日本語のときに正しい値を返しません。そのため日本語の文字数に合わせてウィンドウの幅を変えたり、文字の切り出しをすることが難しいです。これにより FlxText.alignment も正常に機能しません。 試していないですが、以下のライブラリを導入することで解決できるかもしれません。
BMPフォントを使用したい
BMPフォントを使用するには、PxBitmapFont / FlxBitmapTextFieldを使用します。
// フォント画像と定義文字列のロード var font:PxBitmapFont = new PxBitmapFont().loadPixelizer(Assets.getBitmapData("assets/fontData10pt.png"), " !\"#$%&'()*+,-./" + "0123456789:;<=>?" + "@ABCDEFGHIJKLMNO" + "PQRSTUVWXYZ[]^_" + "abcdefghijklmno" + "pqrstuvwxyz{|}~\\"); // 描画オブジェクトにフォントを登録 tf = new FlxBitmapTextField(font); tf.text = "Hello World!\nand this is\nmultiline!!!"; tf.color = 0xffffff; tf.fixedWidth = false; tf.multiLine = true; tf.alignment = PxTextAlign.LEFT; tf.lineSpacing = 5; tf.padding = 5; add(tf);
AngelCodeでフォントレイアウトを作成する場合には以下のように記述します。
override public function create():Void { super.create(); var fnt = "assets/font.fnt"; // AngelCode var png = "assets/font_0.png"; // フォント画像 var text = "フォントてすと"; // レイアウトデータ取得 var textBytes = Assets.getText(fnt); // XMLをパース var XMLData = Xml.parse(textBytes); // フォント画像とレイアウトデータを読み込み var font2:PxBitmapFont = new PxBitmapFont().loadAngelCode( Assets.getBitmapData(png), XMLData); // 描画オブジェクト生成 tf2 = new FlxBitmapTextField(font2); tf2.x = 0; tf2.y = 0; tf2.useTextColor = false; tf2.width = 400; tf2.outlineColor = 0xff0000; tf2.color = 0xFFFFFFFF; tf2.text = text; add(tf2); }
入力インターフェース
キーが押された瞬間を判定したい
`FlxG.keys.justPressed`を使用します。キー定数についてはFlxKeyの定数一覧を参照
/// PlayState.hx override public function update():Void { super.update(); // キー入力 if(FlxG.keys.justPressed.SPACE) { // スペースキーが押された }
また押しているかどうかは`pressed`、離した瞬間は`justReleased`を使用します。複数のキーを指定する場合は`any###()`に文字列の配列を渡してチェックします。
マルチタッチを判定したい
`FlxG.touchs`からタッチ情報を取り出して判定をします。タッチ情報の詳細はFlxTouchに記載しています。
/// PlayState.hx override public function update():Void { super.update(); // マルチタッチを判定。 // デスクトップ環境では無効にできるようシンボル「FLX_NO_TOUCH」で`#if`する #if !FLX_NO_TOUCH for (touch in FlxG.touches.list) { if (touch.pressed) { // タッチしている var px = touch.x; // タッチ座標(X)を取得 var py = touch.y; // タッチ座標(Y)を取得 } } for (swipe in FlxG.swipes) { var distance = swipe.distance; // スワイプ距離の取得 var angle = swipe.angle; // スワイプ角度の取得 } #end
取得可能なスワイプ情報はFlxSwipeで確認できます。 なおシンボルの定義方法はProject.xmlの書き方に記載しています。
スワイプ情報を取得したい
スワイプ情報(FlxSwipe)はFlxG.swipesにあります。マウスとタッチデバイスのみ有効な情報で、クリックまたはタッチが離された瞬間のみ、情報が格納されます。
for(swipe in FlxG.swipes) { var distance = swipe.distance; // スワイプ距離の取得 var angle = swipe.angle; // スワイプ角度の取得 var start = swipe.startPosition; // スワイプ開始座標の取得 var end = swipe.endPosition; // スワイプ終了座標の取得 }
加速度センサーの値を取得したい
HaxeFlixelに加速度センサーの値取得方法がないので、OpenFLの機能を使います。 加速度センサーの値を取得するには、Accelerometerクラスを使用します。
// 加速度センサー private var _accelerometer:Accelerometer; private var _accelerometerX:Float = 0; private var _accelerometerY:Float = 0; private var _accelerometerZ:Float = 0; /** * 生成 **/ public function new() { super(0, 0); if(Accelerometer.isSupported) { // 加速度センサー有効 _accelerometer = new Accelerometer(); // 更新の頻度(msec)。値を大きくすると更新頻度が高くなります _accelerometer.setRequestedUpdateInterval(50); _accelerometer.addEventListener(AccelerometerEvent.UPDATE, onUpdateAccelerometer); } } /** * 加速度センサー更新 **/ public function onUpdateAccelerometer(e:AccelerometerEvent):Void { _accelerometerX += e.accelerationX; // G(9.8m/秒/秒)の X 軸の加速度値 _accelerometerY += e.accelerationY; // G(9.8m/秒/秒)の Y 軸の加速度値 _accelerometerZ += e.accelerationZ; // G(9.8m/秒/秒)の Z 軸の加速度値 }
注意点は以下のとおりです。
- Accelerometer.isSupportedがtrueであれば加速度センサーが有効な端末です
- 「縦向き」のゲームであれば、X軸は横、Y軸は縦、Z軸が奥行きとなります
- 横向き(orientation="landscape")の場合はXY軸が逆になります
- 値は「-1.0〜1.0」の範囲となります
- 例えば「縦向き」のゲームの場合、Y軸は正面に垂直に傾けると「1.0」、奥に裏返すと「-1.0」となります
- 値は現在の傾きに対する値となります
- 「加速」させたいのであればこの値を速度(velocity)足し込む必要があります。
最後について少しややこしいので説明すると、加速度として扱うのであれば、以下のように値を足し込みます。
/** * 加速度センサー更新 **/ public function onUpdateAccelerometer(e:AccelerometerEvent):Void { velocity.x += e.accelerationX * 10; // X軸の速度を加速させる velocity.y += e.accelerationY * 10; // Y軸の速度を加速させる }
なお前回からの変動値を取得するのであれば、前回の値を保持し差分を求めます
private var _oldX:Float = 0; private var _oldY:Float = 0; /** * 加速度センサー更新 **/ public function onUpdateAccelerometer(e:AccelerometerEvent):Void { var nowX = e.accelerationX * 10; var nowY = e.accelerationY * 10; var dx = nowX - _oldX; // 今回の傾きの変化(X軸) var dy = nowY - _oldY; // 今回の傾きの変化(Y軸) // 次回計算用に今回の値を保持 _oldX = nowX; _oldY = nowY; }
ボタン
ボタンを実装したい
FlxButtonを使用します。
/// PlayState.hx override public function create():Void { // ボタンを生成 var myButton = new FlxButton(0, 0, "Label", myCallback); // 画像を設定 myButton.loadGraphics("assets/custom.png"); add(myButton); } // ボタンを押した時のコールバック関数 private function myCallback():Void { // ここに処理を記述する }
ボタンの色を変えたい
FlxButton.color に色を指定するとボタンの色が変わります。また、FlxButton.label.color を変更するとボタンテキストの色が変わります。
// 青系のボタンに変更 var button:FlxButton; button.color = FlxColor.NAVY_BLUE; button.label.color = FlxColor.AZURE;
ボタンの上にマウスカーソルが乗っているかどうかを調べたい
FlxTypedButton.status を参照して判定します。
private var _btn:FlxButton; override public function update():Void { if(_btn.status == FlxButton.HIGHLIGHT) { // マウスがボタンの上に乗っている } }
ボタンのサイズを変えたい
FlxButtonのページで、リサイズする方法を紹介しています。 もしくは、拡張モジュールにあるFlxButtonPlusを使用します。FlxButtonPlusの実装方法としては、コンストラクタでボタンの幅と高さを渡し、フォントのサイズを変更します。
class MyButton extends FlxButtonPlus { public function new(X:Float = 0, Y:Float = 0, ?Text:String, ?OnClick:Void->Void) { var w = 200; // ボタンの幅 var h = 40; // ボタンの高さ var s = 20; // フォントのサイズ super(X, Y, OnClick, Text, w, h); textNormal.size = s; textHighlight.size = s; } }
なおFlxButtonPlusは通常のボタンよりもリッチな見栄えとなっています。
ボタンを画像にしたい
FlxButton.loadGraphicで、画像ボタンにできます。
- 0番目: 通常
- 1番目: マウスがボタンの上に乗った状態
- 2番目: クリックした状態
ボタンにクリック音を設定したい
FlxButtonの onUp / onDown / onOver / onOut が持つ sound に FlxSoundを設定すると、音が再生されます。
var btn:FlxButton; // ボタンをクリックしたらSE "click" を再生する btn.onDown = FlxG.sound.load("click");
ツイートボタンを作りたい
Twitterのツイートボタンの作り方を参照してください。
仮想ゲームパッドを使いたい
以下のページに基本的な使い方をまとめています
ゲームオブジェクト
物体を動かしたい
`FlxObject.velocity`(FlxSpriteのスーパクラスのフィールド)が速度なので、これに値を入れると動き出します。単位は1秒間に移動するピクセル数となります。また加速度は`acceleration`です。 その他のパラメーターについてはFlxObjectに記載しています。
移動速度の制限を設定したい
コリジョン抜けを避けるために、移動速度の制限をかけたいときがあると思います。そのときは`FlxObject.maxVelocity`により速度制限をかけることができます。
中心座標を取得したい
オブジェクトの中心座標は origin(原点)となります。回転や拡大縮小はこの座標を基準に行われます。そのため中心座標を取得するには以下のように記述します。
// 中心座標(X)を取得する public function getCenterX():Float { return x + origin.x; } // 中心座標(Y)を取得する public function getCenterY():Float { return y + origin.y; }
(x, y)は左上座標なので、このような方法となります。
キャラクターが動かない
よくあるのが super.update()の呼び忘れです。これを呼び忘れると移動量の更新が行われません
class Player extends FlxSprite { // 更新 override public function update(elapsed:Float):Void { super.update(elapsed); // これを呼び忘れると動きません }
画面外に出たら消すようにしたい
FlxSprite.isOnScreen?() で判定できます。
override public function update(elapsed:Float):Void { super.update(elapsed); if(isOnScreen()==false) { // 画面外で消える kill(); } }
画面外に出ないようにしたい
FlxSpriteUtil.bound() を使うと画面内に収まるように座標を調整します
class Player extends FlxSprite { override public update(elapsed:Float):Void { // …… 何らかの更新処理 // 画面外に出ないようにする FlxSpriteUtil.bound(this, 0, 0, FlxG.width, FlxG.height); } }
ゲームオブジェクトをまとめて管理したい
FlxTypedGroupを使うと、FlxObjectの派生クラス(FlxSpriteやFlxTextを含む)をまとめて管理できます。
特定のオブジェクトグループをまとめて操作したい
membersをfor文で回してもいいですが、FlxTypedGroup.forEach*() 関数を使用すると簡単な記述ができます。以下、生存しているオブジェクトから特定のIDを持つ数をカウントする関数の実装例です。
/** * 指定のIDの敵の生存数を返します * @param 敵ID(Enemy.ID_*) * @return 存在する敵の数 **/ public function getEnemyCount(eid:Int):Int { var ret:Int = 0; // チェック関数 var check = function(e:Enemy) { if(e.id == eid) { // 一致した ret++; } } // 生存しているオブジェクトに対して実行 _enemys.forEachAlive(check); return ret; }
特定の型のオブジェクトだけ別の処理をしたい
FlxTypedGroup.forEachOfType?() を使います。例えば、HUDをFlxSpriteGroupで作成すると、membersにFlxSpriteやFlxTextが混在しますが、この関数を使うことで、FlxTextクラスだけ、テキストの形式をまとめて設定することができます
/** * Head-up Display. **/ class HUD extends FlxSpriteGroup { // ... public function new() { // ... // FlxTextやFlxSpriteの生成などを行う // ... // FlxTextだけフォントやアウトラインの設定を行う forEachOfType(FlxText, function(member:FlxText) { member.setFormat(AssetPaths.pixel_font__ttf, 8, FlxColor.WHITE, FlxTextBorderStyle.OUTLINE, 0xFF005784); }); }
オブジェクトグループ (FlxTypedGroupなど) を入れ子に(階層化)したい
FlxGroupを使うと、オブジェクトグループを入れ子にできます。
class PlayState extends FlxState { var player:Player; // プレイヤー var enemies:FlxTypedGroup<Enemy>; // 敵管理 var items:FlxTypedGroup<Item>: // アイテム管理 var bullets:FlxTypedGroup<Bullet>; // 敵弾 var entities:FlxGroup; // オブジェクト管理をさらに管理する override public function create():Void { // ... // 各種インスタンス生成 // ... // エンティティに登録 entities.add(enemies); entities.add(items); entities.add(bullets); // これを忘れない this.add(entities); } override public function update(elapsed:Float):Void { FlxG.overlap(entities, player, _collideEntities); } /** * 衝突処理 */ function collideEntities(entity:FlxSprite, player:Player):Void { if(Std.is(entity, Enemy) { // 敵との衝突処理 var enemy:Enemy = cast entity; // ... } else if(Std.is(entity, Item) { // アイテムとの衝突処理 var item:Item = cast entity; // ... } else(Std.is(entity, Bullet) { // 敵弾との衝突処理 var bullet:Bullet = cast entity; // ... } else { throw "Error: 衝突処理が未実装です" } } }
FlxG.overrap() を複数並べるのではなく、1つの関数にまとめることができました。
他の例としては、FlxGroupを使うと、FlxTilemapで読み込んだ地形に、別の地形を後から追加することができます。
var _terrain:FlxGroup; // 地形グループ var _map:FlxTilemap; // タイルマップ var _blocks:FlxTypedGroup<Block>; // ブロック override public function create():Void { // ... _terrain.add(_map); // 地形グループに登録 _terrain.add(_blocks); // 地形グループに登録 this.add(_terrain); } override public function update():Void { // 1回の FlxG.collideで衝突判定ができる FlxG.collide(_terrain, player); }
kill() と destroy() の違いは何?
`kill()`は一時的な消滅で、`revive()`または`reset()`により復活することできます。しかし`destroy()`は完全な消滅で復活させることができません。
インスタンス数を制限してオブジェクト管理をしたい
FlxTypedGroupを使用して、あらかじめインスタンス数を決定します。
// 敵弾グループ private var _bullets:FlxTypedGroup<Bullet>; /** * 生成 */ override public function create():Void { // インスタンス数を256個に制限 _bullets = new FlxTypedGroup<Bullet>(256); for(i in 0..._bullets.maxSize) { // 固定プールする var b = new Bullet(); // 消しておく b.kill(); // 追加する _bullets.add(b); } }
そして利用するときにはFlxTypedGroup.getFirstDead?()を使用します。
// 未使用のインスタンスを取得 var b = _bullets.getFirstDead(); if(b != null) { // 再利用する b.revive(); // 各種初期化 b.x = FlxG.random.float(0, FlxG.width); b.y = FlxG.random.float(0, FlxG.height); }
kill()してもキャラクターが非表示にならない
そのキャラクターに付随する何らかの描画が残っている可能性があります。よくあるのがFlxTrailによるトレイル・エフェクトの消し忘れです。
2つのオブジェクトの距離を取得したい
FlxMath.distanceBetween() を使用すると2つのオブジェクトの距離を取得できます
// obj1とobj2との距離を取得 var distance = FlxMath.distanceBetween(obj1, obj2);
2つのオブジェクトがなす角度を取得したい
FlxAngle.angleBetween() を使用すると2つのオブジェクトがなす角度を取得できます
// プレイヤーからアイテムへの角度を取得する (3番目の引数がtrueなら角度。そうでなければラジアン) var degrees = FlxAngle.angleBetween(player, item, true);
更新を止めたい(動きを止めたい)
例えば、ヒットストップやポーズボタン(一時停止)など、オブジェクトの動きを止めるには、FlxBasic.activeフラグをfalseにします。
class Enemy extends FlxSprite { …… // 何かの処理 public function doSomething():Void { // これで更新(update)が呼ばれなくなる active = false; } }
アニメーションは有効にして、移動だけ止める場合は FlxObject.moves を false にします
時間
起動してからの経過時間を取得したい
flash.Lib.getTimer()を使用します。この関数はミリ秒を整数値で返します。なお、Sys.time()はFlash環境などでは使用できないので注意が必要です。
// 単位はミリ秒なので1000分の1にすることで、秒数に変換することが可能です var sec:Float = flash.Lib.getTimer() * 0.001;
前フレームからの経過時間を取得したい
FlxG.elapsed で取得できます。またFlxBasicを継承していれば、updateの引数 elapsed が経過時間(秒)となります
タイマー(一定時間経過後にコールバック)を使いたい
FlxTimerを使います。
trace("タイマー開始"); new FlxTimer().start(3, function(timer:FlxTimer) { // 3秒経過後に呼び出される trace("3秒経過"); });
衝突
FlxG.overlap と FlxG.collide の違い
FlxG.overlap は衝突検知のみ行います。FlxG.collide は衝突検知を行い、重ならないように衝突応答(押し戻し)を行います。
シューティングゲームなど、衝突しても反動で動かないゲームを作る場合は、overlap を使う方がよいです。
FlxG.collide で衝突しても動かない壁を作りたい
FlxG.collide は衝突応答をしますが、アクションゲームの床などはプレイヤーが上に乗ってもプレイヤーの重みで落ちないようにすることが多いはずです。そのような実装をしたい場合は、`FlxObject.immovable`(FlxSpriteのスーパクラスのフィールド)に`true`を設定すると動かなくなります。
/// PlayState.hx override public function create():Void { // 壁を作る var wall = new FlxSprite(0, 0); wall.makeGraphic(10, 240, FlxColor.GRAY); wall.immovable = true; // 当たっても動かなくなる add(wall);
衝突判定を行いたい
`FlxG.overlap(FlxBasic,FlxBasic)` または `FlxG.collide(FlxBasic,FlxBasic)`を使用します。これにより衝突判定が行われ跳ね返るようになります。
FlxG.overlap(ball, walls); // ボールと壁との当たり判定
引数にはFlxGroupやFlxSpriteなどが指定可能です。グループを渡すことで簡潔な記述が可能になります(自機の弾グループ vs 敵グループ など)。
衝突後に片方が消滅するようにしたい
`FlxG.overlap(FlxBasic,FlxBasic,Dynamic->Dynamic->Void)` または `FlxG.collide(FlxBasic,FlxBasic,Dynamic->Dynamic->Void)` を使用します。最後の引数はコールバック関数です。
/// PlayState.hx override public function update():Void { FlxG.collide(bat, ball, _cbPing); // 第三引数にコールバック関数を指定 } /** * バーとボールの当たりコールバック **/ private function _cbPing(bat:FlxObject, ball:FlxObject) { // ボールを消す ball.destroy(); }
コリジョンのサイズを小さくしたい
シューティングゲームなどでは、見た目よりも当たり判定を小さくしたい場合があると思います。 FlxObjectの width / height がコリジョンのサイズなので、この値を変更するとコリジョンのサイズが変わります。 ただ変更後はFlxSprite.centerOffsets()でコリジョン位置を調整する必要があります。
class Bullet extend FlxSprite { override public function create():Void { super(100, 100); // 画像を読み込み loadGraphic("assets/images/bullet.png"); // コリジョンサイズを半分にする width /= 2; height /= 2; // コリジョン位置を調整 centerOffsets(); } }
衝突範囲を目で確認したい
Flixelが持つデバッガを使用することで可視化できます。 FlxG.debugger.drawDebugに true を設定することで、コリジョンが赤い線で表示されるようになります。 詳細はFlixelデバッガの使い方で確認して下さい。
矩形でなく円で当たり判定を行いたい
FlxG.overlap() / FlxG.collide()は矩形同士の当たり判定を行います。そうではなくて円で当たり判定を行いたい場合はFlxG.overlap() 関数を使用して、独自に当たり判定のコードを記述します。
override public function update():Void { // プレイヤーとリングの当たり判定 FlxG.overlap(_player, _rings, _vsPlayerRing, _collideCircle); } private function _vsPlayerRing(p:Player, r:Ring):Void { // 衝突した時の処理 } private function _collideCircle(spr1:FlxSprite, spr2:FlxSprite):Bool { var r1 = spr1.width/2; // widthの半分を半径とする var r2 = spr2.width/2; // widthの半分を半径とする var px1 = spr1.x + r1; // 中心座標X var py1 = spr1.y + r1; // 中心座標Y var px2 = spr2.x + r2; // 中心座標X var py2 = spr2.y + r2; // 中心座標Y var p1 = FlxPoint.get(px1, py1); var p2 = FlxPoint.get(px2, py2); // 距離を取得 var dist = FlxMath.getDistance(p1, p2); if(r1*r1 + r2*r2 >= dist*dist) { // 当たった return true; } // 当たっていない return false; }
マウスの座標とスプライトが重なっているかどうかを調べたい
FlxObject.overlapPointを使うとチェックできます。
// 判定するスプライト var spr:FlxSprite; // マウス座標を取得 var pt = FlxPoint.get(FlxG.mouse.x, FlxG.mouse.y); if(spr.overlapsPoint(pt)) { // マウス座標と重なっている }
パーティクル
パーティクルが正しい位置から出現しない
FlxEmitter.start() により生成する FlxParticle は、update()のタイミングで生成がされます。 どういうことかというと、1フレーム内に複数回start()を呼ぶと、最後に呼び出したときの座標から出現してしまいます。 そのため、start()の後にupdate()を呼ぶと正しい位置から出現するようになります。
// パーティクルを(px,py)の座標から生成する public function explode(px:Float, py:Float):Void { x = px; y = py; start(true, 1, 0, 4, 1); super.update(FlxG.elapsed); // update()で生成する }
乱数
乱数を使いたい
FlxG.random が乱数オブジェクト(FlxRandom)となります。
var i = FlxG.random.int(0, 200); // 0 〜 200 の整数値をランダムで取得 var f = FlxG.random.float(0, 1.23); // 0.0 〜 1.23 の小数値をランダムで取得 var b = FlxG.random.bool(32.25); // 32.25%の確率でtrueになる
乱数を初期化したい
FlxRandom.initialSeed にシード値を設定すると初期化できます
FlxG.random.initialSeed = 10; // シード値 10 で乱数を初期化
なお、FlxG.random.resetSeed() を呼び出すと、ランダムなシート値で初期化します
配列をシャッフルしたい
FlxRandom.shuffleArray() を使います
var array:Array<Int> = [1, 2, 3, 4, 5]; FlxG.random.shuffleArray(array, 3); trace(array);
shuffleArrayの2番目の引数はシャッフル回数ですが、1回だけだとあまり混ざらないので、3回ほどするのが良い気がします
重み付けしてランダムな抽選をしたい
FlxRandom.weightedPick() を使うと、配列で指定した数値を按分して抽選を行います。 例えば、
- 木の棒が、100 の割合で見つかる (100/170 ≒ 58.82%)
- 青銅の剣が、50 の割合で見つかる (50/170 ≒ 29.41%)
- はがねの剣が、20 の割合で見つかる (20/170 ≒ 11.76%)
という場合は以下の記述をします。
// 抽選テーブル var array:Array<Float> = [100, 50, 20]; // 抽選実行。配列のインデックスを返す var idx = FlxG.random.weightedPick(array);
idx の値が、0であれば「木の棒」、1は「青銅の剣」、2は「はがねの剣」となります
State制御
Stateをやり直したい
現在のレベルをリセットして、リトライさせたいときがあると思います。その場合は、FlxG.resetState() を呼び出します
if(_player.exist == false) { // プレイヤーが死亡したら、現在のStateをやり直し FlxG.resetState(); }
別のStateに遷移したい
メインゲームからタイトル画面に戻る、というような制御をするには FlxG.switchState() を使用します。
if(_player.exist == false) { // プレイヤーが死亡したら、MenuStateに遷移する FlxG.swichState(new MenuState()); }
ゲームをリセットしたい
FlxG.resetGame() を使用すると、ゲームを最初から始めることができます。
カメラ
画面をスクロールさせたい
FlxCamera.follow()を使用します。また、カメラが移動可能な最大の範囲と当たり判定の範囲を設定します
var width = map.width; // マップの幅 var height = map.height; // マップの高さ // _playerを追いかけるカメラを設定 FlxG.camera.follow(_player); // カメラの移動範囲を設定 FlxG.camera.bounds = new FlxRect(0, 0, width, height); // 当たり判定の範囲を設定 FlxG.worldBounds.set(0, 0, width, height);
カメラのスクロール範囲を設定したい
FlxCamera.setScrollBoundsRect?() でカメラのスクロール範囲を指定できます。
例えば、ステージの幅が(1920, 480)の場合、
以下のコードで設定できます。
// カメラのスクロール可能な範囲を (0, 0)~(1920, 480)に設定 // コリジョンの有効範囲も同時に設定 FlxG.camera.setScrollBoundsRect(0, 0, 1920, 480, true);
なお、FlxCamera.setScrollBoundsRect?()の5番目の引数をtrueにすると、 FlxG.worldBounds(コリジョンの有効な領域)の設定も同時に行われます。
オブジェクトをスクロールせずに特定の位置に固定したい
FlxObject.scrollFactor.set(0, 0) を呼び出すことで、カメラのスクロールに対する移動を無効にします。
// スクロール無効 scrollFactor.set(0, 0);
なお、FlxSpriteGroupの子要素とする場合は、FlxSpriteGroup自身のscrollFactorを0に指定する(親のスクロールを無効にする)ことで、スクロールしなくなります。
カメラの移動位置を取得したい
FlxCamera.scrollが移動した位置となります。
override function update():Void { // カメラのスクロールに合わせて位置を合わせる x += FlxG.camera.scroll.x; y += FlxG.camera.scroll.y; }
HUDを画面の固定の位置に表示したい
HUD(Head-Up Display)など、スクリーン座標に描画したいオブジェクトは、FlxObject.scrollFactoerに(0, 0)を設定します
// カメラのスクロールにかかわらず、画面上の(8, 8)の位置に固定して表示します var txt = new FlxText(8, 8, 64, "Test"); txt.scrollFactor.set(0, 0); add(txt);
画面をフラッシュさせたい
FlxCamera.flash() を使用します
// 画面を1秒間、白フラッシュします FlxG.camera.flash(0xffFFFFFF, 1);
画面を揺らしたい
FlxCamera.shake() を使用します
// 画面を2%の揺れ幅で0.35秒間、揺らします FlxG.camera.shake(0.02, 0.35);
HUD用カメラを使う(同じHUDをFlxSubStateで表示したい)
FlxStateからFlxSubStateを呼び出した場合、通常はFlxStateに描画しているオブジェクトは表示されません。そこで、HUD用にメインカメラとは別のFlxCameraを登録すると、FlxSubStateでも描画されるようになります。
メインとなるFlxStateを「PlayState」、サブとなるFlxSubStateを「InventroyState?」とします。
/** * メインゲーム */ class PlayState extends FlxState { var _gameCamera:FlxCamera; // メインカメラ var _hudCamera:FlxCamera; // HUDカメラ var _hud:HUD; // Head-up Display // カメラ生成 _gameCamera = new FlxCamera(); _hudCamera = new FlxCamera(); // カメラを初期化してゲームカメラを登録する FlxG.cameras.reset(_gameCamera); // HUDカメラを追加 FlxG.cameras.add(_hudCamera); // HUDカメラの背景を透過する _hudCamera.bgColor = FlxColor.TRANSPARENT; // ゲームカメラをメインカメラにする FlxCamera.defaultCameras = [_gameCamera]; // HUDにHUDカメラを登録 _hud.setCamera(_hudCamera); }
デフォルトで登録されているメインカメラを削除し、新たに作った_gameCameraをメインカメラとします。そして、HUDカメラを登録します。HUDカメラは背景色が不要なので、FlxColor.TRANSPARENTで透過します。最後の部分で、HUDにHUDカメラを登録しています。
HUDクラスは以下のように実装します。
/** * Head-up Display */ class HUD extends FlxSpriteGroup { // カメラを登録する public function setCamera(cam:FlxCamera):Void { forEach(function(member:FlxSprite) { member.scrollFactor.set(0, 0); // スクロール無効 member.cameras = [cam]; // カメラを登録 }); } }
これで、InventoryState?を呼び出しても、HUDが描画されるようになります。 カメラにオブジェクトを登録するのではなく、オブジェクトにカメラを登録する、という考え方となります。
セーブ
ゲームデータをセーブしたい
FlxSaveを使います。
// FlxSave生成 var save = new FlxSave(); // "SAVEDATA"という名前でバインド save.bind("SAVEDATA"); // スコアを登録 save.data.score = score; // セーブデータ書き込み save.flush();
ゲームデータをロードしたい
// FlxSave生成 var save = new FlxSave(); // "SAVEDATA"という名前でバインド save.bind("SAVEDATA"); // スコアを取得 if(save.data != null && save.data.score != null) { // 取得 score = save.data.score; } else { // 保存されていない score = 0; }
セーブデータを初期化したい
// FlxSave生成 var save = new FlxSave(); // "SAVEDATA"という名前でバインド save.bind("SAVEDATA"); // セーブデータを初期化 (自動で書き込みも行う) save.erase();
ファイル制御
ファイルを保存したい
Neko環境のみ、sys.io.Fileが使えます。
// "/Users/syun/Desktop"にhope.txtを保存。内容は"hogehoge" sys.io.File.saveContent("/Users/syun/Desktop/hoge.txt", "hogehoge"); // アプリケーション実行フォルダ内に"hoge.txt"を保存。内容は"hogehoge" sys.io.File.saveContent("hoge.txt", "hogehoge"); // アプリケーション実行フォルダ内の"hoge.txt"を読み込み trace(sys.io.File.getContent("hoge.txt"));
getBytes / saveBytes を使用するとバイナリを保存できます。
ファイルの存在チェックをしたい
openfl.Assetsを使うと存在チェックができます。
import openfl.Assets; …… public function exists():Bool { // "hoge.txt" が存在するかどうかをチェックする var path = "assets/data/hoge.txt"; if(Assets.exists(path, TEXT)) { // 存在する return true; } else { // 存在しない return false; } }
Assets.existsの第二引数には以下のものが指定できます。
- BINARY: バイナリ
- FONT: フォント
- IMAGE: 画像
- MOVIE_CLIP: 動画
- MUSIC: サウンド(BGM)
- SOUND: サウンド(SE)
- TEMPLATE: テンプレート(?)
- TEXT: テキスト
通信
HTTP通信のPOST送信をしたい(Flash不可)
Httpクラスを使うとPOST送信ができます。
// HTTPクラス生成 var http = new Http("http://example.com/post.php"); // POSTデータ作成 var post = "id=hoge&pass=123"; // Content-TypeにURLエンコード不要であることを指定する http.setHeader( "Content-Type", "application/x-www-form-urlencoded" ); // POSTデータを設定 http.setPostData(post); // POST送信する (falseを指定するとGET送信) http.request(true); // レスポンスデータを出力 trace(h.responseData);
ただし、このクラスはFlash環境では動きません。Flash環境でHTTP通信を行うには、Javascriptと連携する必要があります。
Javascriptと連携したい(Flashのみ)
// 送信データ作成 var param = Json.parse('{"score":100}'); // Javascriptの"SendScore"関数を呼び出す var ret = flash.external.ExternalInterface.call("SendScore", param); // 返却データ出力 trace(ret);
Javascript側では以下のように記述します。
window.SendScore = function(obj){ // 受け取ったデータを表示 alert(obj.score); // 返却 return 200; }
なお、JavascriptからPOSTするには、jQueryのAjaxを使う必要があります。
window.SendScore = function(obj){ // POST送信 $.ajax({ type: 'POST', url: 'post.php', cache: false, data: 'id=test&pass=123', success: function(html){ // 返却データ受け取り $("p.text").text(html); } }); }
サウンド再生
BGMやSEを再生したい
FlxGが持つSoundFrontEndを使用して再生をします。
// BGMを再生 FlxG.sound.playMusic("assets/music/bgm.ogg"); // SEを再生 FlxG.sound.play("assets/sounds/se.mp3");
ただし、環境によって使用可能なフォーマットが異なるので、Project.xmlにサウンドファイルを定義しておいたほうがいいです。
/// Project.xml <!-- Flash環境は mp3 のみ再生可能 --> <assets path="assets/sounds" if="flash" exclude="ogg"> <sound path="enemyhit.mp3" id="enemyhit" /> <sound path="enemykill.mp3" id="enemykill" /> <sound path="gameover.mp3" id="gameover" /> </assets> <!-- それ以外は ogg のみ再生可能 --> <assets path="assets/sounds" unless="flash"> <sound path="enemyhit.ogg" id="enemyhit" /> <sound path="enemykill.ogg" id="enemykill" /> <sound path="gameover.ogg" id="gameover" /> </assets>
上記定義の場合は id 要素に指定した値で再生します。
FlxG.sound.play("enemyhit");
再生中のBGMと同じBGMを再生しようとした場合に、何もしないようにしたい
同じBGMを再生しようとした場合に、再生し直さないようにする方法です。
Regクラスにstatic変数と関数を用意します。
/// Reg.hx class Reg { private static var _lastPlayMusic:String = ""; pubulic static function playMusic(name:String):Void { var bPlay:Bool = false; if(FlxG.sound.music == null) { // 1度も再生していないので再生する bPlay = true; } if(FlxG.sound.music.active == false) { // BGMが停止しているので再生する bPlay = true; } if(Reg.lastPlayMusic != name) { // 違うBGMなので再生する bPlay = true; } if(bPlay) { // 再生する FlxG.sound.playMusic(name); // 再生したBGMの名前を覚えておく _lastPlayMusic = name; } }
BGMを再生しようとすると、一時的に画面が止まります
BGMはストリーム再生ではなくまとめて読み込む仕組みとなっているため、iOSなどではディスクアクセスで止まってしまうようです。 これを回避するにはキャッシュ機能を使用します。
/// Reg.hx // BGMをキャッシュする public static function cacheMusic():Void { FlxG.sound.volume = 1; FlxG.sound.cache("001"); FlxG.sound.cache("002"); FlxG.sound.cache("003"); } // キャッシュを使ってBGMを再生する public static function playMusic(name:String, bLoop:Bool=true):Void { // キャッシュからBGM取り出し var sound = FlxG.sound.cache(name); if(sound != null) { // キャッシュがあればキャッシュから再生 FlxG.sound.playMusic(sound, 1, bLoop); } else { // なければディスクからロードして再生 FlxG.sound.playMusic(name, 1, bLoop); } }
サウンドのボリュームが小さくなってしまった。もしくは無音になってしまった
キーボードの「-」キーを押すとボリュームが小さくなります。大きくするには「+」キーを押します。ただし環境によっては動作しないかもしれません。その場合はテンキーの「+」キーを押すと音が大きくなります。
- 「-」キー / テンキーの「-」: 音量を小さくする
- 「+」キー / テンキーの「+」: 音量を大きくする
- 「0」キー / テンキーの「0」: 無音にする
BGMを区間ループしたい
FlxSound.loopTimeにループ開始時間を設定します。詳しくは「BGMを区間ループする」にまとめています。
デバッグ機能
デバッガを使いたい
ビルドオプションに「-debug」をつけてビルドすると「`(バッククオート)」で表示・非表示をトグルできる……、ようなのですが、なぜかできません(2016/1/20追記: キーボード配列がUSキーボードでないと正常に動作しないようです)。 そこで、以下の記述でトグルキーを割り当てます。
FlxG.debugger.toggleKeys = ["ALT"];
割り当てるキーは何でもいいのですが、一番問題なさそうな ALTキー としました。これで実行中に ALTキー でFlixelデバッガを表示します。
デバッガの色々な機能を知りたい
FlixelDebuggerに詳細な情報をまとめています。
その他
アプリケーションを終了したい
強制的にアプリを終了するには System.exit(0) を使用します。
import flash.system.System; System.exit(0);
環境
iOSで動かしたい
iOS版をAppleに提出するための手順を読んでpackeageやiOSタグを正しい設定に編集します。
Androidで動かしたい
Android版をAndroid Marketに提出するまでの手順に方法をまとめています。
flixel.addonsをimportするとコンパイルエラーになる
Project.xmlの書き方を読んでhaxelibタグを正しい設定に編集します。
Windowsでビルド時に原因不明のエラーとなる
assetsフォルダ内に日本語のファイル名が含まれるとコピーに失敗してエラーとなります。なので日本語のファイル名をつけてはいけません。
AdMobを組み込みたい
AdMobの組み込み方法に手順をまとめています。