ラベル 開発 の投稿を表示しています。 すべての投稿を表示
ラベル 開発 の投稿を表示しています。 すべての投稿を表示

2017年11月21日火曜日

iPhoneX時代の画面設計

iPhoneXが発売され、いわゆるアスペクト比2:1の端末が増えてきました
AndroidでもGaraxy S8、LG G8などが2:1に近いアスペクト比となっており、GoogleもAndroidアプリ開発者に対し、アスペクト比2:1以上の画面に対応することを促しています

例えば横スクロールのアクションゲームで、タブレット端末でよくある4:3の端末と
2:1の両対応を単純にしようとするとこのような画面になってしまいます
これだとステージの先、見えている敵キャラが違うためゲームの難易度に大きな違いが生じてしまいます
4:3のアスペクト比を基本にして、それよりも横長なら両端黒帯ってのは簡単な対応ですが、それは避けたいところですので、いくつか対応案を考えてみました
1つ目は2:1の横幅を基本として天井(空)の拡縮で対応する横幅固定の下寄せ
こんな感じ
見た目にかなり差が出ますが、見えているステージの範囲は同じになるので難易度に違いは出なくなるかと
2つ目は横スクロールでも天井がある場合や、シューティングの場合ステージの縦幅を変えるのは都合が悪い時に対応する横幅固定の真ん中寄せ
横幅は2:1を基本にして上下の地面と天井部分を拡縮します
ものによっては地面と天井の比率を変えてもよいかと思います

3つ目は2:1端末が増えてきたとはいえ、主流はまだまだ16:9のワイドなので、それを基本として調整するバランス型
横幅は16:9のワイドを基本にして、2:1端末では影響が出にくい自キャラの後ろ部分を拡縮してやります
4:3では上部分を拡縮して対応

以上3つの手法を紹介しましたが、やり方はまだまだあると思います
要はメインとなるステージの縦と横幅で変えられないところ、アスペクト比が変えられるのかというところを決めて、基本とする端末のアスペクト比からサイズ差を吸収する調整枠を決定するという流れになるかと思います

2017年11月10日金曜日

XMLで作るアニメーションするメニュー

新作アプリオタマーズ(仮)作ってます タイトルメニューに階層があって、切り替えにアニメーションをつけてみたので紹介します
ほぼほぼXMLで済ませているので簡単です
画面レイアウトのXMLがこちら

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 <ImageView
 <FrameLayout
  <LinearLayout
  <ImageButton
   android:id="@+id/setting"
  <ImageButton
   android:id="@+id/mission"
・・・
  </LinearLayout>
 </FrameLayout>
</LinearLayout>
くっそ長いので全文は下のほうに追いやりました
メニュー部分はFrameLayoutで直下のLinearLayoutに1階層目のボタン3つ
id="@+id/stageの部分が2階層目になっててvisible=goneで隠してます
次にAnimation用のXMLがこちら
// 1階層目メニュースライドイン
// res/anim/menu_in.xml

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
 android:interpolator="@android:anim/decelerate_interpolator"
 android:fromXDelta="-100%p"
 android:toXDelta="0%p"
 android:fillAfter="true"
 android:duration="800" />

// 1階層目メニュースライドアウト
// res/anim/menu_out.xml

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
 android:interpolator="@android:anim/decelerate_interpolator"
 android:fromXDelta="0%p"
 android:toXDelta="120%p"
 android:fillAfter="true"
 android:duration="800" />

// 2階層目メニューズームイン
// res/anim/zoom_in.xml

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
 android:interpolator="@android:anim/decelerate_interpolator"
 android:fromXScale="0"
 android:toXScale="1.0"
 android:fromYScale="0.0"
 android:toYScale="1.0"
 android:pivotX="50%"
 android:pivotY="50%"
 android:fillAfter="true"
 android:duration="600" />
で、ボタンを押したときの処理がこちら
動かすViewに上記のstartAnimationをセットしてやるだけです
@Override public void onClick(View v) {
  switch (v.getId()) {
    case R.id.mission : //2階層目表示
      menu_off = true;
      findViewById(R.id.stage).startAnimation(AnimationUtils.loadAnimation(this, R.anim.zoom_in));
      findViewById(R.id.setting).startAnimation(AnimationUtils.loadAnimation(this, R.anim.menu_out));
      findViewById(R.id.mission).startAnimation(AnimationUtils.loadAnimation(this, R.anim.menu_out));
      findViewById(R.id.challenge).startAnimation(AnimationUtils.loadAnimation(this, R.anim.menu_out));
      break;
    case R.id.title1 : //1階層目に戻る
      menu_off = false;
      findViewById(R.id.stage).clearAnimation(); //これやらんとなぜか変に表示が残る
      findViewById(R.id.stage).setVisibility(View.GONE);
      findViewById(R.id.setting).startAnimation(AnimationUtils.loadAnimation(this, R.anim.menu_in));
      findViewById(R.id.setting).setVisibility(View.VISIBLE);
      findViewById(R.id.mission).startAnimation(AnimationUtils.loadAnimation(this, R.anim.menu_in));
      findViewById(R.id.mission).setVisibility(View.VISIBLE);
      findViewById(R.id.challenge).startAnimation(AnimationUtils.loadAnimation(this, R.anim.menu_in));
      findViewById(R.id.challenge).setVisibility(View.VISIBLE);
      break;
  }
}
メニューを消すときにstartAnimation直後に.setVisibility(View.GONE)をやってしまうと
アニメーションする前に消えてしまうので、AnimationListenerをimplementsして
onAnimationEndで消してます
public class Menu extends Activity implements View.OnClickListener, Animation.AnimationListener {

@Override public void onAnimationStart(Animation animation) { }
@Override public void onAnimationRepeat(Animation animation) { }
@Override public void onAnimationEnd(Animation animation) {
  if (menu_off) {
    findViewById(R.id.setting).setVisibility(View.GONE);
    findViewById(R.id.mission).setVisibility(View.GONE);
    findViewById(R.id.challenge).setVisibility(View.GONE);
  }
}
以上

res/layout/menu.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:background="@drawable/title_back"
 android:gravity="center_horizontal"
 android:orientation="vertical" >
 <ImageView
  android:scaleType="fitStart"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_margin="32dp"
  android:src="@drawable/title" />
 <FrameLayout
  android:layout_width="fill_parent"
  android:layout_height="0dp"
  android:layout_weight="1"
  android:gravity="center">
  <LinearLayout
   android:orientation="vertical"
   android:layout_width="fill_parent"
   android:layout_height="wrap_content"
   android:gravity="center"
   android:layout_gravity="center">
  <ImageButton
   android:id="@+id/setting"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_marginBottom="36dp"
   android:background="@null"
   android:onClick="onClick"
   android:src= "@drawable/menu_sg"/>
  <ImageButton
   android:id="@+id/mission"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_marginBottom="36dp"
   android:background="@null"
   android:onClick="onClick"
   android:src= "@drawable/menu_mi"/>
  <ImageButton
   android:id="@+id/challenge"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_marginBottom="36dp"
   android:background="@null"
   android:onClick="onClick"
   android:src= "@drawable/menu_ch"/>
  </LinearLayout>
  <LinearLayout
   android:id="@+id/stage"
   android:orientation="vertical"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:gravity="center"
   android:visibility="gone">
   <LinearLayout
    android:orientation="horizontal"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal">
    <LinearLayout
     android:id="@+id/stage1"
     android:tag="1"
     android:orientation="vertical"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_margin="12dp"
     android:onClick="onClick"
     android:gravity="center_horizontal">
     <ImageView
      android:scaleType="matrix"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/mission_u"/>
     <TextView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="Stage1"
      android:gravity="center"
      android:textColor="#000"
      android:textSize="12sp"
      android:ellipsize="end"/>
    </LinearLayout>
    <LinearLayout
     android:id="@+id/stage2"
     android:tag="2"
     android:orientation="vertical"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_margin="12dp"
     android:onClick="onClick"
     android:gravity="center_horizontal">
     <ImageView
      android:scaleType="matrix"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/mission_l"/>
     <TextView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="Stage2"
      android:gravity="center"
      android:textColor="#000"
      android:textSize="12sp"
      android:ellipsize="end"/>
    </LinearLayout>
    <LinearLayout
     android:id="@+id/stage3"
     android:tag="3"
     android:orientation="vertical"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_margin="12dp"
     android:onClick="onClick"
     android:gravity="center_horizontal">
     <ImageView
      android:scaleType="matrix"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/mission_u"/>
     <TextView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="Stage3"
      android:gravity="center"
      android:textColor="#000"
      android:textSize="12sp"
      android:ellipsize="end"/>
    </LinearLayout>
   </LinearLayout>
   <LinearLayout
    android:orientation="horizontal"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal">
    <LinearLayout
     android:id="@+id/stage4"
     android:tag="4"
     android:orientation="vertical"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_margin="12dp"
     android:onClick="onClick"
     android:gravity="center_horizontal">
     <ImageView
      android:scaleType="matrix"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/mission_u"/>
     <TextView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="Stage4"
      android:gravity="center"
      android:textColor="#000"
      android:textSize="12sp"
      android:ellipsize="end"/>
    </LinearLayout>
    <LinearLayout
     android:id="@+id/stage5"
     android:tag="5"
     android:orientation="vertical"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_margin="12dp"
     android:onClick="onClick"
     android:gravity="center_horizontal">
     <ImageView
      android:scaleType="matrix"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/mission_u"/>
     <TextView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="Stage5"
      android:gravity="center"
      android:textColor="#000"
      android:textSize="12sp"
      android:ellipsize="end"/>
    </LinearLayout>
    <LinearLayout
     android:id="@+id/stage6"
     android:tag="6"
     android:orientation="vertical"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_margin="12dp"
     android:onClick="onClick"
     android:gravity="center_horizontal">
     <ImageView
      android:scaleType="matrix"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/mission_u"/>
     <TextView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="Stage6"
      android:gravity="center"
      android:textColor="#000"
      android:textSize="12sp"
      android:ellipsize="end"/>
    </LinearLayout>
   </LinearLayout>
   <ImageButton
    android:id="@+id/title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="36dp"
    android:background="@null"
    android:onClick="onClick"
    android:src= "@drawable/menu_tt"/>
  </LinearLayout>
 </FrameLayout>
</LinearLayout>

2015年7月1日水曜日

パッティングゲームの製作(1万件超データの格納方法)

さて、前回は傾斜面を転がるボールが曲がるアルゴリズムを作成しましたが、その中でボール位置の傾斜角度を利用して計算してます
そのためグリーンの傾斜角データを用意してやらねばなりません
とりあえずグリーンの大きさは横10m×縦6mとして、まずは高度を濃度で表現した画像を作成しました

これを縦横共に100分割した各点の高度を求めて、隣接点の高度からXY方向の傾斜角を取得します

こうして取得したXY方向の各点の傾斜角が100×60×2で12000個になりました
この12000個のデータをリアルタイムで利用しなければならんのですが、とりあえずCSVファイルにしてアプリ起動時に読込んでみましたが、読込みが遅い・・・4~5秒ほどかかってしまいます
データベースに入れるのもなんだし、どうしたもんかなぁ悩んでみて、もういっそのこともうベタで書いてみるかとスタティック変数にぶち込んでみました
ひどいですね、良い子はマネしてはいけません
しかしこれは劇的に早くなり、かなりよさそうなんで、もうこれでいっちゃいます

2015年6月16日火曜日

パッティングゲームの製作(斜面でボールが曲がるアルゴリズム)

前回グリーンを転がるボールが止まるアルゴリズムのやったんで、今度は地面の傾斜でボールが曲がるアルゴリズムを作ってみたいと思います
なぜボールが曲がるかっていうと、重力が働いているからですね
図にするとこんな感じ

傾斜角θの時、x方向にg・sinθの力が加わり、ボールが曲がります
これをソースに起こしてみましょう

public class Ball {
    final public static int G = 9800;    //重力加速度(mm/ss)
    final public static float u = 0.075f; //摩擦係数
    public Plot position = new Plot();   //位置
    public int speed; //並進速度(mm/s)
    public double angle; //進行方向(Radian)

    //移動処理(interval:経過時間(ms))
    public void move(long interval) {
        //摩擦による減速処理
        speed -= (u * G * interval) / 1000;
        //速度をXY成分に分割
        int xSpeed = (int)(speed * Math.cos(angle));
        int ySpeed = (int)(speed * Math.sin(angle));
        double xa = getXTilt(prev.x, prev.y); //X方向の傾斜角取得
        double ya = getYTilt(prev.x, prev.y); //Y方向の傾斜角取得
        xSpeed -= (G * Math.sin(xa) * interval) / 1000; //X方向に働く重力による加速
        ySpeed -= (G * Math.sin(ya) * interval) / 1000; //Y方向に働く重力による加速
        angle = getAngle(0f, 0f, xSpeed, ySpeed); //重力により曲がった進行方向を取得
        //XYの速度から位置を更新
        position.x += (xSpeed * interval) / 1000;
        position.y += (ySpeed * interval) / 1000;
    }
}
こんな感じでボールが転がる処理は完成

2015年6月10日水曜日

新作アプリ、パッティングゲームの製作(転がるボールのアルゴリズム)

以前の新作アプリのことを記事で書きましたが、もうだいぶ出来てきました
ベース部分はほぼEasy Basketballなのでかなり楽が出来ましたが、グリーン上を転がるボールのアルゴリズムを一から作る必要がありました
せっかくなので製作過程を書いていきます

まずは任意の初速で放たれたボールがグリーン上を転がり止まるという基本的な動きを考えます
なぜボールが止まるかというと当然地面から受ける摩擦で止まるので、摩擦係数がわかれば転がるボールのアルゴリズムが組み立てられると考えられます

調べてみるとグリーンの速さを測る道具にスティンプメータという滑り台のような道具があるようで、これでボールを射出すると初速1.83m/sで放たれどのくらい転がるかでグリーンの早さを測ることが出来ます
これで測った時の典型的な速さのグリーンの転がる距離が2.286mということなので、これを水平面上を摩擦を受けて並進する物体ということで、運動量保存の法則に入れてみます



m:ボールの重さ
v:初速(m/s)
μ:摩擦係数
g:重力加速度(m/s2)
d:移動距離(m)

ここから摩擦係数μ= 0.075が求められますね

ところが、よく調べてみるとこの計算は大間違いのようで
参考:蜂の子:グリーンの転がり摩擦係数μ
正しくは回転エネルギーを用いて計算せねばならず正しい摩擦係数(転がり摩擦係数)はμ= 0.02989715571ということです
しかしこれを使うとなると移動の計算に角速度を用いねばならず、ちょっとめんどくさいゲーム上では並進速度と摩擦係数μ= 0.075を用いた計算で特に支障はないのでこちらを使うことにします

public class Ball {
    final public static int G = 9800;    //重力加速度(mm/ss)
    final public static float u = 0.075f; //摩擦係数
    public Plot position = new Plot();   //位置
    public int speed; //並進速度(mm/s)
    public double angle; //進行方向(Radian)

    //移動処理(interval:経過時間(ms)
    public void move(long interval) {
        //摩擦による減速処理
        speed -= (u * G * interval) / 1000;
        //速度をXY成分に分割
        int xSpeed = (int)(speed * Math.cos(angle));
        int ySpeed = (int)(speed * Math.sin(angle));
        //XYの速度から位置を更新
        position.x += (xSpeed * interval) / 1000;
        position.y += (ySpeed * interval) / 1000;
    }
}

細かいことを言うとパットにはロフトがついていて打出し時にはボールは少し浮いているので、摩擦を受けない時間があったりするのですが、まあ細かいことはいいそれほどゲーム性に影響はないのでこれでよしとします

2015年6月4日木曜日

PCの突然の死!!!からの~NewPCとともに開発再開

昨年末長年使っていた開発用PCが突然電源がつかなくなってしまいました
まあ相当古いPCだったのでこれを機に新しいPCを組み直すことに
いろいろ考えてるうちに結局3月までかかってしまいました…
ケース以外全とっかえのNewPCの仕様はこちら

種別メーカーパーツ
CPUIntelCore i5 4690
メモリADATADDR3 4G*2
MBASUSH97-PRO
SSDCrucialBX100 256GB
HDDWDWD20EZRX 2TB
電源玄人志向SS600W/85
OSOSWindow7 64bit

そこそこがんばりました
前が前(Core2Duo)だっただけにめっちゃ早く感じます
これで前はきつかったAndroidStudioや重いソフトもキビキビ動くのでいろいろとやれる幅が広がりそうです
休業中に色々とアイデアもたまったので、いろいろと作っていこうかと思います

2014年10月21日火曜日

Android ADT 22.6.0以降はEclipse Heliosでは動かない

いやいやいや、いまだにHeliosHeliosて!
思われるでしょうが
6年物のCore2DuoのPCでがんばってる身としては、なるべく軽くて、枯れた環境でやっていたかったのです・・・

ADT 22.6.0(March 2014) Dependencies:
  • Java 1.6 or higher is required.
  • Eclipse Indigo (Version 3.7.2) or higher is required.
http://developer.android.com/tools/sdk/eclipse-adt.html 

今日からEclipse Keplerでがんばりたいと思います

2014年6月4日水曜日

AndroidでTwitter公式の投稿Activityを取得(新旧バージョン対応)

前回Androidアプリからツイートする4つの方法を紹介しましたが、その中でPackageManagerを利用してピンポイントで公式クライアントアプリを呼び出す方法を紹介しました
しかし、この方法は呼出し時に投稿に使われるActivityまで指定しなければならないため、公式クライアントの更新により変更されてしまった場合使えなくなります
実際過去2回ほどありました
ここではその2回の変更に対応してユーザーの公式クライアントが古いバージョンの場合にも対応できる方法を紹介します

  public static String getTwitterClass(Context context) {
    String twitterClass = null;
    PackageManager pm = context.getPackageManager();
    String packagename = "com.twitter.android";
    String class1 = "com.twitter.android.PostActivity";
    String class2 = "com.twitter.applib.composer.TextFirstComposerActivity";
    String class3 = "com.twitter.android.composer.TextFirstComposerActivity";
    try {
      ActivityInfo[] activities = pm.getPackageInfo(packagename, PackageManager.GET_ACTIVITIES).activities;
      for(int i = 0; i < activities.length; i++) {
        if (activities[i].name.equals(class1)) {
          twitterClass = class1;
          break;
        } else if (activities[i].name.equals(class2)) {
          twitterClass = class2;
          break;
        } else if (activities[i].name.equals(class3)) {
          twitterClass = class3;
          break;
        }
      }
    } catch (PackageManager.NameNotFoundException e) {
      //Twitter is not install
    }
    return twitterClass ;
  }
※Twitter バージョン5.8.0対応

内容はいたって簡単で、パッケージ名は不変なので"com.twitter.android"でGET_ACTIVITIESによりActivityの一覧を取得して、既知の3つのActivity名と照合します
あとはこれで取得したActivity名を
intent.setClassName("com.twitter.android", 取得Activity名);
とセットして明示的インテントで呼べばオッケーです

2014年6月2日月曜日

AndroidアプリからTwitter公式クライアントでツイートする4つの方法

今回EasyBowlingとBowlingScoreBookにSNS投稿機能をつけましたが、BowlingScoreBookでは特に「Twitterで投稿」ボタンを付けました
その時に調べたTwitterの公式クライアントを呼びだしてツイートする4つの方法です
  1. 暗黙的Intentで投稿
  2. Intent.ACTION_SENDで暗黙的Intent呼び出しによる投稿です
    ACTION_SENDと"text/plain"に反応するアプリがすべて出るので公式以外のクライアントアプリ、Twitterに関係のないアプリも出てきます
      public static void tweetFile1(Context context, String message) {
        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setType("text/plain");
        intent.putExtra(Intent.EXTRA_TEXT, message);
        ((Activity)context).startActivity(intent);
      }
    
  3. 公式アプリのPackage、Classを指定して投稿
  4. PackageManagerで公式クライアントをピンポイントで呼び出します
    公式以外のクライアントアプリ以外は反応しません
    公式の投稿用Activityが変わった場合には修正が必要となります(過去2回程変更されている)
      public static void tweetFile2(Context context, String message) {
        String twitterClass = "com.twitter.android.composer.TextFirstComposerActivity";
        try {
          Intent intent = new Intent();
          intent.setAction(Intent.ACTION_SEND);
          intent.setType("text/plain");
          intent.putExtra(Intent.EXTRA_TEXT, message);
          intent.setClassName("com.twitter.android", twitterClass);
          ((Activity)context).startActivity(intent);
        } catch (PackageManager.NameNotFoundException e) {
          //Toast("Not found com.twitter.android")
        }
      }
    
  5. URIスキームを指定して投稿
  6. WEBページからアプリを呼び出すのに使用するURIスキームを利用して呼び出します
    公式以外でも"twitter://"のスキームに対応しているアプリが呼び出されます
      public static void tweetFile3(Context context, String message) {
        String url = "twitter://post?message=" + message;
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setType("text/plain");
        intent.setData(Uri.parse(url));
        ((Activity)context).startActivity(intent);
      }
    
  7. URL(http://twitter.com/)を利用して投稿
  8. 公式クライアントかWEBブラウザが呼び出されます ブラウザの場合、"http://twitter.com/"の投稿画面が呼び出されます
      public static void tweetFile4(Context context, String message) {
        String url = "http://twitter.com/share?text=" + message;
        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
        intent.setType("text/plain");
        intent.setData(Uri.parse(url));
        ((Activity)context).startActivity(intent);
      }
    
このように色々と投稿方法がありますが、どれを採用すべきかは状況によるかと思います
以下に各手法の特徴をまとめてみました
投稿方法互換
クライ
アント
画像
投稿
備 考
1.暗黙的IntentTwitter関連アプリ以外もでる
2.Package指定×公式Twitterクライアントのみでる
3.URIスキーム指定×互換クライアントはスキーム対応アプリのみ
4.URL(HTTP)指定××公式クライアントがなくてもブラウザで投稿できる
※2014/06/02現在

2014年4月28日月曜日

SurfaceViewで点線(破線)を描画する

新作アプリも最終調整段階に来ました
前にテコ入れ編 操作感をなんとかするという記事で操作領域を画面全体にしたことで、シュート方向がわかりにくくなってしまいました
その対策としてボールを引っ張った時のガイド線を入れてみようと思います
今回は簡単にPaint.setPathEffectの機能を使った点線を描画します
ソースはこんな感じ

Paint paint = new Paint();
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(2f);
paint.setColor(Color.BLACK);
paint.setAntiAlias(true);
paint.setPathEffect(new DashPathEffect(new float[]{ 2.0f, 2.0f }, 0));
canvas.drawLine(100, 100, 100, 200, paint);

描画した感じはこれ
これの注意点としてはsetStrokeWidthもそうですが指定がピクセル値ですので、指定値が同じだと端末のdpiによって見え方が変わってしまいますので注意しましょう
と言いつつ今回は方向がわかればいいので気にしない・・・

2014年4月22日火曜日

iOS/Android両対応の開発環境のまとめ

4/22日エンバカデロ・テクノロジーズは「Delphi」「C++Builder」の流れを汲むマルチデバイス対応アプリ開発プラットフォーム「Appmethod」を提供開始したようです
http://codezine.jp/article/detail/7745

いい機会なので iOS/Android両対応の主な開発環境をまとめてみました
色々と選択肢が増えるのは開発者にとってはありがたいことですね

Appmethod
概要:「Delphi」「C++Builder」の流れを汲むマルチデバイス対応アプリ開発プラットフォーム
開発言語:C++, Object Pascal(Delphi言語)
有料/無料
http://www.appmethod.com/jp/

Xamarin
概要:C#言語によるクロスプラットフォーム(iOS/Android)なモバイルアプリケーションの開発環境
Microsoft Visual Studioを利用して開発することが出来る
開発言語:C#
無料
http://xamarin.com/

Adobe AIR
概要:アドビシステムズが開発する複数のオペレーティングシステムに対応したFlashベースのアプリケーション実行環境
開発言語:ActionScript, HTML, JavaScript
有料/無料
http://get.adobe.com/jp/air/

Unity
概要:様々なプラットフォームで動作する高度な 3D アプリケーションを制作することが出来る、統合型のゲーム開発環境
開発言語:C#, JavaScript, Boo
有料/無料
http://japan.unity3d.com/

Cocos2d-X
概要:Zhe Wang氏によって開発され、オープンソースかつ無料の2Dゲームエンジン
開発言語:C++
無料
http://www.cocos2d-x.org/

PhoneGap
概要:Adobe社により公開されているHTML5・CSS・JavaScriptで様々なプラットフォームに対応するアプリケーションを開発するフレームワーク、各デバイスのカメラやGPS,電話帳も利用できる
開発言語:HTML5, CSS, JavaScript
無料
http://phonegap.com/

Titanium Mobile
概要:HTML・JavaScriptを利用してネイティブアプリを作成するオープンソースの開発環境、ネイティブUI,デバイスのカメラやGPS,電話帳も利用できる
開発言語:HTML, CSS, JavaScript
無料
http://www.appcelerator.com/titanium/

monaca
概要:クラウドサービスを利用して、アプリ開発の一連の作業をすべてWebブラウザを通じて行うことができる開発環境
開発言語:HTML5, CSS, JavaScript
有料/無料
http://monaca.mobi/ja/

2014年4月14日月曜日

新作Androidアプリ開発 インスト・即消し対策 トレーニングモードの追加

本業が一息ついて気が抜けてしまいました
いい加減早く新作アプリをリリースしたい

さて、アプリの有効インストール率を上げるためにアプリを消されるのはいつか?ということを考えると一番多いのがインストール直後です
その削除に至る理由としては
  1.  やり方がよくわからない
  2.  どうやればうまくいくのかよくわからない
  3.  しょぼい、おもしろくない
といったところでしょうか

1.に対してはHow toプレイ動画の提示、2.に対してはチュートリアルTipsトレーニングモード等を用意するといいでしょう
3.についてはとにかく企画からデザインまでがんばるしかないと・・・

というわけで2.対策として、トレーニングモードを追加しました
実装はこんな感じ

2014年3月21日金曜日

新作Androidアプリ開発 GUIを盛る編 ゲージのような表示

さて新作アプリの開発ですが、 4回にわたるテコ入れ編でクリティカルな問題に対しての対策が出来ました
あとは完成に向けてひた走るだけ!?
というわけで今回から見た目部分の改良でGUIを盛る編といきたいと思います

まずは右上に表示していた残り時間の表示がスコアの表示と同じじゃわかりにくいし、なんかしょぼいのでもう少し視覚的に残り時間がわかりやすいようにしたいと思います

完成イメージはこう
これをどうやって描画したらいいかなぁと考えました
やり方はいろいろでしょうが今回は丸いゲージの部分の画像をフルで用意して、それを扇形のclipPathでマスクして必要部分だけを描く方法を取りました
パーツを一個用意して回転させながら60個描く方法もありますが、clipPathの方法なら切れ目がないようなアナログ的なゲージにも対応できる思います
コード的には以下のような感じ
    int time; //残り時間
    int rect; //ゲージの大きさのRect
    Bitmap gauge; //フルゲージ画像
    Path clip = new Path();
    clip.moveTo(rect.centerX(), rect.centerY());
    clip.addArc(rect, 267 + ((60 - time) * 6), 360 - ((60 - time) * 6));
    clip.lineTo(rect.centerX(), rect.centerY());
    canvas.save();
    canvas.clipPath(clip);
    gauge.draw(canvas);
    canvas.restore();
このブログで以前、SurfaceViewで描画範囲を限定するでやった方法と同じですね
clipPathは描画したい部分の指定になるので、addArc()の開始角と終了角をずらしながらセットしてやります

わりといい感じにできました

2014年3月6日木曜日

新作Androidアプリ開発 テコ入れ編 操作感をなんとかする

さてテストプレイ編で課題がいくつかあがったので、それらの対策をしていきたいと思います

まずはじめにあがった

1.一番大事な操作感、すべてが小さくて、ちまちましてイライラする

というところ、 ゲームにとって操作は生命線です
操作するだけで楽しい、うまくいくと気持ちがいいという感覚
そういった操作感がゲームの面白さに直結するため非常に重要なところ
今回原因はすべてが小さいところに起因するわけなんで、単純に大きくしてやろうと考えました
グリップを表示して引っ張って放してシュートという方式は直感的でいいのですが、グリップの分どうしても引きしろ範囲を大きくとらねばなりません
なので思い切ってグリップを廃止、画面全体を操作対象にしました

全体的にかなり大きくなったので、操作感は大分良くなりました
ただグリップ式よりも方向が決めにくくなった感があるのでガイドなど表示する必要があるかも

2014年3月3日月曜日

新作Androidアプリ開発 テストプレイ編 作りたいから作った結果がこれ・・・

さて一通りの動きは実装したのでここらでざっくりとテストプレイをしてみたいと思います
本当は誰かほかの人に頼んで意見や感想をもらったりしたほうがいいのでしょうが、なかなか難しいので自分で心を鬼にして厳しいユーザー視点に立ってプレイします
ここでアプリのコンセプト、方向性、課題を厳しくチェックします


んークソゲーか? 
見た感じのしょぼさはまだ仮画像なんでおいといて・・・
ゲームとしてどうなのこれ、成り立ってるのか!?
誰だよこの企画を作ったやつは?オレカ・・・しかもこんなことまで言っている

http://anadreline.blogspot.com/2014/02/blog-post.html

>もしこれが企業での企画なら絶対通らないでしょう
>今更バスケットのシュートゲームかよと一蹴されて終わる
>しかしここが個人開発者の最高なところ
>作りたいから作るのです

作りたいから作った結果がこれか・・・
いやしかしそうだ、作りたいんだった、脳内企画ではもっと面白かったはず
まだどうにかする余地はきっとあると思う
とりあえず現状の問題点を挙げよう
  1. 一番大事な操作感、すべてが小さくて、ちまちましてイライラする
  2. ボールの動きがわかりにくい、特に高低差、ゴールしたのかどうか、たまに変な挙動
  3. 60秒で何ゴールできるかというルールではスコアの頭打ちが早いのではないか
  4. 何度もやりたくなる気がおきない
いろいろあるがクリティカルなところはこんなところか
残念ながらこのままリリースへ向かえるような状態ではない
とりあえずこれらに対して何かしらの解決策を見出して、根本的にてこ入れを行う必要がある

はあ・・・ゲーム作りって難しいですね

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);
}

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