まるぼ実験場

アプリの開発日記を載せるサイトでしたが、ただの技術ブログになりました。

睡眠導入剤を飲み始めて珍しい副作用が出たかもしれない

このブログの更新は久しぶりでした。

色々あって退職し、以前のエントリで言及したように専門学校に進学したりしてます。イラレの勉強してます。ちゃんと転職できるか不安です。

 

で、荒れきった生活を改善するための治療の一環として、かかっている先生から、ゾルピデムという睡眠導入剤をもらって飲み始めていたのですが…

そこでちょっと珍しいかもしれない?副作用に出くわしたので記しておく。

 

何が起きたかというと……薬を飲み始めて一晩経ったくらいからやたらと目がカラカラになる。

一晩後はなんかちょっと違和感ある程度だったが、次の日の日中には我慢出来なくなり市販の目薬を差し始めた。ホットアイマスクも買った。


3日目、上記秘密兵器ためしても改善せず。乾きと痛みがピークに。目を開けてられない。

もう乾きで逆に目が痛くなりスムーズな入眠どころじゃないレベル。

 

ここでその晩の分を一日断薬したところ(←勝手な判断で)、4日目午前は目の乾きと痛みと頭痛がひどかったが、午後になる頃には涙も出始めかなりマシになったので、薬が原因ではないかと自己推定。

この薬を出した医者に電話したところ、そういう副作用は珍しいなーと言う回答。

もし、薬のせいでは無いなら素でなにか起きている可能性がある、とすぐさま近くの眼科を探して受診。

 

ここで見てもらった眼科医曰く……

・精神向上薬系の薬は分泌を抑える作用がある

・分泌を抑えるということは涙などの分泌も抑える

・それで目が乾く

 

とのことだったので薬の副作用という予想はある程度当たっていたらしい。(実際めちゃくちゃ目が乾いていたらしい)

 

とりあえず指導と処方箋で目薬を出していただいたのだが、効果あったのか薬に慣れたのか、一度断薬したときに近い状態で今はかなり楽になっている。

この副作用っぽい現象が再度出るようなら、この飲み薬辞めた方がいいんじゃないかなぁと思えてきた。

薬自体は寝る前に思考がぐるぐるする現象は抑えられ、なんか意図的に考えようとしても抑えられるというか重みで落ちていく……ような感覚があるので入眠に効果はありそうだが、目が痛くなってはそれどころじゃない……。

呉服屋(着物屋)が怖い

皆さん勧誘セールスって受けたことありますか。

その最たるものとして某放送局やインターネット系の勧誘は有名だが、うっかりひっかかるとそれらに並ぶくらいものすごく厄介だと思った業種がある。

呉服屋である。

今回は私が呉服屋の勧誘にひっかかって酷い目にあった話をしようと思います。

 

私とその呉服屋との最初の出会いはあるショッピングモールでのイベントブースである。

まぁ普段ならばそそくさと歩き去るのだが、その日はどこかぼーっとしていて担当者に捕まってしまい、そのままガラポン引いてお菓子もらってアンケートとかに答えて住所とかを書いてしまう。

このとき、すみませんが引っ越し予定なので…ということにして住所記入は断ればよかった。今でも郵便物が送られてくる。送られてくるたびにムカついてる。

 

まぁしかし筆者、好きなゲームが昔の日本を舞台とした侍が活躍するゲームだったり最近読んだ小説の和装キャラがカッコいい!って思っていたりするし和装に全く興味がない訳ではない。だから、和装が出来る機会と聞いてちょっと浮かんでいたところがあるのは事実だ。

会場に用意されていた着物を着付けしてもらって写真をもらったり、着物への憧れとか着物着てやってみたいこととかで話が弾んでいた。

次回この時この時間にお店に来てくれたらまた着物着せてあげるしお土産(お菓子)もあるよ、とチラシを渡される。
ここで話が弾み楽しかったこと、担当者の着物が好き!という姿勢にも惹かれ、今度ちょっと話聞いて見てみてもいいかな、と思った。

……着物着てやってみたいこと?それはもちろん京都観光とbeatmaniaIIDXですね。(勿論これは言わないでおいた。)

 

約束の日に店舗を訪ねる。

雑談を挟みつつ、こんなのが着てみたいな~ってリクエストしながら着物を選ぶ。

そうして選んでもらった着物と帯と帯〆を着付けしてもらう。……すごい、私の好みにすごく近い。

具体的には両儀式みたいなイメージ。

担当者が着付けしてくれる傍ら、なんか普段は居なくて今日たまたま居たらしい本社の偉い人が話を挟んでくる。

「素晴らしい!似合っている!君にこの着物を来てもらいたい」「これは一生の出会いですよ。」…などと担当者と偉い人はとにかく褒めてくれる。ここに書いた台詞より数倍上手いお世辞のオンパレード。いえいえそれはきっとあなたの選び方のセンスが素晴らしいんですよ。

偉い人の話から推察するに、どうやら私がこれと言ったのはなんかすごく良いやつだったらしい。文化財級とか一つ買えば一生着られるレベルだとかなんとか。本当か?

そんなことは話半分に、「いいですよね~」「はい、いいですね~この柄とか気に入りました。」みたいなやりとりしながらこの姿いいなぁ、夢のようだと鏡を見ながら見惚れていると、

「これは元々百ウン十万するもので~」

(筆者、冷や汗をかきはじめる)

「この帯が二万円くらいで~」

(安……くない!Switch Liteが一台買えちまうぞ?!)

キャンペーンとかで特別サービスとかで、だいたい400k……くらいにできると隣で電卓を叩き始める。

いや、いくら元が良いものでこの値段がお値打ちだとしても私の身の丈に合わない買い物だ。…これは断らないとまずいのでは。

それに40万の衣類とかお手入れとか怖すぎて持て余す気しかしない。汚れたらきものクリーニングに持って行くとか資金以上にその手間が面倒すぎる。
最初にちゃんと防カビ加工するから大丈夫…?いやいや、私の衣類に対する扱いの雑さを舐めないで頂きたい。

ちなみにこの値段に落ち着くまでに、ずっと高い高い言い続けてます。ここまできたら無い袖は振れないからもう諦めてくれよ。
「この値段に出来るのは○○感謝祭の今だけですよ!」とかそんな感じの文句もセット。
お金があって気を良くした人ならもう少し前の時点で買ってただろうな。

流石にその値段でも…と渋っていると、クレジットカードはお持ちですがと聞いてくる偉い人。当然ここは持っていないことにする。

するとまた電卓を叩き始めて、一月一万ちょっとを3年払えば一生ものの着物が手に入ります!!クレジットカードはもって無くても運送会社の制度でローン出来ますよ!!と説明される。

……いやなんでローン組んでまで買う前提なの。しかもどんだけローン組ませたいの。

あいつらヤバい、仕事しててお金を持ってるとみるやいなやあの手この手でローン組ませようとしてくるぞ。

一ヶ月一万ちょいと言ったら、ちょっとした毎月の贅沢を我慢すれば正直手が出せてしまうお値段だ。

いやしかし、諸事情で資金が厳しくなる来年から三年近く、贅沢を我慢した生活三年も続けられるか?そんなの絶対嫌だ。これから専門行くんで学費の支払いが等と事情を話して諦めてもらおうとする…が。
なんなら今のうちにローン組んでおけば来年(私の方の事情で)ローンが組めなくなっても支払いだけで買えますよ
とか言ってくる。なんなら生活の変化の機会に買う方も多いですよ。

いやいやいやそれ絶対払いながら後悔するわ、そんな時に楽しむ余裕絶対ねぇわ、それほどお金無くなるんですと言ってもなかなか解放してもらえない。

終いにはもう泣いていた。

そちらの利益に一切貢献できないような貧乏人がこんな所に来てごめんなさい。

そこまでしてようやく着付けを解放してもらえることとなった。買えないと言っているのにここまでしないと解放してもらえないとは。惨めすぎて今書いてても泣けてきた。

あと一応、事前に約束していたお菓子はちゃんと頂けた。

お金がない、ただお金がないんですなんならお仕事くださいと泣きつく私に最後まで付き合ってくれた担当者、めちゃくちゃお人好しみたいな人だったけどこんな客には関わらない方が良いと思う。いや関わらないでください。

なんか次回予約も入れられていたが、もう二度と行かないだろう。

断るときは完全NOで。

筆者は純粋に良いなぁと思っていたので、良いなぁ~でもこの値段ではなぁ~と思わせぶりな返事を言い続けてたせいでセールスが長引いたんだと思います。

少しでも買いそうな素振りがあると奴らは乗せて買わせようとしてきます。これ本当に買うまで返してくれなかった勢いなんじゃないか。こんなのよほどの金持ちで心が強い人じゃないと破産まっしぐらやろ。

着物や和装自体には以前から興味はあったから覗いてみたんだけど、初心者にいきなり40万はねぇわ。

例の偉い人から初心者なのにモノの見方が分かっている人、みたいな持ち上げられ方してたけど、コレって巧妙だな……と思いましたね……。
いやでも弾き方も手入れも保管の仕方も何も知らない初心者に練習用すっ飛ばしていきなりプロ用楽器勧めるようなもんでしょ。そういうのは価値がわかってから手を出すモノでは?
最初はもっと手頃なものから勧めて欲しかったな……と思ったのでした。

そんなものは存在しなくて、初心者用でも40万円くらいの金銭感覚が当たり前の世界だったのかもしれんけど。

それよりも担当者二人に途中から散々ageられながら完全に買う前提で話されてたのが怖かった。私がお金持ってて心に余裕があったらマジでサインしてたかもしれない。

業界全部が全部こんな店ばかりではないと信じたいが、後になって調べたらネットで言われる勧誘タイプをあまりにもビンゴしてたので呉服屋行くの怖い。いや前を通るのも怖いわ。

みんなどこで最初の着物買ったんだろう?

 

新社会人のみんなも呉服屋の勧誘には気をつけような。

 

で、最近そこからまたDMが来たんだけども。

着付け教室申し込んだ人に洗える着物無料プレゼント!!とか書いてあってこういう価値くらいのやつから始めるべきでしょと思いながら、
非常にそそられるがもう二度と行かない。

『もっと書き込み隊2』を令和のサーバーで動かし隊

懐かしの掲示スクリプト、『もっと書き込み隊2』を現代のサーバーで動かしたくて試行錯誤したメモ。

tackysroom.com

これ見た目がかわいいだけじゃなく、機能面もかなり高機能なんですよねー。
トリップ機能とか昇格機能とか他じゃ見かけない。昇格を楽しみについつい書き込みたくなっちゃう。
個人サイト全盛期は人気があったみたいで、一時期かなりのサイトで採用されていたと記憶している。
それでちょっとした懐かしさというか愛着がある、この掲示板を復活させたい!…と思いましてね…。

まずは普通にサーバーに転送してパーミッションなどをマニュアル通りに設定してみるも上手く動作せず。
一度ローカル環境を用意してから解析作業を行った。

動作環境:ロリポップレンタルサーバー スタンダード(Perl5.30)、スタードメイン無料サーバー
確認環境:Mac OS Monterey 12.0.1(Perl5.30.3)

事前準備

ソースコードを全てUTF-8(LF)に変換。
Macだとなぜかうまく変換されなかったのでWindowsVSCodeで変換してからMacに持って行った。…なんでそのまま全てWindowsでやらなかったのかって?環境構築が面倒だから(オイ
ソースの文字コードを変換したことに伴い、ソースコード内のHTMLヘッダーなどShift-JISが指定されていた箇所を全てUTF-8に書き換えます。

ログで発生していたエラー

defined(%hash) is deprecated ~

発生箇所を見るに原因は使用されているモジュール、「jcode.pl」絡みのエラー。
jcode.plは大昔に更新が停止しており、後継のモジュールも出ているのでそちらに乗り換えることにした。
今回は標準で搭載されている、『Encode』に乗り換えました。

mkakikomitai2.cgi

use utf8;
use Encode;
binmode(STDOUT, ":utf8");

初期設定の冒頭に追加。binmode(STDIN, ":utf8");は使われていなかったので入れなかった。
use utf8も要らないかも知れない…が当該の処理が使われているか解析までのする気力が無かったのでとりあえず書いておいた。
詳しくは参考資料のサイトへ。

その後、jcode.pl絡みの記述を全てコメントアウト

#require './jcode.pl'

次に、mkakikomitai2.cgiとmkakikomitai2_pc.pl内を$jcodeで検索し、出てきた所の行頭をコメントアウトする。

この状態でローカルサーバーでテストし、文字化けなしで書き込めることを確認できました。

一カ所、気になったところがあったので修正

mkakikomitai2_pc.pl
入力フォーム、メールのフォームを作成している所

#if ( $mailerr ) { $dmy="style={display:none;}"; $c_email=""; } else { $dmy=""; }
if ( $mailerr ) { $dmy="style=display:none;"; $c_email=""; } else { $dmy=""; }

ホームページアドレスのフォームを作成している所

#if ( $urlerr ) { $dmy="style={display:none;}"; $c_hp=""; } else { $dmy=""; }
if ( $urlerr ) { $dmy="style=display:none;"; $c_hp=""; } else { $dmy=""; }

メールとホームページの欄に入力された場合スパム扱いする設定を行った際、メールとホームページアドレスの入力欄が隠されるのだが、それがうまく働いていなかったので上記のように変更した。(コメントアウト文:変更前、下の文:変更後)

これらをサーバーに転送して動作することを確認。

正直これだけで良かったのだろうか…と思っていたりするので、間違いとか指摘とかあったら教えてください。
元がかなり古いスクリプトでセキュリティ対策とかに不安な面があるため、まだ手を入れる必要があると思いますが、
ひとまずお気に入りのスクリプトが無事動いたので嬉しいです(^^*)

参考資料
PerlのCGIのutf8改造で文字化けしたときの処方箋

まる募メンテ:消えたモジュール?

今日の11時25分頃から、まる募のTwitter投稿プログラムがCron実行エラーを吐いていました。

エラー内容は、tweetpyを呼び出した行で、

『ModuleNotFoundError: No module named 'six'』

解決方法

1.SSHでサーバーにログインします。

2.SSHでログインしたら、pip install sixと打ちます。

pipが無い場合、使用できるようにしておく。

(参考;ロリポップ!でpythonのpipを導入し、Flaskの環境を構築する | dattesar

3.おわり。

 

今までは問題なく動作していて、今日から急にエラーになっていた様子なのだが、
ロリポップ!のサーバー側で仕様変更でもあったのだろうか?

ウィルコムPHS用のゲームを自作した記録(3)

最初にサンプルを動作させてみる。

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

新規プロジェクトを作成

新規プロジェクト作成を押します。
f:id:kikyou_kiki:20180505211201p:plain
クラス名とクラス名を入力します。
f:id:kikyou_kiki:20180505211237p:plain
API設定では、
marubodiary.hateblo.jp
の項で確認した項目を設定していきます。
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
プロジェクト名を選択し、プロジェクトを開く。
作業フォルダのファイルを開き、編集を再開します。

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

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

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.)

音楽ファイルの使用方法について解説します

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

変数宣言

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が再生されないという状況ですが、もし他の機種で再生されたなどありましたら、情報提供していただけると幸いです。

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

ウィルコムPHS用のゲームを自作した記録(2)

どんなゲームにするか。

ゲーム内容(コンセプト)を一言でいうと、ワンボタンで遊べる避けゲー。
ざっくり言うと、シューティングゲームから弾を避ける要素のみにしたような感じ。
ワンボタンで遊べるという手軽さから、ガラケーでの操作にも似合い、制限された環境でも動作させられるという点がポイント。
個人的に開発したことのないプラットフォームでの開発を経験してみたいと感じ、敢えてガラケーJavaというプラットフォームをチョイスした。

ゲーム仕様

ボタンを押している間キャラクターは右に移動。離すと左に戻っていく。
オブジェクトはランダムに降ってくる。
オブジェクトにキャラクターが触れるとライフが減少する。
UIとして、進行状況を表すバーとスコア表示を常に画面に表示
スコアが上昇している間には表示とSEの再生を入れる。
避けるだけのゲーム内容で単調だったので何かしら要素を追加 →降ってくるオブジェクトに接近することで得点が増加するように変更。ギリギリのスリルが味わえます(?)*1
ストーリー面 →クリアごとにタイトル画面が変わる
ゲーム内容自体はこんなシンプルなルールしかありません。元々初めて作ったアクション系ゲームなので面白いかどうかはともかくプログラムとしての想像が沸きやすい、といった点を重視した所このようなゲームに。

メイン関数内の解説。

run()を実行してwhileに入るまでの長々とした部分でゲーム内の変数を生成しています。
whileに入ってからはswitch文で分岐させています。遷移を図にしてみるとこのようになっています。
f:id:kikyou_kiki:20180504153801p:plain
図にしてみるともっとわかりやすい分岐ありそうだと思うんですよね…ゲームはまだ見よう見まねで作ってるので詳しい人教えてください!
 コードを見てみると、switch文の各caseに処理と描画メソッドの呼び出しが入っており、処理に続いて描画メソッドの呼び出しが入っています。
メモリが限られた環境でパフォーマンスを意識する場合はあまり関数分けしない方が良いと聞いたので*2、描画部分や長くなっている部分以外はそのままにしたため、このようになっています。

ソースコード

呼び出し部分。Y!mobileのサンプルを参考に。

import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;

public class Teruteru extends MIDlet {
	static MIDlet midlet;

    //コンストラクタ
    public Teruteru() {
		midlet=this;
        TeruteruCanvas sc = new TeruteruCanvas();
        Display.getDisplay(this).setCurrent(sc);
        Thread thread=new Thread(sc);
        thread.start();
    }

    //アプリの開始
    public void startApp() {
    }

    //アプリの一時停止
    public void pauseApp() {
    }

    //アプリの終了
    public void destroyApp(boolean flag) {
    }
}

メインの部分。

import java.util.Random;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.game.*;

import javax.microedition.media.*;
import javax.microedition.media.control.*;
import java.io.*;

public class TeruteruCanvas extends GameCanvas implements Runnable,CommandListener{
	//画像
	Image im_title;
	Image im_Player;
	Image im_ame;
	Image im_bg;
	Image im_gover;
	//BGM
	Player sndBGM;
	Player goverBGM;
	Player damageSE;
	Player scoreSE;
	
	String BGMname;
	String SEname;
	
	//ディスプレイサイズ取得
	int display_width = getWidth();
	int display_height = getHeight();
	
	//ランダム生成
	Random random = new Random();
	//ソフトキー
	Command exit;
	Command inst;
	
	String keyCommand = ""; //コマンド保存
	
	boolean inst_flag = false;
	boolean flag;
	
	//コンストラクタ
	TeruteruCanvas(){
		//キーイベントの抑制
		super(false);
		
		//画像の読み込み
		try{
			im_title = Image.createImage("/teruri-1.png");
			im_Player = Image.createImage("/player_sp.png");
			im_ame = Image.createImage("/ame.png");
			im_bg = Image.createImage("/bg.png");
			im_gover = Image.createImage("/gameover.png");
			
		}catch(Exception e){
			System.out.println(e.getClass().getName());
		}
		//BGMの読み込み
		try {
			InputStream in_bgm = getClass().getResourceAsStream("/teruteru_pops.mid");
			InputStream in_gover = getClass().getResourceAsStream("/teruteru_Healing_Harp.mid");
			InputStream in_damage = getClass().getResourceAsStream("/se_damage.wav");
			InputStream in_score = getClass().getResourceAsStream("/se_score.wav");
			
			sndBGM = Manager.createPlayer(in_bgm, "audio/midi");
			goverBGM = Manager.createPlayer(in_gover, "audio/midi");
			damageSE = Manager.createPlayer(in_damage, "audio/x-wav");
			scoreSE = Manager.createPlayer(in_score, "audio/x-wav");
			sndBGM.setLoopCount(-1);
		}catch (Exception e) {
			e.printStackTrace();
		}
		
		//ソフトキーの設定
		exit = new Command("終了", Command.EXIT, 0);
		addCommand(exit);
		inst = new Command("操作説明", Command.SCREEN, 1);
		addCommand(inst);
		setCommandListener(this);
	}
	
	//雨粒オブジェクトクラス
        //落ちてくるオブジェクトの座標の生成、移動、生存の情報を持っているクラス。オブジェクトを生成するとランダムで座標を決定します。
	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;
		}
	}
	//プレイヤーに関する情報と制御を行うクラス。inv_checkとisinvの役割がかぶってる気がしますが、inv_checkはインクリメントしてるのでコード中1回はこっちを呼び出すんだと思う()多分もっとうまくやれる
	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;
		}
	}
	// ステージ制御
	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++;
		}
	}
	// あたり判定などの制御
	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;
		}
	}
	
	public void run(){
		Graphics g = getGraphics();
		int scene = 1; //シーン情報
		
		//ゲームグラフィック
		Sprite title = new Sprite(im_title, 250, 250);
		Sprite p = new Sprite(im_Player, 50, 50);
		Sqer p_cl = new Sqer(16, 4, 18, 19);
		p.defineCollisionRectangle(p_cl.getX(), p_cl.getY(), p_cl.getW(), p_cl.getH());
		Sprite bg = new Sprite(im_bg);
		Sprite gover = new Sprite(im_gover);
		//ステージ生成
		Stage s = new Stage();
		//配列変数
		Object_Ame[] ame = new Object_Ame[s.getAmemax()];
		Sprite[] am = new Sprite[s.getAmemax()];
		Sqer am_cl = new Sqer(2,11,12,12);
		for(int i = 0; i < s.getAmemax(); i++){
			am[i] = new Sprite(im_ame);
			am[i].defineCollisionRectangle(am_cl.getX(), am_cl.getY(), am_cl.getW(), am_cl.getH());
		}
		//プレイヤー生成
		Object_Player player = new Object_Player();
		//ゲージ用変数設定
		Sqer g1 = new Sqer(display_width - display_width/10, display_height/10, display_width/25, (display_width * 3)/5);
		
		//Font
		Font largefont;
		Font font;
		largefont = Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_PLAIN,Font.SIZE_LARGE);
		font = Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_PLAIN,Font.SIZE_MEDIUM);
		
		int clear = 0; //クリア回数
		int high_score = 0; //ハイスコア
		
		int keyState = 0; //キー情報
		flag = true;
		
		while(flag){
			//画面の初期化
			g.setColor(255,255,255);
			g.fillRect(0,0,display_width,display_height);
			
			//ボタン操作取得
			keyState = getKeyStates();
			
			switch(scene){
			case 0: //init
				player = new Object_Player();
				s = new Stage();
				//配列初期化
				ame = new Object_Ame[s.getAmemax()];
				//コマンド初期化
				keyCommand = "";
				
				scene = 1;
				break;
			case 1: //タイトル画面
				//スタートキーが押された
				if((FIRE_PRESSED &keyState) != 0){
					scene = 2;
					//BGMスタート
					try{
						sndBGM.start();
						sndBGM.setMediaTime(0); //最初から再生します
					}catch (Exception e) {
						e.printStackTrace();
					}
				}
				
				//操作説明キーが押された
				if(inst_flag == true){
					scene = 5;
				}
				
				drawTitle(g, font, largefont, title, high_score, clear);
				
				break;
			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();
				
				//終了判定
				if(s.StageEnd()){
					scene = 4;
					try{  
						sndBGM.stop();
					}catch (Exception e) {
						e.printStackTrace();
					}
				}else if(player.getlife() <= 0){
					scene = 3;
					try{  
						sndBGM.stop();
					}catch (Exception e) {
						e.printStackTrace();
					}
				}
				
				//隠しコマンド
				if(keyCommand.equals("6109")){
					player.setinv_f(1);
				}else if(keyCommand.equals("37564")){
					player.lifedown();
				}
				
				//描画
				drawGameBackground(g, bg);
				drawGamePlayer(g, player, p, s.getFramecount());
				drawGameMoveObj(g, ame, am, s.getAmest());
				drawGameUI(g, font, largefont, s.getScore(), player.getlife(), player.getclose(), s, g1);
				
				break;
			case 3: //ゲームオーバー画面
				//BGM
				try{
					goverBGM.start();
				}catch (Exception e) {
					e.printStackTrace();
				}
				
				//スコアを更新
				if(s.getScore() > high_score){
					high_score = s.getScore();
				}
				
				//描画
				drawGameOver(g, font, gover, s.getScore());
				
				try {
				Thread.sleep(500);
				} catch (Exception e){
				}
				
				if((FIRE_PRESSED &keyState) != 0){
					scene = 0;
					try{
						goverBGM.stop();
						goverBGM.setMediaTime(0);
					}catch (Exception e) {
						e.printStackTrace();
					}
				}
				
				break;
			case 4: //クリア画面
				//スコアを更新
				if(s.getScore() > high_score){
					high_score = s.getScore();
				}
				//描画
				drawClearStage(g, font, s.getScore(), player.getlife(), bg);
				
				try {
				Thread.sleep(1000);
				} catch (Exception e){
				}
				
				if((FIRE_PRESSED &keyState) != 0){
					scene = 0;
					clear++;
				}
				
				break;
			case 5: //操作説明
				if(inst_flag == false){
					scene = 1;
				}
				//描画
				Instraction(g, font);
				
				break;
			}
			//画面に反映
			flushGraphics();
			
			//スリープ
			try {
				Thread.sleep(100);
			} catch (Exception e){
			}
		}
		
		//アプリ終了通知
		Teruteru.midlet.notifyDestroyed();
	}
	
	public void keyPressed(int keyCode) {
		if (keyCode==0){
			return;
		}
		
		switch(keyCode) {
			//数字キー・記号キー
			case KEY_NUM0: keyCommand+="0";break;
			case KEY_NUM1: keyCommand+="1";break;
			case KEY_NUM2: keyCommand+="2";break;
			case KEY_NUM3: keyCommand+="3";break;
			case KEY_NUM4: keyCommand+="4";break;
			case KEY_NUM5: keyCommand+="5";break;
			case KEY_NUM6: keyCommand+="6";break;
			case KEY_NUM7: keyCommand+="7";break;
			case KEY_NUM8: keyCommand+="8";break;
			case KEY_NUM9: keyCommand+="9";break;
			case KEY_STAR: keyCommand ="";break;
		}
	}
	public void GameisHit(Object_Ame[] ame, Sprite[] am, Object_Player player, Sprite p, Stage s, Sqer am_cl, Sqer p_cl, int ame_st){
		//接近変数
		int px2=0;
		int px1=0;
		int py2=0;
		int py1=0;
		int r = 40;
		int close_count = 0;
		//オブジェクト処理
		for(int i = 0; i < ame_st; i++){
			//オブジェクト生成
			if(ame[i] == null || ame[i].getState() == false){
				ame[i] = new Object_Ame();
			}else{
				//オブジェクト移動
				ame[i].MoveAme();
			}
			//範囲外に出たときの処置
			if(ame[i].getY() > display_height){
				ame[i].deadState();
			}
			//衝突判定
			if(player.getinv_f() == 0){ //無敵でないとき
				if(ame[i].getState() & p.collidesWith(am[i], true)){ //AmeがDeadでない&衝突している
					//SE再生
					try{
						damageSE.start();
						damageSE.setMediaTime(0); //最初から再生します
					}catch (Exception e) {
						e.printStackTrace();
					}
					player.lifedown();
					ame[i].deadState();
					player.setinv_f(1);
					break;
				}
				//かすり判定
				px2 = am[i].getX() + am_cl.getCX() -r/2;
				py2 = am[i].getY() + am_cl.getCY() -r/2;
				px1 = player.getX() + p_cl.getCX() -r/3;
				py1 = player.getY() + p_cl.getCY() -r/2;
				
				if(Math.abs(px2 - px1) * Math.abs(px2 - px1) + Math.abs(py2 - py1) * Math.abs(py2 - py1) < r*r){
					//接近スコア加算
					try{
						scoreSE.start();
					}catch (Exception e) {
						e.printStackTrace();
					}
					close_count++; //接近カウント
					s.incScore();
				}
			}
		}
		
		//1つ以上と接近でフラグON
		if(close_count > 0){
			player.setclose(true);
		}else{
			player.setclose(false);
		}
	}
	
	public void drawGameBackground(Graphics g, Sprite bg){
		//背景
		bg.setFrame(0);
		bg.setPosition(0, 0);
		bg.paint(g);
	}
	public void drawGamePlayer(Graphics g, Object_Player player, Sprite p, int framecount){
		//プレイヤーキャラ
		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);
		}
		
		//点滅演出
		if(player.isinv()){
			if(player.switchvisflag()==0){
				p.setVisible(false);
			}else{
				p.setVisible(true);
			}
		}else{
			p.setVisible(true);
		}
		
		//プレイヤー描画
		int p_x = player.getX();
		int p_y = player.getY();
		p.setPosition(p_x, p_y);
		p.paint(g);
	}
	public void drawGameMoveObj(Graphics g, Object_Ame[] ame, Sprite[] am, int ame_st){
		//障害物オブジェクト
		for(int i = 0; i < ame_st; i++){
			int am_x = ame[i].getX();
			int am_y = ame[i].getY();
			am[i].setPosition(am_x, am_y);
			am[i].paint(g);
		}
	}
	
	public void drawGameUI(Graphics g, Font font, Font largefont, int score, int life, boolean close, Stage s, Sqer g1){
		//進行ゲージ描画
		int x1 = g1.getX();
		int y1 = g1.getY();
		int w1 = g1.getW();
		int h1 = g1.getH();
		int progress = s.getProgress();
		int stage = s.getStage();
		g.setColor(0,0,0); //影
		g.drawRect(x1, y1, w1, h1);
		g.setColor(255,0,0); //実ゲージ
		g.fillRect(x1, y1, w1, h1);
		int gh_f = (int)((double)(1-((double) progress / stage)) * 100 * ((double)h1 / 100));
		g.setColor(0,128,128); //減る部分
		g.fillRect(x1 + 1, y1 + 1, w1 - 2, gh_f - 2);
		
		//スコア表示描画
		g.setColor(0,0,0);
		g.setFont(largefont);
		g.drawString("ENERGY:" + score, 0, 0, g.LEFT | g.TOP);
		
		//接近演出
		if(close){
			g.setColor(255,0,0);
			g.drawString("+ENERGY", 70, (s.getFramecount() % 3), g.LEFT | g.TOP);
		}
		
		//ライフ表示描画
		g.setColor(255,0,0);
		g.setFont(font);
		for(int i = 0; i < life; i++){
			g.drawString("●", 0 + (i * 14), 25, g.LEFT | g.TOP);
		}
	}
	
	public void drawTitle(Graphics g, Font font, Font largefont, Sprite title, int high_score, int clear){
		//描画
		title.setFrame(0);
		title.setPosition(0, 0);
		title.paint(g);
		
		title.setPosition(0, 0);
		if(clear >= 3){
			title.setFrame(3);
			title.paint(g);
		}else if(clear >= 2){
			title.setFrame(1);
			title.paint(g);
			title.setFrame(2);
			title.paint(g);
		}else if(clear >= 1){
			title.setFrame(1);
			title.paint(g);
		}
		
		g.setColor(0,0,0);
		g.setFont(largefont);
		g.drawString("HI SCORE:" + high_score, 0, 0, g.LEFT | g.TOP);
		g.drawString("PRESS START", display_width/3, display_height*4/5, g.LEFT | g.BOTTOM);
		g.setFont(font);
		g.drawString("★"+keyCommand, 0, display_height - 35, g.LEFT | g.TOP);
		g.drawString("(c) sui-kiki", 0, display_height, g.LEFT | g.BOTTOM);
	}
	
	public void drawGameOver(Graphics g, Font font, Sprite gover, int score){
		//描画
		g.setColor(0,0,0);
		g.fillRect(0,0,display_width,display_height);
		gover.setFrame(0);
		gover.setPosition(0, 0);
		gover.paint(g);
		
		g.setColor(255,255,255);
		g.setFont(font);
		g.drawString("Game Over", display_width/5, display_height*3/5+5, g.LEFT | g.TOP);
		g.drawString("Stage Score(ENERGY):" + score, display_width/5, display_height*3/5 + 20, g.LEFT | g.TOP);
		g.drawString("Restart PRESS BUTTON", display_width/3, display_height, g.LEFT | g.BOTTOM);
	}
	
	public void drawClearStage(Graphics g, Font font, int score, int life, Sprite bg){
		drawGameBackground(g, bg);
		g.setColor(255,0,0);
		g.setFont(font);
		g.drawString("Game Clear!!", display_width/4, display_height/2 - 10, g.LEFT | g.TOP);
		g.setColor(0,0,0);
		g.drawString("Stage Score(ENERGY):" + score, display_width/4, display_height/2 + 15, g.LEFT | g.TOP);
		g.drawString("Life Bonus(Life*50):" + life * 50, display_width/4, display_height/2 + 30, g.LEFT | g.TOP);
		g.drawString("Final Score:" + score + (life * 50), display_width/4, display_height/2 + 45, g.LEFT | g.TOP);
		g.drawString("Restart PRESS BUTTON", display_width/4, display_height*4/5, g.LEFT | g.TOP);
	}
	
	public void Instraction(Graphics g, Font font){
		//操作説明
		g.setFont(font);
		g.setColor(200,200,0);
		g.drawString("★操作方法★", 0, 0, g.LEFT | g.TOP);
		g.setColor(0,0,0);
		g.drawString("中央キーで右方向に移動し、", 0, 15, g.LEFT | g.TOP);
		g.drawString("離すと元の位置に戻っていきます。", 0, 30, g.LEFT | g.TOP);
		g.setColor(200,200,0);
		g.drawString("★得点の増やし方★", 0, display_height/5, g.LEFT | g.TOP);
		g.setColor(0,0,0);
		g.drawString("雨粒に近づくとエネルギーを吸収し、", 0, display_height/5+15, g.LEFT | g.TOP);
		g.drawString("スコアが増加します。", 0, display_height/5+30, g.LEFT | g.TOP);
		g.setColor(200,200,0);
		g.drawString("★隠しコマンド★", 0, display_height*2/5, g.LEFT | g.TOP);
		g.setColor(0,0,0);
		g.drawString("いろいろためそう", 0, display_height*2/5+15, g.LEFT | g.TOP);
		g.setColor(200,200,0);
		g.drawString("★クリア回数によって", 0, display_height*3/5, g.LEFT | g.TOP);
		g.drawString(" タイトルイラストが変化します★", 0, display_height*3/5+15, g.LEFT | g.TOP);
		g.setColor(200,200,0);
		g.drawString("★使用した素材★", 0, display_height*4/5 , g.LEFT | g.TOP);
		g.setColor(0,0,0);
		g.drawString("BGM:音楽研究所", 0, display_height*4/5+15 , g.LEFT | g.TOP);
		g.drawString("SE:TAM Music Factory", 0, display_height*4/5+30 , g.LEFT | g.TOP);
	}
	
	//キーリリースイベント
	public void keyReleased(int keyCode) {}
	
	public void commandAction(Command c, Displayable d) {
		if (c == exit) {
			flag=false;
		}else if(c == inst){
			//ボタン情報を送信
			inst_flag = !inst_flag;
		}
	}
}

関数一覧

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抜けてるけど描画メソッド フォント情報

*1:シューティングゲームのシステムを参考にした。

*2:関数呼び出しにスタックを使用するのでそれがメモリに効いてくるとのこと。

ウィルコムPHS用のゲームを自作した記録(1)

※かなり昔に書いたブログ内容の移転です。正直今見返すのもアレなレベルなのですが、自らの記録のために書いておこうと思います。

学生時代に作った、Javaもゲームの作り方もよく分かっていなかった時期のプログラムですが、よくここまで調査して作ったなぁ……と思ったので。自分以外に今更ガラケー向きアプリを作成する奇特な人間のために(?)残しておきます。

ゲーム制作自体の解説より、どのようにして実機で動かしたかといったHowto的な側面をまとめることを主旨としたいと考えています。史料となる…かどうかは分かりませんが、制作の過程をまとめておきたいと感じ、執筆しました。

というか、もう新規契約が終了してしまったどころかサービス終了したプラットフォームのゲームを遊んでもらうことが絶望的であると悟ったので、ソースコードや色々を公開してしまおうと思ってこんなことを始めました。遅かったのは分かっているんです、でも何かの役に立てば幸いです。

制作成果物のダウンロード

京セラ製PHS、402KCでのみ実機動作確認。(製作途中一度だけWX05Kでも動かした)

402KC実機上ではSEが再生されないのは仕様となります。

SDカードのPRIVATE\DATAフォルダにjadを置くとデータフォルダに認識されるので、インストールを実行します。

jad(OneDriveなので表示に時間がかかるようです)

開発前の準備

まずは開発環境を整えました。

特有のものとしては、j2me Wireless Toolkit

Sun Java Wireless Toolkit 2.5.2 for CLDC Download

エミュレータが同梱されており、テストやソースコードからのビルドを行えるツール。現在ダウンロードにはOracleアカウントでのログインが必要。

 

このツールをインストールする前に、先にJava Development Kit(JDK)をインストールしておく必要があります。

Java SE - Downloads | Oracle Technology Network | Oracle

バージョンは当時の環境のことを思い出しながらあえて6にしました。

詰まった点として、JDKは32ビット版でないとJ2MEのインストール時に認識されません。64ビット版を既にインストールしている場合でも32ビット版が必要でした。

開発の参考にした資料(ほとんどWebのもの)

連載インデックス「携帯アプリを作って学ぶJava文法の基礎」 - @IT

初心者向け。一つのアプリを作っていく構成であるのでわかりやすい。

DoCoMo(Doja)とそれ以外のキャリアのMIDPの違いも解説されている。

 

www.ymobile.jp

各機種の仕様が載っている

 

www.ymobile.jp

サンプルコードなどがあるので参考になります。

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

AudioPlaySample・・・音楽再生

FontSample・・・文字表示

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

ImageSample・・・画像表示

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

KeyEventSample2・・・キーイベント(方向キー決定キーの使用)

SpriteSample・・・Spriteの使用

www.ymobile.jp

詳細な機種スペックを参照

 

概要 (Unofficial 'CLDC 1.0 + MIDP 1.0' API Reference.)

Unofficial “CLDC 1.1 + MID Profile 2.0” API Reference. | KEI SAKAKI's PAGE.

MIDPの日本語リファレンス

 

モバイルの素-MIDP オープンアプリプレイヤー対応

 auオープンアプリプレイヤーでのアプリ作成

 

あとは自分の好きなテキスト編集ソフトを用意しておく。私はサクラエディタを使用しました。

今回は、ウィルコム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

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

スペシャルサンクス

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

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

saraemi.com

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

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

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

使用した素材など

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

音楽研究所 様

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

TAM Music Factory 様

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

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

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

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