2014年2月12日水曜日

新作Androidアプリ開発 ボールの跳ね返り編

前回ボールを投げることが出来たので今回はバックボードとリングの接続部(フランジ)との跳ね返りの処理を作成したいと思います
処理の大まかな流れは以下

対象との衝突判定
 ↓
衝突位置を計算
 ↓
衝突後の速度を計算
 ↓
衝突後の位置を計算

まずはバックボードの部分、ボールの移動前、移動後の位置を与えて当たっていれば衝突位置を返すメソッドを作成します
final private static int R = 122;     //半径(mm)
final private static int BOARD_Y = 1400;
final private static Plot BOARD_LEFT = new Plot(6600, BOARD_Y + R);
final private static Plot BOARD_RIGHT = new Plot(8400, BOARD_Y + R);
final private static Rect BOARD_H = new Rect(6600 - R, 0, 8400 + R, 10);
final private static Rect BOARD_V = new Rect(0, 2900, 10, 3950);

//バックボートとの衝突判定関数
public Plot hitBoard(Plot prev, Plot move) {
    Plot ret = null;
    //大雑把な判定条件としてボード方向へ移動している、移動のY座標がボード位置をまたがっている
    if (move.y < prev.y && (move.y - Ball.R <= BOARD_Y && prev.y + Ball.R > BOARD_Y)) {
        //移動前後のZ座標にボールの半径を加味したRectを生成
        Rect rv = new Rect(0, (prev.z < move.z ? prev.z : move.z) - Ball.R, 10, (prev.z < move.z ? move.z : prev.z) + Ball.R);
        //移動前後のX座標にボールの半径を加味したRectを生成
        Rect rh = new Rect(prev.x < move.x ? prev.x : move.x, 0, prev.x < move.x ? move.x : prev.x, 10);
        //ボードの垂直方向Rect(BOARD_V)と水平方向Rect(BOARD_H)と生成したrv、rhを交差判定する
        if (Rect.intersects(BOARD_V, rv) && Rect.intersects(BOARD_H, rh)) {
            //移動直線とボードから水平方向交点を取得(getLineVSLineは2直線の交点を返す関数)
            ret = Utl.getLineVSLine(new Plot(prev.x, prev.y), new Plot(move.x, move.y), BOARD_LEFT, BOARD_RIGHT);
        }
    }
    //衝突がなければnullをあれば衝突位置を返す
    return ret;
}
こんな感じ
このメソッドをボールの移動処理にはさみます
final private float bk = 0.95f;

//位置変更
Plot prev = new Plot(position);
Plot move = new Plot(position);
move.x += ((xSpeed * interval) / 1000);
move.y += ((ySpeed * interval) / 1000);
move.z += ((zSpeed * interval) / 1000);
Plot hb = hitBoard(prev, move);
if (hb != null)  {
    //反発係数bkを速度にかける
    xSpeed *= bk;
    ySpeed *= -bk; //y方向の速度を反転
    zSpeed *= bk;
    //衝突後の経過時間を計算
    int citv = (int)((interval * Utl.getDistancePtoP(hb, move)) / Utl.getDistancePtoP(prev, move));
    hb.z = (int)(prev.z + ((zSpeed * (interval - citv)) / 1000));
    prev = new Plot(hb);
    //移動後の位置を変更速度から再計算
    move.x = (int)(hb.x + ((xSpeed * citv) / 1000));
    move.y = (int)(hb.y + ((ySpeed * citv) / 1000));
    move.z = (int)(hb.z + ((zSpeed * citv) / 1000));
}
こんな感じ、これを動かしてみます
よさそうですね
次にフランジ部分、ほぼバックボードと一緒です
速度がY軸方向ではなくZ軸方向に反転します
final private float fk = 0.92f;

Plot hf = hitFlange(prev, move);
if (hf != null) {
    //反発係数fkを速度にかける
    xSpeed *= fk;
    ySpeed *= fk;
    zSpeed *= -fk; //z方向の速度を反転
    prev = new Plot(hf);
    //衝突後の経過時間を計算
    int citv = (int)((interval * Utl.getDistancePtoP(hf, move)) / Utl.getDistancePtoP(prev, move));
    //移動後の位置を変更速度から再計算
    move.x = (int)(hf.x + ((xSpeed * citv) / 1000));
    move.y = (int)(hf.y + ((ySpeed * citv) / 1000));
    move.z = (int)(hf.z + ((zSpeed * citv) / 1000));
}

//フランジとの衝突判定関数
public Plot hitFlange(Plot prev, Plot move) {
    Plot ret = null;
    //大雑把な判定条件として落下方向へ移動している、移動のZ座標がフランジ高さをまたがっている
    if (move.z < prev.z && move.z - Ball.R <= FLANGE_H && prev.z - Ball.R > FLANGE_H) {
        //フランジ高さに達するまでの比率を計算
        int r = (int)((((prev.z - Ball.R) - FLANGE_H) * 100) / (prev.z - move.z));
        //フランジ高さに達した時の位置を取得
        Plot pos = Utl.getPlotInLine(r, prev, move);
        //フランジとの交差判定
        if (FLANGE.contains(pos.x, pos.y)) {
            //フランジとの衝突位置をセット
            ret = new Plot(pos.x, pos.y, FLANGE_H + Ball.R);
        }
    }
    //衝突がなければnullをあれば衝突位置を返す
    return ret;
}
動かしてみます

よさそうです
衝突判定の仕方や衝突位置の取得方法はいろんなやり方があるので、それぞれのやりやすい方法でいいと思います
特にフランジの衝突位置計算なんかは強引に比率で出しちゃってますが、良い子はマネしちゃダメ

Rect.intersectsはRect同士の交差判定ができますので大雑把な判定するのに便利です
バックボードとフランジは軸に平行なため、跳ね返り処理は軸の法線方向への速度反転だけで済むので割と簡単ですね

あとゴールリング(リム)との跳ね返りがありますが、こちらは少し面倒なので次回にします

2014年2月6日木曜日

新作Androidアプリ開発 ボールを投げてみる編

新作アプリはバスケシュートゲームなんで何はともあれボールを投げてみましょう
ボールを投げるということでまずBall.classの作成
public class Ball {
    final public static int G = 9800;    //重力加速度(mm/s)
    final public static float k = 0.01f; //空気抵抗係数
    final public static int R = 122;     //半径(mm)
    public Plot position = new Plot();   //位置
    public int xSpeed; //X方向の速度(mm/s)
    public int ySpeed; //Y方向の速度(mm/s)
    public int zSpeed; //Z方向の速度(mm/s)

    public Ball() {}

}

必要となる主要メンバはボールの速度、位置ですね
ここで位置を表すpositionのクラスPlotはxyz座標を持った自作クラスです
これに初速度と垂直・水平方向の投射角を与えてやります
public void shot(double speed, double vangle, double hangle) {
    //speed  初速(mm/s)
    //vangle 垂直方向の角度
    //hangle 水平方向の角度
    zSpeed = (int)(speed * Math.sin(vangle));
    speed = speed * Math.cos(vangle);
    xSpeed = (int)(speed * Math.cos(hangle));
    ySpeed = (int)(speed * Math.sin(hangle));
    position = new Plot(6423, 8682, 1800);
}

まず与えた初速度をMath.sin(垂直角度)cos(垂直角度)で水平方向と垂直方向の速度に分解
分解した水平方向の速度をまたMath.sin,cos(水平角度)でXY方向の速度に分解してやります
XYZに分解してやれば、それぞれに経過時間をかけるだけで移動距離が出ます

public void move(long interval) {
    //重力加速度による加速
    zSpeed -= ((G * interval) / 1000);
    //位置変更
    position.x += ((xSpeed * interval) / 1000);
    position.y += ((ySpeed * interval) / 1000);
    position.z += ((zSpeed * interval) / 1000);
}

経過時間intervalはミリ秒、物理で習ったように重力加速度をZ方向速度に加算します
移動距離は速度×時間ですからXYZ方向の各速度に(interval/1000ミリ秒)かけた分を加算すれば移動後の位置が取得できます
計算式としては

となりますが、そのままコードにして
int x = x + (vx * (interval / 1000));
などとしてしまうと式に使われている変数はintとlongしかありませんので(interval / 1000)の部分は整数に丸められてしまいます
こういった最終的にint型が必要だとしても、途中計算までもintで行うことはお勧めしません
このように必ず分子に比率をかけてから除算しなければなりません
int x = x + ((vx * interval) / 1000);

こんな感じ
投げた感じがよくわからないので、横からの様子を描画

よさそうですが、なんか飛びすぎな感じがします
学校の物理では省略されましたが、ボールの投射の実際には空気抵抗による影響があります
そこで空気抵抗の計算方法を調べてみますとこのような式が出てきました

参考:ホームランの打球について
http://www.nasekouki.co.jp/~shig/homerun.html


Cd:抗力係数
p:空気密度
v:速度
A:ボールの見付け面積

速度によって変わるようですね、回転速度なども影響するようです
計算がめんどくさい大変なのでやめます
本格的なシミュレータを目指してるわけではないので、簡易式を作ります


k:空気抵抗簡易係数

これを取り入れてmove関数を修正

final public static float k = 0.01f; //空気抵抗係数
public void move(long interval) {
    //空気抵抗による減速
    xSpeed -= ((xSpeed * k * interval) / 1000);
    ySpeed -= ((ySpeed * k * interval) / 1000);
    zSpeed -= ((zSpeed * k * interval) / 1000);
    //重力加速度による加速
    zSpeed -= ((G * interval) / 1000);
    //位置変更
    position.x += ((xSpeed * interval) / 1000);
    position.y += ((ySpeed * interval) / 1000);
    position.z += ((zSpeed * interval) / 1000);
}

これを実行してみます
それらしくなったので良しとします

2014年2月4日火曜日

新作Androidアプリ開発 画面設計編

前回紹介した新作アプリについていろいろと書いていこうと思います
なにはともあれまずは画面設計


こんな感じ、中央バスケットコート部分はアスペクト固定の縦100%でセット
端末による画面サイズの違いを可変部分で吸収します
コート部分左上を原点(0,0)としてボールやゴールを扱うことにする
タッチや描画と実座標を合わせるためCourtクラスをつくる

public class Court {
    final public Rect SIZE = new Rect(0, 0, 15000, 12750); //(mm)
    final public int BALL_R = 122;
    final public int RING_R = 230;
    final public Point RING_CENTER = new Point(7500, 1790);
    public static Rect view;
    public Court(Rect r) {
        view = r;
    }
    public Point getDPlot(Point point) {
        int x = view.left + ((point.x * view.width()) / SIZE.width());
        int y = view.top + ((point.y * view.height()) / SIZE.height());
        return new Point(x, y);
    }
}

こんな感じでSIZEに実座標の大きさをいれてコンストラクタのRect r に描画するピクセル単位のサイズをセットする
あとはgetDPlotメソッドのように、実座標の位置を与えて描画位置を返すメソッドを作成して、実座標から描画位置を取得してボールの描画などを行います

2014年2月3日月曜日

新作Androidアプリの企画

今年の目標として月1本アプリを公開すると決めていたんですが
2月になってしまいました…
現在新作ゲームを鋭意開発中ですが自分に発破をかける意味でもここに進捗を書いていこう

アプリ名:1Min Basketball(仮)
概要:バスケットボールのシュートを1分間でどれだけゴールできるかを競うゲーム
コンセプト:
・既存同型ゲームにはない見た目と操作
・1ゲーム1分で終わるスキマ時間ゲーム
・何度もやりたくなるスルメゲーム
  目指せTouch the numbers
徴:
・見た目リアル志向のリッチなイメージ
・他同型ゲームでは見ない見下ろし型のゲーム画面
・他のゲームにはまずない統計分析機能付
画面イメージ

もしこれが企業での企画なら絶対通らないでしょう
今更バスケットのシュートゲームかよと一蹴されて終わる

しかしここが個人開発者の最高なところ
作りたいから作るのです
誰にも文句は言われないのです
それが報われるかどうかは別として…

2014年1月29日水曜日

終了したソーシャルゲームのコンテンツフリー化 (商用利用可・事前承諾不要)

無料で商用利用可・事前承諾不要なコンテンツフリー化をソーシャルゲーム「星宝転生ジュエルセイバー」が開始
http://gigazine.net/news/20140121-jewelsavior-free-contents/

株式会社ブリリアントサービスが昨年サービス終了したソーシャルゲーム"星宝転生ジュエルセイバー"の画像データを商用利用も含め無償で配布・提供する「ジュエルセイバーオープンコンテンツサイト」を公開しました。

さすがプロが作っているアプリ、素材でありそのクオリティは非常に高いものとなっています。
キャラクターだけでも300種以上の素材があり、いろいろなゲームに利用できそうです。

個人でゲームを作る場合、このような画像をふんだんに使ったゲームを作ることは物理的に非常に困難です。
ゲームの世界での画像素材は基本そのゲームのためだけに作成され、終了したらそこでおしまいと使い捨てのような面があります。
今回のこのような試みは個人開発者としては非常にありがたいところですが、素材の有効利用、長寿命化につながることは、業界としてもかなりの画期的なことではないでしょうか。

なお事前承諾は不要なものの、事後承諾が必要なようですのでご注意ください。

2014年1月21日火曜日

東芝プレイスにて簡単ガーデニングプラン作成アプリ「庭師」が紹介されましたー

東芝プレイス内のアッププレイスにてスマホで簡単ガーデニングプラン作成アプリ「庭師」が紹介されましたー


今回はアンドロイダーさんの提携メディアでの紹介となりました。ありがとうございました。

2014年1月20日月曜日

GooglePlayで登録するタイトル(アプリ名)の命名がやはりすごく大事

リリースから1月ほどたった、スマホで簡単ガーデニングプラン作成アプリ「庭師」ですが思っていたよりもダウンロードが伸びません。

まあもともとターゲットがニッチであることと、いまだレビューサイトに紹介されていないため当然の結果ではあるかもしれません。

現状期待できるアプリのインストール導線は、拙作「間取りー図」のタイトル画面でのリンク表示とGooglePlayの検索からのインストールがほとんどかと思われますが、このGooglePlayの検索において一番期待している"ガーデニング"と"庭"というキーワードで庭師が出てこない

ということがずっと気になってました。

"庭師"や"ガーデニング プラン"あたりなら何とか出てきますが、これでは検索からの流入がなかなか期待できません。

そこで登録するタイトルを「庭師 ガーデニング」に変えてみたところ、なんと!"ガーデニング"での検索でいきなり5番目になりました



検索のランキングではダウンロード数やg+などいろんな要素が加味されてるといわれていますが、アプリ名の効果は絶大のようです。
ちなみに"庭"ではいまだに出てきません。庭師なのに…