2011年12月12日月曜日

a9padをバージョンアップさせたらoggが再生できくなった

a9padがここに書いてある通り、2.3にバージョンアップできるといういことで、ついついやってしまいました。

動作とかMarketとかは順調なんですが、
今まで動いていた自作のアプリが動かなく。

で、デバッグウインドウで見ると、MediaPlayer.createでnullぽ。

よくわからないんですが、createしようとするものがoggだとnullるっぽいです。
mp3にしてみたら再生出来たので。
でも、SoundPoolでは普通にoggも再生できるのです。

なにこれ。バグ?バグなの?
どうすればいいの??

2011年10月4日火曜日

AndroidでZipInputStream

最近、ColladaをAndroidで表示しようとか頑張ってたんですが(すでに過去形)

Metaseqoiaしかり、Colladaしかり、バイナリじゃなくて、テキストファイルなんですね。

なので、読みやすいという利点はあるんですが、逆に容量が増えがち(アニメーション入れると平気で2~3Mにはなる)
Androidには何気にファイル容量制限があって、機種によっては1Mを超えたくらいでオープンできなくなるし(経験則なので、実際のところは不明)

なので、荒業で「Zip圧縮したファイルを展開しながら読む」って事ができないかなーーと。

JavaにはもともとZipInputStreamという素敵なストリームクラスがあるので、リソース読むにもAssetsから直接読むにも、ZipInputStreamを挟むだけ。

というわけで、Meshクラスのロード部分抜粋
/**
  * Assetsフォルダ内のzipファイルからMeshを作成
  * @param zipName  zip圧縮されたファイル
  * @param loader 使用するローダー
  * @return
  * @throws IOException
  */
 public static Mesh getFromAssetsZip(String zipName,MeshLoader loader) throws IOException{
  int resID = zipName.hashCode();
  //既に読み込まれている
  if (meshMap.containsKey(resID)) {
   return meshMap.get(resID);
  }
  ZipInputStream zis = new ZipInputStream(Global.view.getContext().getAssets().open(zipName));
  zis.getNextEntry(); //これやらないと、ファイルが読み込めないよ。 1zipに複数ファイル入れないでね。
  Mesh newMesh = loader.load(zis);
  meshMap.put(resID, newMesh);
  return newMesh;
 }
 /**
  * リソースに登録されたzipからMeshを作成
  * @param resID  リソースID
  * @param loader 使用するローダー
  * @return
  * @throws IOException
  */
 public static Mesh getResZip(int resID,MeshLoader loader) throws IOException{
  //既に読み込まれている
  if (meshMap.containsKey(resID)) {
   return meshMap.get(resID);
  }
  ZipInputStream zis = new ZipInputStream(Global.view.getResources().openRawResource(resID));
  zis.getNextEntry(); //これやらないと、ファイルが読み込めないよ。 1zipに複数ファイル入れないでね。
  Mesh newMesh = loader.load(zis);
  meshMap.put(resID, newMesh);
  return newMesh;
 }

気をつけるのは getNextEntry(); をやってあげないと、zip内が1ファイルだとしても読み込み準備ができないということ。
ちなみに複数ファイルを圧縮しちゃった場合は知らんぜよ。

これによって、2MぐらいのColladaファイルも圧縮したら200kぐらいになってくれたので、今まで読めなかったファイルも読めるようになったりしました。

いや、もう、Colladaはアニメーションで挫けたので今更ではあるんですが。

2011年9月30日金曜日

Assetsフォルダにアンダーバーから始まるファイルを置いてはいけない

はまった! 超ハマったので、殴り書きだけど記述しておく。

Androidでファイルを直接読み込みたい時にAssetsに入れておいて、

getResources().getAssets().open(filename);

って感じなんだけど、なんと、このfilenameはアンダーバーから始まるファイルはFileNotFoundException が飛んでしまうのだ。 なーーーんじゃそらー!!!!!!!!

アンダーバーから始まるファイルなんて普通じゃねぇか・・。 なんだこのうんこ仕様は・・・。

2011年9月19日月曜日

AndroidGameBaseのソース

ちょいちょいと整形してここに貼り付けてーーーってやってたんですが、複数クラスが絡みあってる部分が多くて無理があるので、全部アップロードしてみました。

本当はgoogle codeとか使えば良いような気もするんだけども。 なんかよくわからなかったので
axfcさんところに。

とーいっても、まだまだ作り途中なんですけど。

しかもコレだけ渡されても使い方分からないかもですが。 まぁ、なんかしらのヒントにはなるのかなーとか。
傾きセンサーあたりがまだ微妙なので修正中です。

それでは、良いandroidライフを。

2011年9月8日木曜日

フィードバックブラー

GREEのスマホゲームで、「ぶっとびハリー」って物理演算系の・・・
なんというか、「ア○グリーバードのパクリ」みたいなゲームがあるんですが、そのゲーム内で
シーンが切り替わる時に、なんかカッコイイエフェクト(前の画面が残像で残る感じ。 きっと加算合成)
ぽいのをやっているのです。

これは、俗にいうフィードバックブラーってやつですな。
いいなぁ。 ローコストでそれなりのエフェクトに見えるなぁ。


・・・・ということで。 DirectXで言うところのRenderTargetの切り替え。 OpenGLだとFrameBufferObjectで、フィードバックブラーのテストをしてみた。


方法としては、1フレーム前の画面キャプチャをちょっと薄くして重ねて、またキャプチャしておくーーってやつです。

ついでに、回転や拡大したものを重ねてあげるようにすれば派手になりますな。 もちろん見づらくなりますが。

ソースについては、また今度ー。

2011年7月13日水曜日

今更法線付けました


キューブを横10縦10奥行き10で1000個描画です(わざとバラつかせている)。

ちなみに、これは以前「弱い」と紹介したa9pad(別名opad renesas、さらに別名 N71 もうわけわからん。)で描画してます。

うーん。そこそこ出ているじゃないか。
と・・・。いうか・・・。

実は何の工夫も無しに負荷テストをやってみると、a9padの方がIconia TAB A500 よりFPSが高かったりします。

しっかり調べてないんですが、想像するにもともとの解像度が高い故に起きているんじゃないかと。
glViewportとかで機種毎の解像度の差を吸収しようとすると、この現象が起きると思う。
例をあげると、FF14ベンチでLow(1280x720)とHigh(1920x1080)をごっちゃにするようなもん。
なので、条件を一致させなきゃベンチマークとしての正当性が無くなっちゃうよなぁ。

解決案としては、同じサイズのFBOを作成して、FBOから取ってきたテクスチャを全画面にして描画・・・かなぁー。

もちろん、FBOを全画面に広げる際のコストは解像度によってかわるけどー。 まぁ、それは誤差レベルだと信じたい。

2011年7月6日水曜日

Iconia A500のスペックテストで3D描画をしてみた。


1体3600ポリ(3角)のグラドリエルたんを25体表示(色はランダムで乗算)テストでFPSは見ての通り13fps。
ふむん。 

3600x25=90000ポリ? いや、でも行列セットのコストとか、カラーリングとか、テクスチャ切り替えとかあるから、単純にそうも言えないんですよね。

まぁ、でも、速い。 僕の持っている端末の中では当然ダントツ。

でも、あんまりスペックの高い端末で開発しても意味ないんだよなぁ。 低スペックの端末を切り捨てる気が無い限りは。

Iconia A500 も購入

というわけで、既にAmazonでポチっていたIconia A500が4日に届きました。 

まぁ、ゲームがプリインストールされてたりするんですが、まったくもってスルーして、PCとデバッグ接続。 俺に開発させてくれぃーーー!

AcerのHPにUSBドライバがあるらしいんですが・・・。 必要なの?例のごとく、今のUSBドライバのinfファイル書き換えるだけで行けるんじゃないの?

っちゅーことで、


android_winusb.inf

[Google.NTx86]
の下に

;Iconia Tab a500
%SingleAdbInterface% = USB_Install, USB\Vid_0502&Pid_3325
%CompositeAdbInterface% = USB_Install, USB\Vid_0502&Pid_3325&MI_01


を入力して、ドライバ当てたらサクっと認識しました。
記念SS

うーん。 しかし、A9Padの出番がほとんどなくなるぞ・・・こりゃ・・・。
まぁ、でもあれだ。 ブルートゥースのテストとかで活躍してもらおう。

2011年6月28日火曜日

a9pad とやらを買ってみたんだけど

正式名称がopad renesas なんだか、a9padなんだかよくわかりませんが、値段とマルチタッチ対応ってところで、開発の手助けになるかなーと思って買ってしまいました。(実はIconiaA500もポチり済みなのに、物欲センサーに負けた・・・)

んで、結果から言えば

「買わなくても良かったんじゃないかな!?」

っていう出来。


いや、普通に使う分にはいいのかもですが、開発機としてはかなり微妙。
一番愕然としたのは、加速度センサーが整数でしか返ってこない事かなぁ。
何の冗談かと思ったけど、本当です。なんか-16~16までの整数が帰ってくるっぽいです。

深度バッファもなんかヤバいのか、まだしっかりテストしてないけど、スカイボックスが表示されないのは何故だろう(その他IS01やLiftTouchNote,MEDIASなどではでてる)

あと、デュアルコアで1Gって書いてあったんだけども、これって、1コアが500GHzで足して1GHzって事なのかなぁ。
なんか弱い。具体的には言えないけど、なんか弱い

そして、メーカーが存在しないので、最新ファームがどこにあるのかさっぱりわからないのも難点です。

まぁ、価格相応っちゃー相応なのかなぁーーーー。

2011年6月20日月曜日

AndroidでMetasequoiaのmqoを読み込むコード その5 MQOLoaderクラス

やっとメタセコファイルを読み込む所です。

とは言っても、テキストを1行づつ解釈していくだけなんですけど。

package com.dividebyzero.KszGameBase;

import java.io.BufferedReader;
import java.util.StringTokenizer;

import android.util.Log;

public class MQOLoader extends MeshLoader{
 private Subset[] subset;
 private int materialCnt;

 @Override
 public Mesh load(BufferedReader in) throws Exception {
  TokenReader tokenReader = new TokenReader(in);
  float v[] = null;
  int isVisible = 1;
  for (String token = tokenReader.getLine(); (token = tokenReader.getString()) != null;) {
   if ("visible".equals(token)) {
    isVisible = tokenReader.getInt();
    Log.v("debug", "visible:"+isVisible);
   }
   if (isVisible != 0) {
    if ("vertex".equals(token)) {
     v = vertexParse(tokenReader);
    } else if ("color".equals(token)) {
     Log.v("debug", "color finded.");
    } else if ("face".equals(token)) {
     faceParse(tokenReader, v);
    } else if ("Material".equals(token)) {
     materialParse(tokenReader);
    }
   }
  }
  //subset配列 で、Meshクラスを作る
  return new Mesh(subset);
 }

 private void materialParse(TokenReader in) throws Exception {
  Log.v("debug", "material finded.");
  String tToken;
  materialCnt = in.getInt() + 1;
  Log.v("debug", "material is " + materialCnt + ".");

  subset = new Subset[materialCnt];
  for (int i = 0; i < materialCnt; i++){
   subset[i] = new Subset();
  }
  tToken = in.getString();
  Subset mat;
  if ("{".equals(tToken)) {
   for (int i = 0; i < materialCnt; i++) {
    Log.v("debug", "material name is " + in.getString() + ".");
    mat = subset[i]; //ローカル化して速度を稼ぐ(雀の涙程度)
    while (!in.empty()) {
     tToken = in.getString();
     if ("tex".equals(tToken)) {
      mat.addTexture(Texture.get(in.getString2()));
     } else if ("col".equals(tToken)) {
      mat.r = in.getFloat();
      mat.g = in.getFloat();
      mat.b = in.getFloat();
      mat.a = in.getFloat();
     } else if ("dif".equals(tToken)) {
      mat.diffuse = in.getFloat();
     } else if ("amb".equals(tToken)) {
      mat.ambient = in.getFloat();
     }
    }
   }
  }
  //マテリアル無し用マテリアル
  mat = subset[materialCnt-1];
  mat.r = 0.5f;
  mat.g = 0.5f;
  mat.b = 0.5f;
  mat.a = 0.5f;
  mat.diffuse = 0.5f;
  mat.ambient = 0.5f;
 }

 private float[] vertexParse(TokenReader in) throws Exception {
  Log.v("debug", "vertex parse start.");
  String tToken;
  float[] tVertices;
  int tNumberOfVertex = in.getInt();
  Log.v("debug", "vertex is " + tNumberOfVertex + ".");
  tVertices = new float[tNumberOfVertex * 3];
  tToken = in.getString();
  if ("{".equals(tToken)) {
   for (int i = 0; i < tNumberOfVertex; i++) {
    tVertices[i * 3 + 0] = in.getFloat();
    tVertices[i * 3 + 1] = -in.getFloat();
    tVertices[i * 3 + 2] = -in.getFloat();
   }
   tToken = in.getString();
  }
  Log.v("debug", "vertex parse end.");
  return tVertices;
 }

 private void faceParse(TokenReader in, float v[]) throws Exception {
  Log.v("debug", "face finded.");

  String tToken;
  int tNumberOfFace = in.getInt();
  Log.v("debug", "face is " + tNumberOfFace + ".");

  tToken = in.getString();
  int vTemp[] = new int[4];
  int matNo = 0;
  if ("{".equals(tToken)) {
   for (int i = 0; i < tNumberOfFace; i++) {
    int vCnt = in.getInt(); //そのfaceが何頂点か。
    boolean isUV = false;
    boolean isMaterial = false;
    while (!in.empty()) {
     tToken = in.getString();
     if ("V".equals(tToken)) {
      for (int j = 0; j < vCnt; j++) {
       vTemp[j] = in.getInt();
      }
     } else if ("M".equals(tToken)) {
      //順番的にMがあとで来てしまうので、頂点をとっておいて、マテリアル番号毎に
      isMaterial = true;
      matNo = in.getInt();

      subset[matNo].addVertex(v[vTemp[2] * 3], v[vTemp[2] * 3 + 1], v[vTemp[2] * 3 + 2]);
      subset[matNo].addVertex(v[vTemp[1] * 3], v[vTemp[1] * 3 + 1], v[vTemp[1] * 3 + 2]);
      subset[matNo].addVertex(v[vTemp[0] * 3], v[vTemp[0] * 3 + 1], v[vTemp[0] * 3 + 2]);
      if (vCnt == 4) {
       subset[matNo].addVertex(v[vTemp[3] * 3], v[vTemp[3] * 3 + 1], v[vTemp[3] * 3 + 2]);
       subset[matNo].addVertex(v[vTemp[2] * 3], v[vTemp[2] * 3 + 1], v[vTemp[2] * 3 + 2]);
       subset[matNo].addVertex(v[vTemp[0] * 3], v[vTemp[0] * 3 + 1], v[vTemp[0] * 3 + 2]);
      }
     } else if ("UV".equals(tToken)) {
      float uvTemp[] = new float[8];
      isUV = true;
      switch (vCnt) {
       case 3: //三角ポリなので、そのまま
        for (int j = 0; j < 6; j++) {
         uvTemp[j] = in.getFloat();
        }
        subset[matNo].addUV(uvTemp[4], uvTemp[5]);
        subset[matNo].addUV(uvTemp[2], uvTemp[3]);
        subset[matNo].addUV(uvTemp[0], uvTemp[1]);
        break;
       case 4://4角ポリなので、分割して放り込む
        for (int j = 0; j < 8; j++) {
         uvTemp[j] = in.getFloat();
        }
        subset[matNo].addUV(uvTemp[4], uvTemp[5]);
        subset[matNo].addUV(uvTemp[2], uvTemp[3]);
        subset[matNo].addUV(uvTemp[0], uvTemp[1]);

        subset[matNo].addUV(uvTemp[6], uvTemp[7]);
        subset[matNo].addUV(uvTemp[4], uvTemp[5]);
        subset[matNo].addUV(uvTemp[0], uvTemp[1]);
        break;
      }
     }
    }
    //マテリアルが当たってなかった
    if(isMaterial == false){
     matNo = materialCnt - 1; //無名マテリアルに当てる

     subset[matNo].addVertex(v[vTemp[2] * 3], v[vTemp[2] * 3 + 1], v[vTemp[2] * 3 + 2]);
     subset[matNo].addVertex(v[vTemp[1] * 3], v[vTemp[1] * 3 + 1], v[vTemp[1] * 3 + 2]);
     subset[matNo].addVertex(v[vTemp[0] * 3], v[vTemp[0] * 3 + 1], v[vTemp[0] * 3 + 2]);
     if (vCnt == 4) {
      subset[matNo].addVertex(v[vTemp[3] * 3], v[vTemp[3] * 3 + 1], v[vTemp[3] * 3 + 2]);
      subset[matNo].addVertex(v[vTemp[2] * 3], v[vTemp[2] * 3 + 1], v[vTemp[2] * 3 + 2]);
      subset[matNo].addVertex(v[vTemp[0] * 3], v[vTemp[0] * 3 + 1], v[vTemp[0] * 3 + 2]);
     }
    }
    //UVが当たってなかった
    if (isUV == false) {
     subset[matNo].addUV(0, 0);
     subset[matNo].addUV(0, 0);
     subset[matNo].addUV(0, 0);
     if (vCnt == 4) {
      subset[matNo].addUV(0, 0);
      subset[matNo].addUV(0, 0);
      subset[matNo].addUV(0, 0);
     }
    }
   }
   tToken = in.getString();
  }
  Log.v("debug", "face parse end.");
  Log.v("debug", "v=" + subset[0].getVCnt());
 }
}

final class TokenReader {
 private BufferedReader mReader;

 private StringTokenizer mStringTokenizer;

 private boolean mIsInitialized;

 public TokenReader(BufferedReader bufferedReader) throws Exception {
  mReader = bufferedReader;
  mIsInitialized = refreshTokenizer();
 }

 private boolean refreshTokenizer() throws Exception {
  String line;
  while ((line = mReader.readLine()) != null)
   if (line.length() != 0 && line.charAt(0) != '#') {
    mStringTokenizer = new StringTokenizer(line, ",; \t()", false);
    return true;
   }
  return false;
 }

 public String next() throws Exception {
  if (mIsInitialized) {
   if (mStringTokenizer.hasMoreTokens()) {
    String token = mStringTokenizer.nextToken();
    return token;
   }
   if (!(mIsInitialized = refreshTokenizer())) return null;
   if (mStringTokenizer.hasMoreTokens()) return mStringTokenizer.nextToken();
  }
  return null;
 }

 public boolean empty() {
  return !mStringTokenizer.hasMoreTokens();
 }

 public float getFloat() throws Exception {
  return Float.parseFloat(next());
 }

 public int getInt() throws Exception {
  return Integer.parseInt(next());
 }

 public String getString() throws Exception {
  return next();
 }

 public String getString2() throws Exception {
  return next().replace('"', ' ').trim();
 }

 public String getLine() throws Exception {
  nextLine();
  return mReader.readLine();
 }

 public void nextLine() {
  while (mStringTokenizer.hasMoreTokens()) {
   mStringTokenizer.nextToken();
  }
 }

 public void close() throws Exception {
  mReader.close();
 }
}
ちゅーか、TokenReaderはどこかから拾った物です・・・。 どこだったかなぁ・・。 肝としては、全て3角ポリに変換するために、4角の面があった場合に
subset[matNo].addVertex(v[vTemp[2] * 3], v[vTemp[2] * 3 + 1], v[vTemp[2] * 3 + 2]);
      subset[matNo].addVertex(v[vTemp[1] * 3], v[vTemp[1] * 3 + 1], v[vTemp[1] * 3 + 2]);
      subset[matNo].addVertex(v[vTemp[0] * 3], v[vTemp[0] * 3 + 1], v[vTemp[0] * 3 + 2]);
      if (vCnt == 4) {
       subset[matNo].addVertex(v[vTemp[3] * 3], v[vTemp[3] * 3 + 1], v[vTemp[3] * 3 + 2]);
       subset[matNo].addVertex(v[vTemp[2] * 3], v[vTemp[2] * 3 + 1], v[vTemp[2] * 3 + 2]);
       subset[matNo].addVertex(v[vTemp[0] * 3], v[vTemp[0] * 3 + 1], v[vTemp[0] * 3 + 2]);
      }

のように、2つの3角ポリに変換をしているところでしょうか。
同じように、UVなんかも割り振ってあげる必要が。

2011年6月7日火曜日

AndroidでMetasequoiaのmqoを読み込むコード その4 Meshクラス

まだ続くのかよって感じですね。 なんでこんなに小出しかって? 仕事中にこっそり書いてるからだよ!!

と、いうわけで、やっとMeshクラスです。 今後メタセコイア以外のファイル形式にも対応するかもなので、先にMeshLoaderなんつー抽象クラスを作っておいて、継承して各種ローダーを作ってもらおうかと。

とりあえず、今あるのはmqoだけ。 (学生が同じ要領でfbxローダーを作ったらしい。未確認)

ともかく、ローダークラス
import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * 抽象クラス 3Dモデルを読み込むためのインタフェース
 * これを軽傷して自前のローダークラスをつくってもらいたい
 * @author ksz
 *
 */
public abstract class MeshLoader {
 public Mesh load(int id) throws Exception {
  BufferedReader in = new BufferedReader(new InputStreamReader(Global.view.getResources().openRawResource(id)));
  return load(in);
 }

 public Mesh load(String name) throws Exception {
  BufferedReader in = new BufferedReader(new InputStreamReader(Global.view.getContext().getAssets().open(name)));
  return load(in);
 }

 /**
  * コレをオーバーライドして、自前のローダー等を作ってください。
  * @param in
  * @return
  * @throws Exception
  */
 abstract public Mesh load(BufferedReader in) throws Exception;
}

このローダークラスを指定して実際にMeshクラスを作る流れ。 拡張子で自動で選ぶとか、クラス名からロードしてプラグインみたいな感じにするとか・・・。 まぁ、夢はひろがリングだけど、そこまでする必要ないかなって気もします。

import java.util.HashMap;

import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;

import android.content.Context;

/**
 * @author ksz
 *
 */
public class Mesh {
 private Vbo[] vbo;
 private Texture[] texs;

 /**
  * 指定されたSubsetの配列からVBOとテクスチャのセットを作成
  * そのままSubsetを使えば良いと思うかもだけど、SubsetクラスのFloarArrayListとかのインスタンスが残るのが嫌だったので。
  * @param subset
  */
 public Mesh(Subset[] subset) {
  vbo = new Vbo[subset.length];
  texs = new Texture[subset.length];
  for (int i = 0; i < subset.length; i++) {
   vbo[i] = subset[i].makeVBO();
   texs[i] = subset[i].getTex();
  }
 }

 /**
  * 全サブセット描画
  * @param x 座標x
  * @param y 座標y
  * @param z 座標z
  * @param param レンダリングパラメーター
  */
 public void render(float x, float y, float z, RenderParam param) {
  GL11 gl = Global.gl;
  gl.glMatrixMode(GL10.GL_MODELVIEW);
  gl.glPushMatrix();
  gl.glTranslatef(x, y, z);
  gl.glRotatef(param.az, 0, 0, 1);
  gl.glRotatef(param.ay, 0, 1, 0);
  gl.glRotatef(param.ax, 1, 0, 0);
  gl.glScalef(param.sx, param.sy, param.sz);

  int len = vbo.length;

  for (int i = 0; i < len; i++) {
   if (texs[i] != null) {
    Texture.setTexture(texs[i].texID);
   } else {
    Texture.setTexture(0);
   }
   vbo[i].draw();
  }
  gl.glMatrixMode(GL10.GL_MODELVIEW);
  gl.glPopMatrix();
 }

 /**
  * 管理用マップ
  */
 public static HashMap meshMap = new HashMap();

 /**
  * 文字列からリソース読み込み
  * @param resName 読み込むリソース名
  * @param loader 使用するローダー
  * @return Meshインスタンス。 すでに読み込まれている場合はそれを返します。
  * @throws Exception
  */
 public static Mesh get(String resName,MeshLoader loader)throws Exception {
  Context context = Global.view.getContext();
  int point = resName.lastIndexOf(".");
  if (point != -1) {
   resName = resName.substring(0, point);
  }
  int id = context.getResources().getIdentifier(resName, "drawable", context.getPackageName());
  return get(id,loader);
 }

 /**
  * 文字列からリソース読み込み
  * @param resID 読み込むリソースのID
  * @param resName 読み込むリソース名
  * @param loader 使用するローダー
  * @return Meshインスタンス。 すでに読み込まれている場合はそれを返します。
  * @throws Exception
  */
 public static Mesh get(int resID,MeshLoader loader)throws Exception {
  //既に読み込まれている
  if (meshMap.containsKey(resID)) {
   return meshMap.get(resID);
  }
  //まだ読み込まれていないので新規で読み込み
  Mesh newMesh = loader.load(resID);
  meshMap.put(resID, newMesh);
  return newMesh;
 }
}

後はローダー部分だけです。
いや、そこがメインなんですけどね・・・。 需要があるのかないのか・・・。

本当はもうプロジェクト毎ザクっとどこかにアップしたいところなんですけど。 bloggerってそういうファイル置き場って無いのかな・・・。

うむん。

2011年6月1日水曜日

AndroidでMetasequoiaのmqoを読み込むコード その3 Subsetクラス

まだまだ続いています。 メタセコファイル読み込み下準備。

頂点配列、UV配列をモデルのファイルから読み込んで管理するために、Subsetクラスを作ってます。
モデルデータをパースする際、頂点数などが先に分かっている場合はいいのですが、それが明記されてないファイル形式も多々あるので、可変長にせざるを得ない。 でも可変長だからといって、単純にArrayListみたいにするとシンタックスシュガーで隠れてはいるけどオートボクシング(裏でラッパークラスへの変換)が行われているはずであってそんなのやってもらっちゃー困る。

ので、FloatArrayListなんつーものも作ってあります。

package com.dividebyzero.KszGameBase;

/**
 * 勝手に作ったFloat型のArrayList
 * ArrayListでも悪くはないけど、オートボクシングがかかって内部でfloat→new Float()がかかってると考えると耐えられないので
 * 作ってみた。効果は不明。
 * @author ksz
 *
 */
class FloatArrayList {
 private int lastIndex;
 private float[] array;
 private static final float[] emptyArray = new float[0];

 public FloatArrayList() {
  this(0);
 }

 public FloatArrayList(int capacity) {
  lastIndex = 0;
  try {
   array = newElementArray(capacity);
  } catch (NegativeArraySizeException e) {
   throw new IllegalArgumentException();
  }
 }

 private float[] newElementArray(int size) {
  if (size == 0) {
   return emptyArray;
  }
  return new float[size];
 }

 public boolean add(float object) {
  if (lastIndex == array.length) { //いっぱいになっちゃった
   int size = size();
   int increment = size / 2;
   if (increment < 12) {
    increment = 12;
   }
   float[] newArray = newElementArray(size + increment);
   if (size > 0) {
    System.arraycopy(array, 0, newArray, 0, size);
    lastIndex = size;
   }
   array = newArray;
  }
  array[lastIndex++] = object;
  return true;
 }

 public float[] toArray() {
  int size = size();
  float[] result = new float[size];
  System.arraycopy(array, 0, result, 0, size);
  return result;
 }

 public float[] getFloatArray() {
  return array;
 }

 public int size() {
  return lastIndex;
 }
}

そして、↑を使ってSubsetクラス。
ただデータを貯めこむのと、そこからVBOを作れるだけ。

/**
 * Meshを作る前段階のクラス。 必要かどうかよくわかんなくなってきた
 * @author ksz
 *
 */
public class Subset {
 private FloatArrayList vertexList = new FloatArrayList();
 private FloatArrayList uvList = new FloatArrayList();
 private Texture tex;
 private int vCnt, uvCnt;
 public float r, g, b, a, diffuse, ambient;

 public Texture getTex() {
  return tex;
 }

 public int getVCnt() {
  return vCnt;
 }

 public int getUVCnt() {
  return uvCnt;
 }

 /**
  * 頂点を格納していく
  * @param x
  * @param y
  * @param z
  */
 public void addVertex(float x, float y, float z) {
  vertexList.add(x);
  vertexList.add(y);
  vertexList.add(z);
  vCnt++;
 }

 /**
  * UV座標の格納
  * @param u
  * @param v
  */
 public void addUV(float u, float v) {
  uvList.add(u);
  uvList.add(v);
  uvCnt++;
 }

 /**
  * 仕様しているテクスチャのセット
  * @param tex
  */
 public void addTexture(Texture tex) {
  this.tex = tex;
 }

 /**
  * セットされた頂点、UVからVBOを作成して返す
  * @return 作成されたVBO
  */
 public Vbo makeVBO() {
  Vbo retVBO = new Vbo();
  retVBO.makeBuffer(vertexList.getFloatArray(), vertexList.size(), uvList.getFloatArray(), uvList.size());
  retVBO.r = r;
  retVBO.g = g;
  retVBO.b = b;
  retVBO.diffuse = diffuse;
  retVBO.ambient = ambient;

  return retVBO;
 }
}
 

ところどころメンバ変数がpublicなのは単純に面倒だったのと、これを直接いじる人もおらんだろうという想像から。 加えて言うならこれをセッターゲッターにしたときのオーバーヘッドを減らしたかった・・・という欺瞞もありで。

2011年5月31日火曜日

AndroidでMetasequoiaのmqoを読み込むコード その2 Textureクラス

大分空きましたが続きー

VBOとTextureをワンセットにすることで、DirectXで言うサブセットみたいな処理ができるので、Textureクラスも当然必要です(テクスチャの切り替えを最小限にするため)

package com.dividebyzero.KszGameBase;

import java.util.HashMap;

import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLUtils;
import android.util.Log;

/**
 * テクスチャクラス
 * @author ksz
 *
 */
public class Texture {
 public int resID, texID, w, h;
 private static final RenderParam param = new RenderParam();

 /**
  * @param resID リソースID
  * @param texID OpenGLでのテクスチャID
  * @param w 横幅
  * @param h 縦幅
  */
 public Texture(int resID, int texID, int w, int h) {
  this.resID = resID;
  this.texID = texID;
  this.w = w;
  this.h = h;
 }

 /**
  * staticのrenderを呼びやすくしただけ
  */
 public void render(float x,float y){
  render(this,x,y,0,param.init());
 }
 
 /**
  * staticのrenderを呼びやすくしただけ
  */
 public void render(float x, float y, RenderParam param) {
  render(this, x, y, 0, param);
 }

 /**
  * staticのrenderを呼びやすくしただけ
  */
 public void render(float x, float y, float z, RenderParam param) {
  render(this, x, y, z, param);
 }

 /**
  * staticのポリラインを呼びやすくしただけ
  */
 public void renderPolyLine(float sx, float sy, float ex, float ey, RenderParam param) {
  renderPolyLine(this, sx, sy, ex, ey, param);
 }

 /**
  * テクスチャのバインド
  */
 public void bind() {
  setTexture(texID);
 }

 //////////////////ここから管理ゾーン
 public static HashMap<Integer, Texture> texMap = new HashMap<:Integer, Texture>();

 /**
  * リソースIDではなく、文字列からテクスチャインスタンスを作成
  * @param resName 読み込みたい文字列 "***.png"のようにしても、拡張子部分は捨てられる
  * @return 作成されたテクスチャインスタンス
  */
 public static Texture get(String resName) {
  Context context = Global.view.getContext();
  int point = resName.lastIndexOf(".");
  if (point != -1) {
   resName = resName.substring(0, point);
  }
  Log.v("debug", resName);
  int id = context.getResources().getIdentifier(resName, "drawable", context.getPackageName());
  return get(id);
 }

 /**
  * リソースIDからテクスチャインスタンスを作成
  * @param resID 読み込みたいリソース
  * @return 作成されたテクスチャインスタンス。既に読み込まれている場合はそのインスタンス
  */
 public static Texture get(int resID) {
  if (texMap.containsKey(resID)) {
   return texMap.get(resID);
  }

  GL10 gl = Global.gl;
  //リソースBMPを読み込む
  BitmapFactory.Options opt = new BitmapFactory.Options();
  opt.inScaled = false; //勝手に拡大縮小するので止める
  Bitmap bitmap = BitmapFactory.decodeResource(Global.view.getResources(), resID, opt);
  //グラボのテクスチャとして登録
  int[] texID = new int[1]; //グラボからテクスチャIDを受け取るための変数
  gl.glGenTextures(1, texID, 0); //グラボから使ってないテクスチャIDを取得
  Texture.setTexture(texID[0]);
  //BMPをテクスチャに割り当てる
  GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
  //テクスチャパラメータをセット
  gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, //拡大の時は
    GL10.GL_LINEAR); //線形補間
  gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, //縮小の時は
    GL10.GL_LINEAR); //線形補間
  gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, //UVがはみ出したときどうしますか
    GL10.GL_CLAMP_TO_EDGE); //端っこで止める
  gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, //UVがはみ出したときどうしますか
    GL10.GL_CLAMP_TO_EDGE); //端っこで止める

  Texture tex = new Texture(resID, texID[0], bitmap.getWidth(), bitmap.getHeight());
  bitmap.recycle();
  texMap.put(resID, tex);
  return tex;
 }

 private static Vbo vbo = null;
 private static int currentTexID = -1;

 /**
  * テクスチャをOpenGLにセット
  * 同じテクスチャが連続でセットされないようにしている
  * @param id セットしたいテクスチャ
  */
 public static void setTexture(int id) {
  if (currentTexID != id) {
   currentTexID = id;
   Global.gl.glBindTexture(GL10.GL_TEXTURE_2D, id);
  }
 }

 /**
  * 指定されたテクスチャを描画 z軸が無いVer
  * @param tex 描画したいテクスチャ
  * @param x 座標x
  * @param y 座標y
  * @param param 描画パラメータ
  */
 public static void render(Texture tex, float x, float y, RenderParam param) {
  render(tex, x, y, 0, param);
 }

 /**
  * 指定されたテクスチャを描画
  * @param tex 描画したいテクスチャ
  * @param x 座標x
  * @param y 座標y
  * @param z 座標z 2Dゲームの場合は0で
  * @param param 描画パラメータ 
  */
 public static void render(Texture tex, float x, float y, float z, RenderParam param) {
  if (vbo == null) {
   //頂点4つ分用意
   float v[] = {
     //頂点
     0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0,
   };
   float t[] = {
     //UV
     0, 0, 0, 1, 1, 0, 1, 1,
   };

   vbo = new Vbo();
   vbo.makeBuffer(v, t);
  }
  vbo.bindVBO();
  GL11 gl = Global.gl;

  setTexture(tex.texID);
  gl.glColor4f(param.r, param.g, param.b, param.a);

  if (param.cw + param.ch < 0) {
   param.cw = (float) tex.w;
   param.ch = (float) tex.h;
  }
  if (param.isCenterOrigin) {
   if (param.isDestsize) {
    param.ox = param.dw / 2;
    param.oy = param.dh / 2;
   } else {
    param.ox = param.cw * Math.abs(param.sx) / 2;
    param.oy = param.ch * Math.abs(param.sy) / 2;
   }
  }
  if (RenderParam.oldAddAlphaMode != param.isAddAlpha) {
   if (param.isAddAlpha) {
    gl.glDisable(GL10.GL_DEPTH_TEST);
    gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
   } else {
    gl.glEnable(GL10.GL_DEPTH_TEST);
    gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
   }
  }
  RenderParam.oldAddAlphaMode = param.isAddAlpha;

  gl.glMatrixMode(GL10.GL_TEXTURE);
  gl.glLoadIdentity();
  gl.glTranslatef(param.cx + param.cw / tex.w * param.xindex, param.cy + param.ch / tex.h * param.yindex, 0);
  gl.glScalef(param.cw / tex.w, param.ch / tex.h, 1);

  gl.glMatrixMode(GL10.GL_MODELVIEW);
  gl.glPushMatrix(); //カメラ行列を退避

  //移動させつつ、原点指定しつつ
  if (param.isDestsize) {
   gl.glTranslatef(0.5f * param.dw - param.ox + x, 0.5f * param.dh - param.oy + y, z);
  } else {
   gl.glTranslatef(0.5f * param.cw * Math.abs(param.sx) - param.ox + x, 0.5f * param.ch * Math.abs(param.sy)
     - param.oy + y, z);
  }
  gl.glRotatef(param.ax, 0, 0, 1); //回転(なぜかラジアンじゃない)
  if (param.isDestsize) {
   gl.glScalef(param.dw, param.dh, 1); //拡大縮小
  } else {
   gl.glScalef(param.sx * param.cw, param.sy * param.ch, 1); //拡大縮小
  }
  gl.glTranslatef(-0.5f, -0.5f, 0); //中心を原点に回転するための小細工

  gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4); //横1縦1の頂点描画

  gl.glPopMatrix(); //カメラ行列を戻す
 }

 /**
  * ポリライン(テクスチャを線のように描画)
  * @param tex 描画したいテクスチャ
  * @param sx 始点x
  * @param sy 始点y
  * @param ex 終点x
  * @param ey 終点y
  * @param param パラメータ、幅など
  */
 void renderPolyLine(Texture tex, float sx, float sy, float ex, float ey, RenderParam param) {
  Vec3 tang = new Vec3(ex - sx, ey - sy, 0);
  float angle = (float) Math.atan2(tang.y, tang.x) * Util.TOANGLE;
  param.setDestSize(Math.abs(tang.getLength()), Math.abs(param.sx)).setAngle(angle);
  param.setCenterOrigin(true);
  render(tex, sx + tang.x / 2, sy + tang.y / 2, param);
 }

 /**
  * 指定されたリソースを描画 
  * 指定されたリソースがまだ読み込まれていない場合は読み込まれる
  * @param resID R.drawable.iconなど、リソースのIDを直指定
  * @param x 描画位置x
  * @param y 描画位置y
  * @param param RenderParamのインスタンスによって描画パラメータを操作
  */
 public static void render(int resID, int x, int y, RenderParam param) {
  render(get(resID), x, y, param);
 }

 /**
  * 管理されているテクスチャを全て開放
  */
 public static void release() {
  int textureID[] = new int[1];
  for(Texture tex:texMap.values()){
   textureID[0] = tex.texID;
   Global.gl.glDeleteTextures(1, textureID, 0);
  }
  texMap.clear();
  texMap = null;  
 }

 /**
  * 画面クリア処理
  * Textureクラスに入れるのが適当がどうかは微妙なところ
  * 深度を有効化しないと、深度バッファのクリアがうまくいかない場合があるようなので、毎回入れてます。 負荷だけど。
  */
 public static void clear() {  
  Global.gl.glEnable(GL10.GL_DEPTH_TEST);
  Global.gl.glDepthMask(true); // 深度バッファ書き込み有効
  Global.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
 }

 /**
  * 画面をクリアします
  * @param r 赤(0.0f~1.0f)
  * @param g 緑(0.0f~1.0f)
  * @param b 青(0.0f~1.0f)
  * @param a アルファ値(0.0f~1.0f)
  */
 public static void clear(float r,float g,float b,float a) {
  Global.gl.glClearColor(r,g,b,a);
  clear();
 }
 /**
  * 画面をクリアします
  * @param r 赤(0~255)
  * @param g 緑(0~255)
  * @param b 青(0~255)
  * @param a アルファ値(0~255)
  */
 public static void clear(int r,int g,int b,int a) {
  Global.gl.glClearColor(r/255.0f,g/255.0f,b/255.0f, a / 255.0f);
  clear();
 }
}

しかし、ソースのほとんどが2D用だっていうのは僕と君だけの秘密だぞ。

2011年5月25日水曜日

AndroidでMetasequoiaのmqoを読み込むコード その1 VBO

以前書いた記事でmqo読んでるぜーって書いたらなんだか奇特な方がソースを晒してくれってな事だったので、ざっと。

でも、結構絡み合ってるんですよ? これが?

まず、VertexObjectで描画が基本なので、VBOクラスから。

import java.nio.FloatBuffer;
import java.util.ArrayList;

import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;

/**
 * VertexBufferObjectクラス
 * @author ksz
 */
public class Vbo {
 public float r=1,g=1,b=1,a=1,diffuse=1,ambient=1;
 public int vSize, tSize;
 private int vboID = -1;
 private static int oldID = -1;

 /**
  * GLにVBOをセット
  */
 public void bindVBO() {
  if (oldID == vboID) return; //既に同じバッファがセットされていたら無視
  if (vboID < 0) return; //makeBufferされてないようなので無視

  oldID = vboID;
  GL11 gl = Global.gl;
  gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, vboID);
  gl.glVertexPointer(3, GL11.GL_FLOAT, 0, 0); //頂点バッファ

  if (tSize != 0) {
   gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, vSize); //UVバッファ
  }
 }

 /**
  * VBOを描画します
  */
 public void draw() {
  GL11 gl = Global.gl;
  
  //テクスチャ行列を外す
  gl.glMatrixMode(GL10.GL_TEXTURE);
  gl.glLoadIdentity();

  gl.glColor4f(r, g, b, a);

  bindVBO();
  
  //vSizeは頂点配列のバイト数なので、floatにするのに/4 TRIANGLESなので3角ポリで/3 で/12
  gl.glDrawArrays(GL10.GL_TRIANGLES, 0, vSize / 12);
 }

 /**
  * 頂点とUVの配列からVBO作成
  * @param v 頂点配列
  * @param t UV配列
  */
 public void makeBuffer(float v[], float t[]) {
  vSize = v.length;
  tSize = 0;
  if (t != null && t.length != 0) {
   tSize = t.length;
  }
  makeBuffer(v,vSize, t,tSize);  
 }

 /**
  * 頂点とUVの配列からVBO作成(サイズ指定版)
  * @param v  頂点配列
  * @param vCnt 頂点配列の有効要素数
  * @param t  UV配列
  * @param tCnt UV配列の有効要素数
  */
 public void makeBuffer(float v[],int vCnt, float t[],int tCnt) {
  GL11 gl = Global.gl;
  vSize = vCnt * 4;
  tSize = tCnt * 4;

  int vbo[] = new int[1];
  gl.glGenBuffers(1, vbo, 0);
  gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, vbo[0]);
  gl.glBufferData(GL11.GL_ARRAY_BUFFER, vSize + tSize, null, GL11.GL_STATIC_DRAW);
  gl.glBufferSubData(GL11.GL_ARRAY_BUFFER, 0, vSize, FloatBuffer.wrap(v));
  if (tSize != 0) {
   gl.glBufferSubData(GL11.GL_ARRAY_BUFFER, vSize, tSize, FloatBuffer.wrap(t));
  }
  vboID = vbo[0];
 }
}

ちなみに、ライティングは無視してるので、勝手に拡張してね。
Global.gl とかがさも当然のように使われてますが、GL11インスタンスはいろんなところから使うので、グローバルっぽく使うためにpublic static な領域にGl11インスタンスを渡してある前提で。

まだまだ続きます。

2011年5月19日木曜日

ARの前にカメラのプレビュー画像とOpenGLの画像を重ねてみた


カメラ用のSurfaceView作って、FrameLayoutでOpenGLのViewと重ねるだけじゃん? って思ってたら結構はまりました。

いろんなところですでに言われてますが、

①普通に考えるとカメラの画像にOpenGLを重ねるので、
カメラビューの追加 → OpenGLビューの追加
となると思いきや、
OpenGLビューの追加 → カメラビューの追加
にしないといけない罠
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  FrameLayout layout = new FrameLayout(this);
  setContentView(layout);  
  layout.addView(new GameView(this, new OpeningScene(), 640, 480));
  layout.addView(new CameraSurfaceView(this));
 }

②OpenGLの方のピクセルフォーマットを透過にしなければいけない罠+ChoosConfigの時にEGL_ALPHA_SIZEを指定しないと機種によっては表示がバグる罠。
※でもなんか、PixelFormat.RGBA_8888でも大丈夫だった、うん?
  getHolder().setFormat(PixelFormat.RGBA_8888);
  getHolder().setType(SurfaceHolder.SURFACE_TYPE_GPU);
  getHolder().addCallback(this);
  int[] spec = {
   EGL10.EGL_ALPHA_SIZE,1,
   EGL10.EGL_DEPTH_SIZE,1,
   EGL10.EGL_NONE
  }; 
  EGLConfig[] configs = new EGLConfig[1];
  egl.eglChooseConfig(display, spec, configs, 1, new int[1]);

③glClearColorのアルファの指定を1以外にしないとカメラビューが映らない罠
 public static void clear() {  
  Global.gl.glClearColor(1,1,1,0);
  Global.gl.glEnable(GL10.GL_DEPTH_TEST);
  Global.gl.glDepthMask(true); // 深度バッファ書き込み有効
  Global.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
 }

ARとか程遠い。

2011年5月12日木曜日

縦画面対応


ちなみに前回書き忘れましたがモデルはココのをお借りしております。

2D画像の板ポリを球っぽく並べたまではいいんですが、実はこいつらビルボード化してないのですよな。
ちゅーか、ビルボードの為の計算量がもったいないっちゅーか。 ポイントスプライトってないのかなぁ。

2011年5月6日金曜日

Androidでメタセコイアのmqoファイルを読み込んでみるテスト

エミュレータではとてもじゃないけど開発する気にならない3Dですが、実機(IS01・Life Touch Note)ではサクサク動くので、3Dにも手を出してみることに。

最初はXファイル読み込ませようかとも思ったんですが、あのフォーマットは手に負えないのでメタセコイア(Metasequoia)のtxtのmqoファイルを読み込ます事に。
理論上は完璧だと思ってたら、穴が空きまくったり(DEPTH_SIZEが問題だった)
2Dと両用を考えてたんだけど(3Dだけど、スコア表示は2Dなど)Lief Touch Noteの射影マトリクスのスタックの数が1か2ぐらいしかなくて、glPushMatrix()・glPopMatrix()が役立たずだったりしましたとさ。

ソースコードはもうちょっとリファクタリングしたら晒したいなぁ。

2011年4月25日月曜日

LifeTouchNote買いました

でも、感圧なのでせっかくAndroid2.2なのにマルチタッチが出来ない罠

試しに弾幕作ってみましたが、30FPSぐらいは出る様子。

操作をどうしたもんか・・・。

2011年4月14日木曜日

Garaxy S でOpenGLを使おうとしたらegl_bad_surfaceで怒られる罠

タイトルの通りですが、学生のGaraxySでOpenGLのテストをしてもらおうとしたら、画面の背景色クリアすら行かない状態。

よーくログを見ると、EGL_BAD_SURFACEとか言われてるわけです。

で、調べてみてもよくわからないんですよね。 これまた日本語で見つかる記事は無いし。
読めない英語を頑張って読んでいたら、大分前にこんなのが

ようするに、OpenGLの初期化の段階で

  int[] spec = new int[] {
   EGL10.EGL_RED_SIZE, 5, //赤
   EGL10.EGL_GREEN_SIZE, 5, //青
   EGL10.EGL_BLUE_SIZE, 5, //緑
   EGL10.EGL_DEPTH_SIZE, 1, //深度
   EGL10.EGL_NONE
  };
  EGLConfig[] configs = new EGLConfig[1];
  egl.eglChooseConfig(display, spec, configs, 1, new int[1]);

ってやってる、このRGBのbit指定がダメだ。と。
エミュレータはそこらへんなんか適当なのになぁ。へ・・・へぇーーー。

でも、そのビット指定を外すと

  int[] spec = new int[] {
   EGL10.EGL_NONE
  };

こんな情けない配列になりますけど。 良いですか? 良いんです!!!

でも、こんなんで悩んでる人は今はあまりいないっぽい。 多分だけど、みんなGLSurfaceViewの恩恵に預っているのだろう。

じゃぁなんでGLSurfaceView使わないかって? だってあのGLThreadってFPS指定できないでしょ。多分。

あと、描画メソッドの中で更新もしなきゃならなさそうだし(別スレッドでやる方法もあるんだろうけど、synchronizedで同期とるとかなんか嫌。)
更新→描画→更新 のちゃんとしたゲームループがほしいんだあ。

2011年2月2日水曜日

ようやく仕事が落ち着きそうなので

なんかアンドロイドでゲームでも作りたいもんです。

案1 モザイクブロック崩し
モザイクアプリを以前作ってみたので、それを活かして、なんかエロいようでエロくない微妙な絵をモザイク化して、そのモザイクを破壊していくブロック崩し。
普通のブロック崩しと違うのは、色によってブロックへの衝撃が隣接ブロックにもつながる事。 似た色だとブロックへのダメージがあまり軽減せずに突き抜ける。 逆に全然色が違う場合はほぼ通らない。
こうすれば、「元絵」がそのまま難易度になるんじゃないかなぁ。と。

案2 麻雀パズルゲーム
僕はなんだかんだで麻雀大好きで。 Physx麻雀の作者でもあるわけなんですが・・・。
さすがにAndroidで3D+物理演算とかやりたくない。 というか、IS01では動かなそうなので、発想を変えて、積み上がった麻雀牌をドラッグしていって、面子を作り、手配にスタック。 14枚集まった時点で役判定、点数がそのまま持ち点に加算。もちろん、役が無い場合はフリテンで-8000点 上まで牌がつまったらゲームオーバー。

案3 リアルタイムオセロ
対人戦。 プレイヤーあたりの持ち秒数(例えば3秒)が決まっていて、その間であれば何個でも自分の石を置ける。ただし、挟まれた石は一気に変わるのではなく、置いた石を基点に一つづつゆっくりとひっくりがえっていくので、アクティブに取り返す事が可能。 CPU作るのが大変そう。

どれ作ろうかなぁ。

2011年1月7日金曜日

明けましておめでとう

ございます。
もう1月も7日ですが。

今年の抱負はニコニコでandroid開発の生放送してみたいなとか思ってます。

なんと、今日にでも達成できそうな抱負ですが、個人的にはハードルが高いです。
なんか恥ずいよ。