2014年2月28日金曜日

消えそうなアプリがアプリ名変更で大復活! EasyBowlingが5万ダウンロード突破!

2012年に公開したEasyBowlingですが、2013年の3月頃をピークに有効インストールは下がり続けてあとはただ消えゆくのみという状態でした
ある時ふと、GooglePlayの検索時「Bowling 」での検索順位がちょっと低いなぁと感じ、2013年の末にアプリ名を"EasyBowling"から"Easy Bowling"と間にスペースを入れたものに変更してみることにしました

するとびっくり!今まで「Bowling 」での検索100位にも入っていなかったEasyBowlingがいっきに25位になりました
その後インストールが一気に伸びて50000ダウンロードを達成
やはりアプリ名は非常に大事というお話ですね

アプリ名変更前は1日のダウンロード数は50以下程度でしたが、変更後はだいたい200くらいに上昇しました
EasyBowlingの無料スポーツゲームカテゴリでのランクは現在350位くらいなのでランキングからの流入はほとんど見込めません
それでも200ダウンロード/日の自然流入があるというのは、検索からの流入が効いているということかと思います

2014年2月26日水曜日

新作Androidアプリ開発 ボールをランダムに配置しよう編

ボールを操作で投げれるようになったので、今回はボールを任意のエリアにランダムに配置してみましょう
配置したいエリアはというと、距離的にフリースローからスリーポイントまでの間にあたる扇状の青の部分です
操作の都合上、リングの左右側の鋭角を10°程削っています
これをどう計算してランダムな座標を生成するか
もし対象とするエリアが長方形だったりすると話は簡単です
XとY軸の対象範囲をRandom.nextInt(value)するだけ
Rect area = new Rect(100, 100, 400, 300);
Random random =new Random();
int x = area.left + random.nextInt(area.width());
int y = area.top + random.nextInt(area.height());
試しに1000個ほど生成して描画してみましょう

Rect area = new Rect(100, 100, 400, 300);
canvas.drawRect(area, paint);
Random random =new Random();
for (int i = 0; i < 1000; i++) {
    int x = area.left + random.nextInt(area.width());
    int y = area.top + random.nextInt(area.height());
    canvas.drawCircle(x, y, 3f, paint);
}
なんか気持ち悪いですね
今回は変形扇形のエリアではいろいろと方法はあると思いますが、このような考え方で生成しました
赤枠部分フリースローラインからスリーポイントラインまでの範囲でランダムにセットした座標を、対象範囲角をランダムにセットして回転する

ソースとしてはこのような感じ
Random random =new Random();
Plot ring = new Plot(7500, 1882);
for (int i = 0; i < 1000; i++) {
    //リングX座標+フリースローラインまでの距離(4.118m)+フリースローラインからスリーポイントラインの間(2.52m)の間のランダム値
    Plot base = new Plot(ring.x + 4118 + ((2520 * random.nextInt(100)) / 100), ring.y);
    //削った10度+配置部分160°のランダム値
    int angle = 10 + random.nextInt(160);
    //回転処理
    Plot pos = Utl.rotate(angle, ring, base);
    //描画座標変換
    pos = Court.getDPlot(pos);
    canvas.drawCircle(pos.x, pos.y, 3f, paint);
}
試しに1000個ほど生成して描画
よさそうです

2014年2月18日火曜日

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

投げたボールの処理が代替できたので、次にユーザーの操作でボールを投げるところを作ります
操作手法はこんな感じ
いわゆるAngryBird投射法です
処理の考え方としては引っ張り長さに制限をつけて、引っ張っていない時を0、最大まで引っ張った時を100として初速度を計算します


v0:初速度
d:引っ張り長さ
dm:最大引っ張り長さ
min:引っ張り0の時の速度
max:引っ張り100の時の速度

式は簡単ですが、minとmaxの値は初速度を決めるパラメータ、つまりはゲームバランスに繋がるのでテストと調整が非常に重要になります

あとは引っ張ったところからボール初期位置への角度を水平方向の投射角として

新作アプリ開発 ボールを投げてみる編で作ったshot関数

public void shot(double speed, double vangle, double hangle)

に投げてやれば完成

2014年2月15日土曜日

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

前回ボールの跳ね返りをやりました
残るはリングとの衝突と跳ね返り処理です
これはかなり悩みました、3日ほど悩んでむりくりひねり出した結論ですが

※以下の考え方は物理的に正しいかどうかは不明です※

まず例としてこんなボールとリングの衝突と跳ね返りがあるとします
ごく当たり前の跳ね返りですが、これをどうプログラムで計算するのか?
とりあえず衝突点を求めます
そしてリングの断面の中心とボールの中心点を結んで衝突面の法線ベクトルを求めます

その衝突法線ベクトルが作る面に対しての3次元速度ベクトルの反射処理と考えました

この考え方を元にまずボールの移動前、移動後の位置を与えてリング当たっていれば衝突位置を返すメソッドを作成します
//リングとの衝突判定関数
public static Plot hitRing(Plot prev, Plot move) {
    Plot ret = null;
    //大雑把な判定条件として落下方向への移動、移動のZ座標がリング高さをまたがっている
    if (move.z < prev.z && move.z - Ball.R <= RING_H && prev.z - (Ball.R / 2) > RING_H) {
        int r = (int)((((prev.z - Ball.R) - RING_H) * 100) / (prev.z - move.z));
        //ボール下面がリング高さに来る位置取得
        Plot pos = Utl.getPlotInLine(r, prev, move);
        //リング中央との距離取得
        int d = Utl.getDistancePtoP(RING_CENTER, new Plot(pos.x, pos.y));
        //リングに当たりそうな距離にあればさらに詳しく判定
        if (d > (RING_R - Ball.R) && d <= RING_R + Ball.R) {
            float dagl = Utl.getRadians(Court.RING_CENTER, pos);
            int dx = (int)(Court.RING_CENTER.x + Court.RING_R * (Math.cos(dagl)));
            int dy = (int)(Court.RING_CENTER.y + Court.RING_R * (Math.sin(dagl)));
            int bw = Utl.getDistancePtoP(new Plot(dx, dy), new Plot(pos.x, pos.y));
            int bz = (int)(Ball.R * Math.sin(Math.acos((double)(Ball.R - bw) / (double)Ball.R)));
            r = (int)(((prev.z - (pos.z - bz)) * 100) / (prev.z - move.z));
            //実際にリングに当たる位置取得
            pos = Utl.getPlotInLine(r, prev, move);
            //リング中央との距離取得
            d = Utl.getDistancePtoP(Court.RING_CENTER, new Plot(pos.x, pos.y));
            //取得した距離からリングに当たるかどうか判定
            if (d > (Court.RING_R - Ball.R) && d <= Court.RING_R + Ball.R) {
                ret = new Plot(pos);
            }
        }
    }
    return ret;
}
こんな感じ
このメソッドをボールの移動処理にはさみます
Plot hr = hitRing(prev, move);
if (hr != null) {
    //法線ベクトル取得
    float angle = Utl.getRadians(Court.RING_CENTER, hr);
    int dx = (int)(Court.RING_CENTER.x + Court.RING_R * (Math.cos(angle)));
    int dy = (int)(Court.RING_CENTER.y + Court.RING_R * (Math.sin(angle)));
    //衝突時の速度ベクトルと法線ベクトルの内積を計算
    float ip = Utl.getInnerProduct(new Plot(xSpeed, ySpeed, zSpeed), new Plot(hr.x - dx, hr.y - dy, hr.z - Court.RING_H));
    //内積が+のときは裏からの当たりなので処理しない
    if (ip < 0) {
        //速度ベクトルの跳ね返り後の速度を取得
        Plot dv = getReflect(new Plot(xSpeed, ySpeed, zSpeed), new Plot(hr.x - dx, hr.y - dy, hr.z - Court.RING_H));
        float dk = 0.6f;
        //反発係数dkを速度にかける
        xSpeed = (int)(dv.x * dk);
        ySpeed = (int)(dv.y * dk);
        zSpeed = (int)(dv.z * dk);
        //衝突後の経過時間を計算
        int citv = (int)((interval * Utl.getDistancePtoP(hr, move)) / Utl.getDistancePtoP(prev, move));
        //移動後の位置を変更速度から再計算
        move.x = (int)(hr.x + ((xSpeed * citv) / 1000));
        move.y = (int)(hr.y + ((ySpeed * citv) / 1000));
        move.z = (int)(hr.z + ((zSpeed * citv) / 1000));
        prev = new Plot(hr);
    }
}
こんな感じ、いろいろと処理がひどいですが見ないでやってください(良い子はまねしちゃダメ)
試したところやや動きが怪しいところがありますが、基本方針としてはよさそう

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
徴:
・見た目リアル志向のリッチなイメージ
・他同型ゲームでは見ない見下ろし型のゲーム画面
・他のゲームにはまずない統計分析機能付
画面イメージ

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

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