素人のウィルコム用Javaアプリ制作の記録です
このブログの目次

スペシャルサンクス

 

制作協力のお礼

 

f:id:kikyou_kiki:20180518003048p:plain
ばななん。さんに特殊タイトル画面イラストを描いていただきました。
ゲームに使用するイラストを他人に依頼することは初めての経験でしたので、大変勉強になりました。

依頼の仕方などで相手と文字でやりとりするにあたって、相手にどのような文章にして自分の要件を伝えることができるか、などを考えるきっかけになりました。

saraemi.com

キャラクターの設定資料などを用意していなかった中、(あの後ろ姿三枚のタイトル差分くらい)、今まで後ろ姿を向いていたキャラクターがこちらを向いて笑顔を見せるという場面を表現していただきました。

 

このブログを作った理由の一つに、この素晴らしいイラストがプレイヤーの限られるアプリ限定になってしまうのが勿体なかったというがあります。

ばななん。様へ制作に快く協力していただいたことを、この場にて感謝申し上げます。

 

使用した素材など

 

音楽:童謡「てるてる坊主」(MIDI)

音楽研究所 様

 

効果音:ゲーム用効果音「reflect」(wav)

TAM Music Factory 様

 

ゲームのBGM、SEに使用しました。

童謡てるてる坊主の童謡風、ハープアレンジをステージBGMとゲームオーバーBGMに使用。

かすり時のSEにはreflectを使用しています。

音楽ファイルの使用

この記事では、音楽ファイルの使用方法について解説します

基本となりそうな部分について。

変数宣言

Player player=null;//プレイヤーの作成
String ss=null; //ファイル名

Player型変数playerを宣言

サウンドファイルの読み込み

try {
	InputStream in=getClass().getResourceAsStream(ss);
	player=Manager.createPlayer(in,"audio/midi");
}catch (Exception e) {
	e.printStackTrace();
}

再生

try{  
	player.start();
}catch (Exception e) {
	e.printStackTrace();
}

InputStreamでファイルを入力。
Manager.createPlayerでplayerにファイルを渡す。
player.start()やplayer.stop()で再生停止ができます。

SEの使用について 補足

f:id:kikyou_kiki:20180504165010p:plain
端末上でSEを使用するには、wav(量子化ビット数2/4bit サンプリング周波数8/16/32kHz)に変換する必要があるそうなのですが、自分が見つけた変換ソフトのほとんどがWindows10に対応していない模様?よくXP環境で挙げられているサウンドレコーダーも見つからない。
とりあえず仕方ないので現状のwavファイルのままで完成ビルドにいれています。WX402KCではSEが再生されないという状況ですが、もし他の機種で再生されたなどありましたら、情報提供していただけると幸いです。

グラフィックの使用

この記事では、ゲームに不可欠なグラフィックの利用方法をまとめます。

Canvasへの描画

サンプル、GraphicsSampleCanvas.javaが参考になります。
paintメソッド内

色指定方法

        g.setColor(255,255,255); //RGB指定

ラインの描画

        g.drawLine(10,30,50,30); //原点x,y、終点x,y

        //破線の描画
        g.setStrokeStyle(Graphics.DOTTED); //スタイルに破線を設定
        g.drawLine(60,30,100,30);                    //このdrawLineは破線が描画される
        g.setStrokeStyle(Graphics.SOLID);       //スタイルを戻す

四角形の描画

        g.drawRect(10,60,40,40);    //原点x,y、幅、高さ
        g.fillRect(60,60,40,40);        //四角形の塗り潰し

        g.fillRect(0,0,getWidth(),getHeight());        //背景の塗り潰し
        //getWidthとgetHeightで画面サイズを取得できるので、そのサイズで塗りつぶしている

        //角丸四角形の描画
        g.drawRoundRect(10,110,40,40,20,20); // 原点x,y、幅、高さ、弧の幅、弧の高さ
        g.fillRoundRect(60,110,40,40,20,20);        //角丸四角形の塗り潰し

円弧の描画

        g.drawArc(10,160,40,40,0,270); // 原点x,y、直径x,y、開始角度、弧の角度
        g.fillArc(60,160,40,40,0,270);        //円弧の塗り潰し
        // 角度の単位は度(degree)

三角形の描画

        //三角形の塗り潰し
        g.fillTriangle(10,250,35,210,60,250); //三角形の頂点をx,y3つ指定

javaアプレットのpaintを使用したことがあれば、同じような感覚で使用できるでしょう。

画像の利用

f:id:kikyou_kiki:20180504203610p:plain
ゲーム画面内のドット絵や背景は自分で描いたものです。(過去のリソースそのまま)

画像を表示させるだけならばImageメソッドで十分可能ですが、ここではSpriteメソッドを使用して、画像を表示させていきます。

SpriteにはImageにはない便利な機能があるので、活用することによって画像容量&ファイル数削減に役立てることができます。
グラフィックや音楽などのリソースはプロジェクトフォルダのresフォルダに保存すること。

読み込み

Image im_title;
im_title = Image.createImage("/teruri.png");
Sprite title = new Sprite(im_title, 250, 250);

画像のフレーム指定

title.setFrame(0); //x=0-250,y=0-250の範囲をセット
title.setFrame(1); //x=250-500,y=0-250の範囲をセット
title.paint(g);       //(1)のみ描画される
title.setFrame(0); //x=0-250,y=0-250の範囲をセット
title.paint(g);       //(0)描画
title.setFrame(1); //x=250-500,y=0-250の範囲をセット
title.paint(g);       //(1)描画

Imageクラスで読み込んだ画像をSpriteに指定します。Spriteでは反転やフレームを指定できるのでアニメーションに便利。
一つの画像にまとめることによって読み込み回数を減らすことができ、その点でも有利(自分はそうしてないけれども
この機能を使用して、タイトル画面を表示させています。

 f:id:kikyou_kiki:20180504203501p:plain
タイトル画面は差分となっていて、順次重ねていくことによって変化をつけています。

f:id:kikyou_kiki:20180518002139p:plain
例えば、クリア1回目はこうなる 

画像の反転

f:id:kikyou_kiki:20180504203539p:plain
プレイヤーはこの画像を半分に分割したものを反転を利用して表示しています。

drawGamePlayerクラス

if(player.getdir() == 1){
	p.setTransform(2);
	p.setFrame(1);
}else if(player.getdir() == -1){
	p.setTransform(0);
	p.setFrame(1);
}else{
	p.setFrame(0);
}
/*
setTransform(0):そのままの画像。TRANS_NONE
setTransform(2):左右反転した画像。TRANS_MIRROR
*/

プレイヤーが移動している場合、移動方向を取得し、frame(1)を移動方向に応じて反転したものをセットしています。

詳しくは、リファレンスのSpriteの項に色々載ってます:Sprite (Unofficial 'CLDC 1.1 + MIDP 2.0' API Reference.)

まずはサンプルを実機で動作させてみる

さて、開発の必須ツールであるj2meを起動するとこんな画面が表示されます。
f:id:kikyou_kiki:20180505211057p:plain
メニューとコンソールだけのシンプルな画面構成。コンソール部にエラー内容が出力されます。
では、プロジェクトを作成していきます

新規プロジェクトを作成

新規プロジェクト作成を押します。
f:id:kikyou_kiki:20180505211201p:plain
クラス名とクラス名を入力します。
f:id:kikyou_kiki:20180505211237p:plain
API設定では、
kikyou-m.hatenablog.com
の項で確認した項目を設定していきます。
402KCの場合は、JTWI+CLDC1.1を選択し了解します。作成するプログラムや動作させる機種に合わせて他は設定します。(今回は特に弄らなくても実機動作します)
ここの設定を間違えると、パッケージされたファイルが実機でのインストール時に弾かれたりする現象が発生したりします。

作業フォルダをエクスプローラでさがす
f:id:kikyou_kiki:20180505212208p:plain
C\Users\(アカウントのユーザー名)\j2mewtk\2.5.2\apps\(プロジェクト名)に作業フォルダが作られるようです。
ここのフォルダsrc内にファイルを作成し編集していきます。
ここでは、公式サイトからダウンロードしたサンプルFontSample.javaを動作させます。
FontSample.javaとFontSampleCanvas.javaを作業フォルダにコピーし、エディタで編集します。*1
たとえば、クラス名がtestの場合このように変更。

FontSample.java 4~13行目

/** FontSample*/
public class test extends MIDlet { //ここのFontSampleを新規プロジェクト作成時設定したクラス名に変更
	static MIDlet midlet;

    /** コンストラクタ */
    public test() { //ここも同じく
		midlet =this;
        Display.getDisplay(this).setCurrent(new FontSampleCanvas());
    }

FontSampleCanvas.java 60~65行目

	public void commandAction(Command c, Displayable d) 
	{
		if (c == exit){
			test.midlet.notifyDestroyed(); //ここも同じく
		}
	}

編集が終わったら上書き保存してj2meに戻り、ビルド→エラーがなければ実行ボタンを押します。
f:id:kikyou_kiki:20180505215048p:plainf:id:kikyou_kiki:20180505215054p:plain
決定ボタンで(プロジェクト名)を選ぶと実行できます。

パッケージの作成

実機で動作させるためのパッケージを生成します。
f:id:kikyou_kiki:20180505220015p:plain
パッケージ化に成功したら、先ほどの作業フォルダ下、\bin内にjarとjadが生成されます。
f:id:kikyou_kiki:20180505220513p:plain
このjarかjadのどちらかを携帯端末に転送します。

Webページから転送する方法もありますが、今回はSDカードを使用します。
携帯上でフォーマットしたSDカードの、
(SDカードのドライブ):\PRIVATE\KYOCERA\DATA(402KCの場合。機種によって差異があるかも)
にjarかjadをコピーします。(USBマスストレージでも可)
f:id:kikyou_kiki:20180505222320j:plain
データフォルダ→microSDデータからコピーしたファイルを探す。
f:id:kikyou_kiki:20180505222331j:plainf:id:kikyou_kiki:20180505222348j:plain
インストールして起動する

プロジェクトの読み込み

j2meを起動し、プロジェクトを開く、を押すとこのような画面が出ます。
f:id:kikyou_kiki:20180506235358p:plain
プロジェクト名を選択し、プロジェクトを開く。
作業フォルダのファイルを開き、編集を再開します。

エミュや実機上で無事に新規プログラムを動作させる手順をつかめたら、あとはソースの編集→ビルド(エラーが出たらデバッグ)を繰り返してゲームを作ります。

*1:プロジェクト新規作成時、クラス名をFontSampleにしていた場合この作業は必要ない

コード解説(メソッド、クラス編)

このブログではゲーム制作自体の解説よりHowto的な部分をまとめることを主旨としていますが、整理の意味も兼ねてまとめておきます。

関数
case 1内

drawTitle(g, font, largefont, title, high_score, clear);
//タイトル画面描画メソッド タイトル画面で表示するフォント、スプライト、ハイスコア
//タイトル画面変更制御のフラグ(クリア回数)を入力

case 2内

player.PlayerControl(FIRE_PRESSED &keyState);
//ボタン進行判定
player.spacehit();
//移動制限
GameisHit(ame, am, player, p, s, am_cl, p_cl, s.getAmest());
//当たり判定 プレイヤーとオブジェクトの座標を入力
player.inv_check();
//無敵継続処理
s.incProgress();
//進行増加
s.count_up();
//カウンタ増加

GameisHit以外はクラスメソッドとなっている。

drawGameBackground(g, bg);
//背景描画
drawGamePlayer(g, player, p, s.getFramecount());
//プレイヤー描画 Framecountはダメージ受けた時の点滅描画のために使用。
drawGameMoveObj(g, ame, am, s.getAmest());
//オブジェクト描画 オブジェクト座標とスプライト情報とオブジェクト生存状況を入力
drawGameUI(g, font, largefont, s.getScore(), player.getlife(), player.getGraze(), s, g1);
//UI描画 フォント情報とスコア、ライフ、かすり、進行状況、四角の情報を入力

画像の位置関係の問題があるので上に表示したいものを最後に描画

case 3内

drawGameOver(g, font, gover, s.getScore());
//画面描画メソッド ゲームオーバー画面で表示するフォント、スプライト情報、スコア

case 4内

drawClearStage(g, font, s.getScore(), player.getlife(), bg);
//画面描画メソッド クリア画面で表示するフォント、スコア、ライフ、背景

case 5内

Instraction(g, font);
//draw抜けてるけど描画メソッド フォント情報

TeruTeruCanvas.java内Object_Ameクラス

	//雨粒オブジェクトクラス
	class Object_Ame {
		//雨粒初期位置
		private int x;
		private int y;
		//雨粒画像サイズ 現状は直入力
		private int ame_width;
		private int ame_height;
		//雨粒移動距離
		private int dy;
		//画面内生存判定
		private boolean suv;
		
		private int rnd;
		
		//コンストラクタ
		Object_Ame(){
			Set();
			this.ame_width = 32;
			this.ame_height = 5;
			this.dy = 5;
			this.suv = true;
		}
		
		int getX(){return x;}
		int getY(){return y;}
		int getWidth(){return ame_width;}
		int getHeight(){return ame_height;}
		boolean getState(){return suv;}
		
		//xy軸決定
		void Set(){
			setX(randX());
			setY(randY());
		}
		void setX(int setx){
			x = setx;
		}
		void setY(int sety){
			y = sety;
		}
		
		//ランダム生成
		int randX(){
			int field_width = display_width/10;
			rnd = random.nextInt(display_width) - field_width - ame_width*2;
			if(rnd < 0) randX();
			return rnd;
		}
		int randY(){
			int field_height = display_height;
			rnd = (random.nextInt(display_height) - field_height) + 50;
			//if(rnd < 0) randY();
			return rnd;
		}
		
		//dy方向移動
		void MoveAme(){
			y += dy;
		}
		
		//死亡判定
		void deadState(){
			suv = false;
		}
	}

落ちてくるオブジェクトの座標の生成、移動、生存の情報を持っているクラス。オブジェクトを生成するとランダムで座標を決定します。

TeruTeruCanvas.java内Object_Playerクラス

    class Object_Player {
		private int life;
		private int p_width; //プレイヤースプライトサイズ
		//プレイヤー初期位置
		private int x;
		private int y;
		//移動速度
		private int dir; //移動方向
		private int dx; //通常移動速度
		//無敵時間
		private int inv; //無敵時間
		private int inv_f; //無敵経過時間
		//接近フラグ
		private boolean close;
		//点滅管理用
		private int visflag;
		
		Object_Player(){
			this.life = 4;
			this.p_width = 40;
			this.x = 0;
			this.y = display_height * 4/ 5;
			this.dir = 0; //移動方向
			this.dx = 4; //通常移動速度
			this.inv = 25; //無敵時間
			this.inv_f = 0; //無敵経過時間
			this.visflag = 0;
			this.close = false;
		}
		int getX(){return x;}
		int getY(){return y;}
		int getdir(){return dir;}
		int getinv(){return inv;}
		int getinv_f(){return inv_f;}
		int getlife(){return life;}
		int getvisflag(){return visflag;}
		boolean getclose(){return close;}
		
		void PlayerControl(int key){
			//ボタン進行判定
			if (key != 0){
				setdir(1);
			}else if(getX() > 0){
				setdir(-1);
			}else{
				setdir(0);
			}
			PlayerMove();
		}
		
		void PlayerMove(){
			x += dx * dir;
		}
		
		void setdir(int setDir){
			dir = setDir;
		}
		void setinv_f(int sett){
			inv_f = sett;
		}
		
		void inv_check(){
			if(inv_f >= inv){
				setinv_f(0);
			}else if(isinv()){
				inv_f++;
			}
		}
		boolean isinv(){
			if(inv_f >= 1 & inv_f < inv){
				return true;
			}
			return false;
		}
		
		void lifedown(){
			life--;
		}
		
		int switchvisflag(){
			if(visflag == 0){
				visflag = 1;
			}else if(visflag == 1){
				visflag = 0;
			}
			return visflag;
		}
		
		void setclose(boolean st){
			close = st;
		}
		
		void spacehit(){
			int field_width = display_width/10;
			if(x < 0) x = 0;
			if(x > display_width - p_width - field_width) x = display_width - p_width - field_width;
		}
	}

プレイヤーに関する情報と制御を行うクラス。inv_checkとisinvの役割がかぶってる気がしますが、inv_checkはインクリメントしてるのでコード中1回はこっちを呼び出すんだと思う()多分もっとうまくやれる

TeruTeruCanvas.java内Stageクラス

	class Stage {
		private int stagesize; //ステージ長さ
		private int progress; //進行状況
		private int score; //スコア
		//画面内の雨粒数
		private int ame_st;
		private int ame_max;
		private int framecount; //フレームカウント
		
		Stage(){
			this.stagesize = 1000; //ステージ長さ
			this.progress = 0; //進行状況
			this.score = 0; //スコア
			//画面内の雨粒数
			this.ame_st = 7;
			this.ame_max = 7;
			this.framecount = 0;
		}
		int getProgress(){return progress;}
		int getStage(){return stagesize;}
		int getScore(){return score;}
		int getAmest(){return ame_st;}
		int getAmemax(){return ame_max;}
		int getFramecount(){return framecount;}
		//進行増加
		void incProgress(){
			progress++;
		}
		//ステージスコア増加
		void incScore(){
			score++;
		}
		//ステージ終了判定用メソッド
		boolean StageEnd(){
			if(progress > stagesize){
				return true;
			}
			return false;
		}
		//現在のフレーム総数
		void count_up(){
			framecount++;
		}
	}

ステージ制御。progressに現在地、stagesizeでステージの長さ

TeruTeruCanvas.java内Sqerクラス

	class Sqer {
		private int x;
		private int y;
		private int w;
		private int h;
		
		Sqer(int sq_x, int sq_y, int sq_w, int sq_h){
			this.x = sq_x;
			this.y = sq_y;
			this.w = sq_w;
			this.h = sq_h;
		}
		
		int getX(){return x;}
		int getY(){return y;}
		int getW(){return w;}
		int getH(){return h;}
		int getCX(){
			return (x+w)/2;
		}
		int getCY(){
			return (y+h)/2;
		}
	}

当たり判定とかで四角形の座標をまとめて扱うためのクラス。中点を返したりできます。

サンプルコードについて

www.ymobile.jp

ここの「サンプルコード」からダウンロードできるzip形式ファイルの中からゲーム制作の参考にしたサンプルについて。

 

AudioPlaySample・・・音楽再生

FontSample・・・文字表示

GraphicsSample・・・Canvasの使用。塗りつぶしや図形描画

ImageSample・・・画像表示

KeyEventSample・・・キーイベント(数字キー記号キーの使用)

KeyEventSample2・・・キーイベント(方向キー決定キーの使用)私はこれをベースにして開発した

SpriteSample・・・Spriteの使用

機種解説

今回は、ウィルコムY!mobilePHS、402KCで動作させることを目標としています。

www.ymobile.jp

最後の京セラ製PHSであり、PHS向けJavaアプリを動作させることのできる最後の端末。

OSは昔から変更なかったと思うので確かTRON。ちなみに今Y!mobileから出てるシャープ製のガラケー型端末はUIそっくり中身Androidの別物です。従来のJavaアプリは動きません。

 

さて、PHSにて採用されているJavaMIDPと呼ばれているもの。

ガラケーではSoftBankauオープンアプリEZアプリ(J))、EMOBILEで採用されていました(DoCoMoは言語はJavaの独自のもの)なので、各社独自の追加機能はあれどY!mobile(ウィルコム)はSoftBankauのアプリの作り方そのままでいけるようです。

PHSと携帯電話の違いについてはこの記事では割愛させていただきます。Javaアプリに関していえば性能や独自機能以外違いはないと思います。

また、細かな制限があるようです。

参考文献:http://www.atmarkit.co.jp/ait/articles/0809/17/news135_3.html

 現在、勝手アプリを制作したい場合ウィルコムau向け(契約している端末のみ?)ということになります

 

では、ターゲットの端末の性能を確認していきます

www.ymobile.jp

このページより、

>搭載するJavaVMは、MIDP 2.0/CLDC 1.1に準拠したもので、JSR-75のFile Connectionにも対応しております。
>また、一部の機種は位置情報、カメラのAPIマスコットカプセルV3.0にも対応しております。
>対応する画面解像度はQVGAで、描画可能領域(Canvas)は240x276です。

この辺はほぼ全機種共通と考えて良さそうです。

 

このページにあるJava機種情報と対応API一覧表は要チェック。

機種情報:https://www.ymobile.jp/service/contents_service/common/pdf/javatm_lineup.pdf

対応API一覧表:https://www.ymobile.jp/service/contents_service/common/pdf/javatm_api.pdf

WX12Kまでしか書かれてないやん…まぁ、ほぼ上位互換的に性能が上がってるはずなのでWX12Kで動けば動くでしょう(

 

機種情報の方のWX12Kの情報も参照しておきます。

www.ymobile.jp

 https://www.ymobile.jp/service/contents_service/common/pdf/function.pdf

f:id:kikyou_kiki:20180504165010p:plain

ほとんど同じですがサウンド関係についてはこちらに詳しく書かれているのでチェックしておきます。

*1:アプリゲット等。今でいうAppStoreやGooglePlayのようなところ