2002 年筒井ゼミ卒業論文 携帯電話アプリケーションの製作 2002 年 12 月 学籍番号 5199189 学籍番号 5199027 学籍番号 5199215 23 日 松尾 岳洋 内田 陽平 森本 晃史 目次 1. はじめに 2. 携帯電話用アプリケーションについて 2.1 i アプリとは 2.2 i アプリの実行の流れについて 3. i アプリの開発目的について 4. 製作環境と実行環境の整備 5. 今回開発した i アプリについて 6. 今後の携帯電話用のアプリケーションの展望と将来性 7. むすび 8. 参考文献 9. 付録 9.1ガンシュティーングアプリのソースとサンプル画面 9.2カーレースアプリのソースとサンプル画面 9.3情報発信型アプリのソースとサンプル画面 要旨 本論文は NTT DoCoMo の携帯電話用アプリケーション、i アプリを Windows 用エミュレーターJ2ME Wireless SDK for the DoJa で製作し、完成した i アプリを 実際にサーバーにアップロードし携帯電話にて実行し、その上で得られたデータと 製作過程を解析し、考察したものである。 1. はじ めに 本論文は携帯電話用アプリケーション、i アプリを Java 2 Micro Edition(以下、 J2ME) 環 境 に お い て 実 際 に 製 作 し 、 イ ン タ ー ネ ッ ト サ ー バ ー に ア ッ プ ロ ー ド し 携 帯電話の実機にて実行して得られたデータを解析し、製作過程ともに考察するもの である。製作した i アプリについては、ガンシューティングゲームアプリ、カーレ ースアプリ、情報受信型アプリを開発した。これらのアプリの製作課程と実行結果 について考察した。 以下、まず2章において本論文で取り組んだ携帯電話アプリケーションについて 解説し、3章において i アプリの開発目的について述べている。4章においては本 論文で使用した製作環境と実行環境について述べる。また本論文で使用した物以外 の制作方法ついての考察を行う。5章では今回開発した主要な i アプリについて解 説と考察を行っている。6章においては今後の携帯電話におけるアプリケーション の展望とその将来性について考察し、7章で本論文のまとめとする。 2. 携帯 電話用アプ リケーショ ンとは 携帯電話用アプリケーションとは現在、国内 No.1シェアを誇る NTT DoCoMo が実用化した i アプリがその先駆けとされ、今日では J フォンなど、他の携帯電話 メーカーでもこの種類のアプリケーションが利用可能になっている。それ以前にも、 その携帯電話のみで使用可能な内蔵されたアプリケーションや一部のメーカーのみ が自社機種のみに提供してたアプリケーションはあったが、スタンドアローン型、 もしくはネットワーク対応型であり様々な機種において実行可能なアプリケーショ ンとしては i アプリが初である。アプリケーションは主に JAVA によって製作されて おり、その製作環境も公開されている事から個人のユーザーが自ら製作、公開する 事も比較的容易であり、様々な種類のアプリケーションが無料もしくは有料にて公 開されている。このような一般ユーザーでも製作できるほど開発容量が少なく、ま た取り組み易い事も携帯電話用アプリケーションの魅力である。 2 .1.i アプリとは i アプリとは 2001 年1月26日に NTT DoCoMo が発売した 503 シリーズから対 応になった携帯電話用アプリケーションの総称である。i アプリは J2MC という携 帯端末用プラットホームをベースとし、Sun Microsystems、NTT DoCoMo によって 決定されたモード対応 JAVA(DoJa)によって記述され、ネットワーク、グラフィ ック、セキュリティにおいて従来の携帯電話アプリケーションを超えたアプリケ ーションである。しかしながらプログラム実行メモリが 10K バイトと少ないこと、 機種依存が激しいこと、ピアツーピアで携帯電話同士が通信できないことなど欠 点も多い。i アプリの主な仕様は下記の表の通りである。 表1 i アプリの仕様表 項目 ドコモ仕様 備考 iアプリの最大サイズ 10Kバイト − スクラッチパッドの容量 5Kバイト N503iでは10Kバイト 保存できるiアプリの数 機種差有り N503iでは10個まで セキュリティ サーバアクセス iアプリは携帯電話のメモリ領 域にアクセス不可 iアプリはダウンロードされた サーバとのみ通信可能 − − 2. 2.i アプ リ実行の流 れについて iアプリが実行されるまでの流れは、下に示す図の様な流れになる。iアプリ は、プログラム本体が含まれる Jar ファイルと、アプリーケーションデスクリプ タファイル ADF と呼ばれる Jam ファイルから構成されている。Web ページからは Jam ファイルにリンクが張られており、最初に Jam ファイルが携帯電話機にダウ ンロードされる。Jam ファイルにはiアプリのサイズやスクラッチパッドの使用 サイズ等が記述されており、インストールが可能かどうかのチェックが行われる。 問題が無い場合は Jam ファイルに続いて Jar ファイルのダウンロードが開始され る。 図1 i アプリ実行の流れ 3. i アプリの 開発目的に ついて 我々が i アプリを製作を研究対象として選択した理由は大別すると3つある。 まず、i アプリを製作する上で容量が 10k バイトと規模が小さく、他のアプリケー ションを製作する事に比べ、比較的容易であるということ。ある程度違いはあるも のの、基本的には Java で記述するプログラミングであり、java 言語の基礎的なと ころ(クラスや継承の概念等)を学習・理解する上で役立つ事。最後に i アプリは 自分のアイデア次第で様々な面白いアプリが製作可能であるという事である。これ らの理由はプログラミング初心者に対して非常に取り組みやすい事を表していると 言える。また携帯電話という現代において最も万人に身近なコンピュータで動かす 事が出来る事、開発した物をインターネットで公開すれば様々な人に利用してもら える可能性があるということも魅力の一つと言えよう。これらの理由が我々が i ア プリを研究対象とした理由である。 4. 製作環境と 実行環境の 整備 i アプリを製作するに当たって、アプリケーションを開発するための環境が必要 となるのであるが、i アプリ製作に関して必要な環境は最低限の物ならば全てフリ ーソフトで入手可能である。具体的にはソースコードを書くテキストエディタ、コ ンパイルを行うのに JDK1.3(Sun Microsystems のフリーソフトのコンパイラ)J2ME CLDC(同じく Sun Microsystem のフリーソフト)と i モードの PC 版エミュレーター である i-jade lite(ZENTEC 社のエミュレーターで lite 版はフリーソフトである) この3つである。しかし、これは非常に遠回りな方法であり極端に PC のパワーが低 い場合を除いてはこの環境で開発を行うべきではない。(1) 現在の一般的な PC がある ならば、NTT DoCoMo の公式開発方法である J2ME Wireless SDK for the DoJa を使 っ て 開 発 す る 方 が 一 般 的 で あ る 。 J2ME Wireless SDK for the DoJa は Sun Microsystems の組み込み機器向けの Java2 で Standard Edition のサブセットであ る J2ME (Java2 Micro Edition)に NTTDoCoMo の提供する J2ME 用のライブラリ(ク ラス,インタフェース)が組み込まれた i アプリ専用の総合開発環境である。ソース からコンパイル、検証、エミュレーターによる PC 上での実行をすべて1つのソフト で行うことが出来るが開発ツールは残念ながら Microsoft の Windows でしか動か ない。ライブラリは JVM で動くものなので,UNIX 上でも利用できるが,動作の確 認が出来ないのである。なお、J2ME Wireless SDK for the DoJa は Sun Microsystems が提供する Java2 SDK (JDK1.3) も必要である。 他にも Java 用の IDE(統合開発環境)である Borland 社の Jbuilder と i-jade lite を組み合わせることにより開発環境とすることも可能である。いずれの方法もフリ ーソフトで賄う事が可能であり、この部分でも i アプリの開発の取り組み易さが現 れている。一方、実行環境についてだが、サーバーに生成された Jam ファイルと Jar フ ァ イ ル を ア ッ プ ロ ー ド す る FTP ソ フ ト と サ ー バ ー 、 ダ ウ ン ロ ー ド 時 に 表 示 す る HTML を記述するテキストエディター、そして実際に実行する NTT DoCoMo の 503 シ リーズ以降の i モード対応携帯電話端末が必要である。Jam ファイルはアップロー ドの際、バイナリモードで良いが Jar ファイルはアスキーモードでの転送が必要な 為、注意が必要である。 アップロードに使用するサーバーはポップ広告が表示されるようなフリーの Web スペース提供サービスのサーバーは控えた方が無難である。広告表示の HTML などが 携帯電話では同時に表示できない為、i アプリのダウンロード画面が表示されない などの不具合を招く場合がある。i アプリのダウンロードファイルである Jam と Jar ファイルはネットワーク上で公開された場合、一度に多数のアクセスを受け転送量 の増加を招くためサーバーに負担をかける事が多く、フリーの Web スペースサーバ ーでは強制削除の対象になってるサーバーも多い。我々は広告の出ない有償のサー バーを使用して研究を行ったが、一般に i アプリを公開している方の中には広告の 出るフリーWeb スペースを使用しておられる場合もあるので一概には言えない。 Web サーバーに Jam ファイルと Jar ファイルをアップロードした上でダウンロー ド用に携帯電話に表示させる HTML の記述にも一定の書式があり、その一例を下図に 示す。iアプリをダウンロードするために、以下の様に OBJECT タグにより、JAM フ ァイルへのリンクを設定しなければならない。A タグの href で設定しているリンク はiアプリをサポートしていない機種でアクセスされた場合にその旨を表示するペ ージを指定する。 <--Aタグのijamと同じIDを指定 <OBJECT declare id=”ファイル名” <--JAMファイルのURLを指定(絶対または相対パス data=”ファイル名.jam” type=”application/x-jam”> </OBJECT><A ijam=”#ファイル名” href=”error.html”>ダウンロード</A> 図2 ダウンロードページの設定 Jam ファイルは、以下の図の様に記述する。DoJa 等のツールを使用した場合は、ツ ールにより自動作成される PackageURL = ファイル名.jar <--JARファイルのURLを指定(絶対または相対パス) AppSize = 10235 <--JARファイルのサイズ(10240以下) AppName = ファイル名 <--iアプリの名称(携帯のソフト一覧に表示される) AppVer = 1.0 <--iアプリのバージョン AppParam = <--iアプリのメインクラス起動のパラメータ AppClass = MyIntro <--iアプリのメインクラス名 <--スクラッチパッドのサイズ SPsize = 10240 <--ネットワーク機能を使用する場合 UseNetwork = http LastModfied = Tue,01 May 2001 14:32:03 KvmVer = CLCD-1.0 <--最終更新日(JST) <--KVMのバージョン 図3 Jam ファ イルの記述 5. 今回開発 した i アプ リと問題点 について 本章では我々が開発した各 i アプリと開発するにあたって発生した問題点につい て解説していきたい。まず我々が最初に開発した i アプリであるガンシューティン アプリについての解説である。この i アプリは画面上に表示される9つの的を携帯 電話の数字テンキーに対応させ、的が表示された瞬間に数字キーを押すことにより 標的が撃破されるという非常にシンプルな物である。的のグラフィックにはアーチ ェリーで使用されるような真円を組み合わせた物を用意し、打ち抜かれた瞬間に的 に穴が開いたグラフィックを表示し、MIDI 音源による発砲音を演奏している。これ により拳銃で撃ち抜いた様な表現と演出を行っている。このアプリは非常にシンプ ルかつ爽快感溢れるものではあるが、単純すぎてやや飽きが早いのが問題点である。 次にカーレースゲームアプリについての解説である。これは画面奥より迫ってくる 妨害車をかわしながらゴールを目指し、何台妨害車を抜けるかを競うアプリである。 画面は遠近感を利用したスクロールで奥行きをだし擬似的な 3D 視点によって進行 する。自分の操作する自機は左右に動いて妨害車をかわす事が可能で、妨害車も左 右に動きながら画面手前、つまり自機に向かって迫ってくる。妨害車に衝突すると 自機は破壊されゲームオーバーになりスタート画面に戻る設定である。妨害車は大 中小の画像に描き分けられており、自機に近づくにつれて大きく表示されるという 手法で画面スクロールを表現している。1ステージにおけるおおよその妨害車の数 は15から25で1ステージのプレイ時間は約 1 分強と短い。このアプリもシンプ ルだが迫りくる妨害車かわしながら進むのはスリルがあり、ガンシューティングゲ ームに比べ画面がスクロールしている分、迫力がある。妨害車が迫ってくるアルゴ リズムも絶妙で上手くかわさないとすぐに衝突してゲームオーバーになってしまう。 このアプリの問題点はアプリ自体のプログラムと画像の容量が大きすぎて効果音や BGM などのサウンドエフェクトを全く組み込めなかった事である。やはりゲームた る物は視覚と聴覚が同時に働く事により、より臨場感や迫力を出す為、サウンドエ フェクトを使えないということは大きな損失である。しかしながらこのアプリは画 面をスクロールさせて表現力を出しているため、カーレースだけでなく道路を空中 に変え、妨害者を敵機に自機を戦闘機などに変え、自機のスクロールを上下左右に し弾丸発射機能をつければ、即フライトシューティングゲームにすることが可能な ど発展性が高いのが特徴である。現段階ではソースが i アプリの容量の限界で改良 が難しいのが現状ではあるが、画像の圧縮や演算の単純化による圧縮、定数の最適 化によりまだまだ改良の余地が残るのが残念である。最後に情報受信型アプリにつ いての解説である。このアプリはアプリを起動した時点で指定したサーバーのテキ ストファイルを読み込みアプリで表示されるキャラクターがそのテキストを読み上 げるように表示するアプリである。このアプリは前回読み込んだテキスト情報をス クラッチパッドに格納し、連続起動させておけば1時間ごとにサーバーから情報を 自動ダウンロードし表示する。ただテキストを表示するだけと考えれば単純すぎる と思われるかも知れないが、画面に表示するキャラクターとテキストの内容によっ てはニュース配信型から掲示板の新規書き込みに対応して書き込みを知らせるアプ リとして、また特定の人間のみ公開してメーリングリストに類似したテキスト配布 をする使い方や時間帯ごとに別のテキストを配信するペット飼育型アプリなど様々 な応用が可能な汎用性の高いアプリである。サーバーから読み込まれるテキストは CGI などで出力すれば無限に変化が可能であり画像表示もそれに伴って変化させれ ば他のアプリにくらべてきわめて汎用性が高い物になる。このアプリの問題点は1 時間ごとに読み込みをする為、その度にパケット通信費が発生し、アプリ実行にコ ストがかかる事である。またこのアプリも容量上の問題からサウンドエフェクトが 組み込めていないのが問題である。しかしながら表示に GIF アニメーションを組み 込んだり、サウンドエフェクトを組み込むことが可能になれば情報をしらせるアプ リとしてより効果的に機能する事が可能な非常に魅力的なアプリケーションである。 どのアプリケーションにおいても、データー容量の限界がネックになりアプリの表 現や演出力には悪戦苦闘しているのが現状である。しかし i アプリには現代のコン ピュータの記憶領域の肥大化と記録媒体の容量増加によって失われた感のあるコン ピュータ黎明期における職人芸の様なプログラミングが要求される。いかにソース を短く、簡潔に出来るか。いかに容量を抑えて表現力豊かなアプリを作成するか、 この部分が i アプリの魅力であり醍醐味であると言える。幸い、コンピュータ黎明 期のようなアセンブラではなく構造化されたオブジェクト指向の JAVA で記述され ている為、その事に関しては黎明期ほど高度な技術を必要とするわけでは無いと言 える。だか、いくらオブジェクト指向の概念は出来上がっていても、そのメリット を活かしたプログラム設計ができるかどうかはプログラマーの力量次第になるため i ア プリ は容 量の 限界と 表現 力の鬩 ぎ合 いであ り、 シンプ ルか つ奥が 深い 物であ る と言えるのである。 6. 今後の携帯 電話用のア プリケーシ ョンの展望 と将来性 つい最近、NTT DoCoMo から新型機種シリーズ N504i が発売された事は記憶に 新しい。N504i は通信速度が従来の 9.6Kbps から 28.8Kbps まで高速化し i アプリ の容量も3倍の 30k バイトまで拡大された。i アプリサイズは従来の 10K バイトか ら 30K バイトに拡大。また、アプリケーション用の記憶領域であるスクラッチパッ ドサイズは 100K バイトと大幅に拡大された。30K バイトという容量は、単に従来 の 3 倍というわけではなく、サイズが拡大すると JAR の圧縮効率が高まるため、 従来の 3 倍以上のプログラムが格納できる様になるのである。また i アプリの仕様 である DoJa もバージョンが 2.0 へと進化し、電話機本体も全機種が JPEG 画像の 表示に対応した。DoJa-2.0 では、時計アプリケーションをはじめとする常駐タイプ のアプリケーションを効率よく実装するため「待ち受けアプリケーション」をサポ ートした。従来の i アプリでもアプリケーションを常に実行し続けることで常駐さ せることは可能だったが、ドコモは以下のような問題があったという。( 2 ) • 電話をかけたり Web ブラウズを行う際に、ユーザーがアプリケーションを終 了しなくてはならない • アプリケーション実行中はメールの着信を受けることができない • 消費電力が大きく、長時間駆動ができない 504i の待ち受け i アプリでは、これらの問題を解決するための機構が採用された。 まず、i アプリ実行時でも電話機の操作が可能な「非活性状態」が用意された。504i には、この非活性化状態と i アプリが全面に出て操作可能な「活性化状態」を切り 替える「モード切り替えキー」(活性化ボタン)が用意される。 この機能により、 例えば待ち受け i アプリ起動時に Web ブラウザを起動すると、i アプリは終了し、 ブラウザが起動。ブラウザを終了すると、自動的に待ち受け i アプリが再起動する ようになっている。 また電力消費を抑制するため、アプリケーションの処理が一時 的に停止する「休眠状態」もサポートされた。休眠状態からは、一定時間が経過し た場合など特定のシステムイベントによって非活性化状態に復帰するようになって いる。 メール着信をリアルタイムでチェックできるよう、待ち受け i アプリでは非 活性化、休眠状態ではメールを受信できるようになった。また、活性化時や通常の i アプリを実行している場合でも、メールの着信を受けた場合、画面上のマークな どによりリアルタイムにユーザーに通知されるようになっている 。 (2) しかしなが ら、相変わらず機種依存が高く機種間での互換性が低い点やサーバーへのアクセス がダウンロードしたサーバーのみである点などまだまだ欠点も多い。そして 504i の 30K バイト化がもたらすのはメリットばかりではない。アプリケーションサイズ が 3 倍になったことで開発費も上昇してしまうのである。すでに NTTDoCoMo か らは 504i 向けの開発環境 DoJa2.0 API 向け iαppli Development Kit を公開しては いるが、容量が増加して本格的なアプリケーションが製作できるようになった分、 一般のクリエイターとコンテンツプロバイダーでアプリケーションの規模に差が出 そうである。i アプリの魅力である気軽さ・取り組み易さは少し失われた感は否め ない。だが、総合的に見ればこの 504 シリーズへのバージョンアップは劇的な性能 向上をもたらしたといえるのである。503 シリーズで携帯電話で Java が動くこと は実証された。将来的には本格的に Java を 有機的に動かし、電話帳やハードウェ アにアクセスできない現在の Java 仕様から,さまざまなデータやハードウェアに Java がアクセスし,連携して動作するようにになるであろう。 ドコモの i アプリ に代表される携帯 Java では,Java プログラムはほかのメモリやハードウェアから 分離し保護されている。これはもちろん悪意のある Java プログラムが作られる可 能性を考慮してのことである。 しかし NTT DoCoMo に遅れること半年で登場した KDDI や J-フォンの Java 仕様では,電話帳やハードウェアへのアクセスを限定的 ながら認めている。今後はセキュリティに配慮しながら,さまざまなデータに Java がアクセスできる方向に進みつつあるようだ。 Web ブラウザやメーラーを Java で 作ってしまおうという動きもある。 携帯電話のソフトウェアが,大規模化し開発難 度が増しているのは周知の事実であり、頻発するバグもその難しさを証明している。 機能の似通ったアプリケーションは使い回せるようにしたほうが制作効率も増すが、 組み込み機器である携帯電話はハードウェアも各機種バラバラな上 OS も異なる。 同じアプリケーションを動作させるには、Java のようにハードウェアや OS の違い を吸収してくれるプラットフォームを使うのが簡単であるからだ。 現在の携帯電話 で,このような形で Java を利用するのはプログラムのサイズ的な問題でできない が,方向性としては正しいといえるだろう。これからの携帯電話は,メモリ容量も 急速に拡大し、アプリケーションプロセッサが搭載されるなど速度も改善されてい く。今後、携帯電話のほとんどのソフトウェアが Java で書かれる日がやってくる かもしれない。( 2 ) 7. むすび 本論文において携帯電話用アプリケーション”i アプリ”が JAVA を理解するうえ で非常に身近な素材であり、優秀な物であると言うことが改めて理解できた。iア プリは非常に容量が少なく、とっつき易いアプリケーション製作であるといえるだ ろう。その上、自分の製作した物をネット上に公開すればたくさんの人にそのアプ リケーションを利用してもらう可能がある。一般的な PC 用のシェアウエアやフリー ソフトに比べて、i アプリは基本的に携帯電話に付属しているアプリケーションで その目的はある程度限定される。待ち時間の暇つぶしやパッと使える機能性などを 考えると必然的にその種類は暇つぶしにつかえるゲームやユーティリーソフトなど の簡易的なものに留まるだろう。しかし、10k バイトという小さい容量はスクラッ チパッドをいれると合計で 15∼20kの容量制限があるため JAVA 言語に深く精通し ている人はより複雑で高度なアプリケーションを組む事が可能である。それはコン テンツプロバイダーと個人のクリエイターの作品を見比べることで悠然と物語って いる。本当に i アプリを理解し、クオリティの高いアプリケーションを製作するに は製作環境を含めツールも理解する事が必要不可欠であり、我々が製作初期の段階 から製作環境の整備に苦しんだのもそれが問題であった。完成した i アプリを実際 にサーバーにアップロードをするには FTP ソフトを使ってバイナリモードとアスキ ーモードに切り替えて作業する知識も HTML の知識も必要である。またプログラムを 格納するサーバーも必要であるし、実際の実行には i アプリに対応した携帯電話も 必要である。我々は研究当初、i アプリの実行可能な携帯電話を持っている者が居 なく、i アプリをアップロード可能なサーバースペースを持っている者も居なかっ た為、エミュレーター上でしか、i アプリを実行することが出来なかった。これら の部分に関しては、プログラミング能力以外にも必要とされる事が多いため初心者 には厳しい部分かも知れない。しかし、それを除いても i アプリには高い携帯電話 普及率があり、i アプリを利用するユーザーがコンピュータの知識を必要としなく ても良いという利点があるのである。プログラミング技術だけではなく少ない容量 にいかにアイデアを詰め込むかという独創性も重要とされるため、一般ユーザーに より開発された物がプロのクリエイターの物より優れている場合もある所が i アプ リの面白い所である。また、どんなにパソコンを利用しないと人でも携帯電話を利 用している人は多い。その点においては携帯電話さえ使えれば利用できる i アプリ は不特定多数のユーザーに利用してもらう可能性があるという点において、優秀と 言えるだろう。最近は i アプリ関係の本が書店に所狭しと並べられ、NTT ドコモの 504 シリーズが発売されてから短い期間に 504 用の i アプリプログラミングの本が すでに何冊も発行されている。5O4 環境での i アプリは 503 を遙かに凌駕するアプ リケーションの製作も可能である。だが、個人レベルのプログラマーによるアプリ ケーションの開発と公開においては高性能化はメリットそれほど無く、むしろ新機 種導入における i アプリ対応機種の所持率の増加を歓迎すべきである。このまま携 帯電話が多機能、高性能化していけば、いずれは PDA 並の性能を獲得するようにな るだろうし、テレビ電話や映像の受信端末までマルチメディア分野でも携帯できる 端末と化す。現代の携帯電話普及率は高く、人々がそれをつねに携帯し持ち運ぶな らば携帯電話はウェアラブル PC と呼んでも差し支えない物なると言える。そのウェ アラブル PC の中核ともいえるアプリケーションを個人レベルで開発公開できる i アプリはこれからの高度情報化社会において個人がクリエイターとなるもっとも身 近な手段の一つだといえるだろう。我々は全くの素人からiアプリの製作を開始し、 試行錯誤を繰り返しながらアプリケーションを開発していくにあたってiアプリが JAVA の学習の糸口になるだけでなく誰でもが、プログラマーやクリエイターになれ る可能性があると思えた。これはインターネットが普及して情報化が進む現代にお いて身近にある携帯電話が立派な情報端末として機能している事の象徴であり、可 能性なのである。この事を理解することが出来た、本研究は非常に有意義な物であ ったといえる。これで本論文における考察を終えるものとする。 8.参考文献 (1)i-Appli からはじめよう http://www2n.biglobe.ne.jp/ ezaki/i-appli/ (2)ZDNet Mobile http://www.zdnet.co.jp/mobile/ (3)Jbuilder5 で入門!Java プログラミング (4)ゲーム作りで学ぶ i アプリプログラミング ソーテック社刊 株式会社SCC刊 9.付録 本論文で製作した i アプリ3点のエミュレーター上での画面とプログラムソース を以下に添付する。 9.1 ガンシュー ティングア プリ 画面 画像 ガンシューテ ィングアプ リプログラ ムソース import com.nttdocomo.ui.*; import com.nttdocomo.io.*; import java.util.Random; public class GunShooting extends IApplication { public static GameCanvas canvas; public void start() { PhoneSystem.setAttribute( PhoneSystem.DEV_BACKLIGHT, PhoneSystem.ATTR_BACKLIGHT_ON ); canvas = new GameCanvas(); Display.setCurrent( canvas ); } } class GameCanvas extends Canvas implements Runnable { private static int selected_mode; private static boolean first_flag; private static int next_appear; private static int appear_rate; private static int target_state[]; private static int appear_sum; private static int hit; private static boolean shoot; private static Image title_logo; private static Image target[]; private static int score; private static ShortTimer timer; private static boolean timer_flag; private static AudioPresenter ap; private static int scene; private static final Font font = Font.getFont( Font.TYPE_DEFAULT ); private static final Random rand = new Random(); private static final int INIT_VALUE = -1, TITLE = 0, INIT_HOLE = 1, PLAY = 2, END = 3; private static int DISPLAY_WIDTH = Display.getWidth(), DISPLAY_HEIGHT = Display.getHeight(); private static final int FONT_HEIGHT = font.getHeight(); private static final int EASY_MODE_RATE = 20, NORMAL_MODE_RATE = 15, EXPERT_MODE_RATE = 10; private static final int APPEAR_TARGET = 16, APPEAR_TARGET_1 = 15, APPEAR_TARGET_2 = 14, APPEAR_TARGET_3 = 13, APPEAR_TARGET_4 = 12, APPEAR_TARGET_5 = 10, APPEAR_TARGET_6 = 8, APPEAR_TARGET_7 = 6; private static final int RETURN_TARGET_4 = 4, RETURN_TARGET_3 = 3, RETURN_TARGET_2 = 2, RETURN_TARGET_1 = 1, RETURN_TARGET_0 = 0; private static final int HIT_TARGET = 25, HIT_TARGET_END = 22; private static final int MISS_SHOOT_1 = 21, MISS_SHOOT_2 = 19, MISS_SHOOT_END = 17; GameCanvas() { MediaImage mi = MediaManager.getImage("resource:///title.gif"); try{ mi.use(); } catch( ConnectionException e ){} title_logo = mi.getImage(); target = new Image[9]; for( int i = 0; i < 9; i++ ){ mi = MediaManager.getImage("resource:///target"+i+".gif"); try{ mi.use(); } catch( ConnectionException e ){} target[i] = mi.getImage(); } MediaSound ms = MediaManager.getSound("resource:///shoot.mid"); try{ ms.use(); } catch(Exception e){} ap = AudioPresenter.getAudioPresenter(); ap.setSound( ms ); setSoftLabel( Frame.SOFT_KEY_1, "TOP" ); setSoftLabel( Frame.SOFT_KEY_2, "終了" ); timer_flag = false; initCanvas(); } public void initCanvas(){ scene = TITLE; shoot = false; next_appear = 1; selected_mode = 0; score = 0; appear_sum = 0; first_flag=true; hit = INIT_VALUE; target_state = new int[9]; for( int i = 0; i < 9; i++ ){ target_state[i] = INIT_VALUE; } setBackground( Graphics.getColorOfName( Graphics.WHITE ) ); } public void paint( Graphics g ) { g.lock(); if( scene == TITLE ){ g.clearRect( 0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT ); g.drawImage( title_logo, 5, 10 ); g.setColor( Graphics.getColorOfName( Graphics.RED ) ); g.fillRect( 10, 60+selected_mode*20-FONT_HEIGHT, FONT_HEIGHT ); g.setColor( Graphics.getColorOfName( Graphics.BLACK ) ); g.drawString( "Easy mode", 10, 60 ); g.drawString( "Normal mode", 10, 80 ); g.drawString( "Expert mode", 10, 100 ); } else if( scene == PLAY ){ if(first_flag){ g.clearRect( 0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT ); for( int i = 0; i < 3; i++ ){ 100, for( int j = 0; j < 3; j++ ){ g.drawImage( target[0], j*(target[0].getWidth()+5)+7, i*target[0].getHeight() ); } } g.setColor( Graphics.getColorOfName( Graphics.BLACK ) ); g.drawLine( 0, 3*target[0].getHeight()+5, DISPLAY_WIDTH, 3*target[0].getHeight()+5 ); g.setColor( Graphics.getColorOfName( Graphics.BLACK ) ); g.drawString( " 出 現 回 数 ", 5, DISPLAY_HEIGHT-FONT_HEIGHT ); g.drawString( "的中回数", 5, DISPLAY_HEIGHT ); first_flag=false; } for( int i = 0; i < 9; i++ ){ if( target_state[i] != INIT_VALUE ){ g.drawImage( target[calcTargetState(i)], i%3*(target[0].getWidth()+5)+7, (int)i/3*target[0].getHeight() ); } } g.clearRect( 80, DISPLAY_HEIGHT-FONT_HEIGHT*2, 30, FONT_HEIGHT*2 ); g.setColor( Graphics.getColorOfName( Graphics.BLACK ) ); g.drawString( appear_sum+"", DISPLAY_HEIGHT-FONT_HEIGHT ); g.drawString( score+"", } else if( scene == END ){ 80, DISPLAY_HEIGHT ); 80, g.setColor( Graphics.getColorOfName( Graphics.YELLOW ) ); g.fillRect( 0, 60-FONT_HEIGHT, DISPLAY_WIDTH, FONT_HEIGHT*2 ); g.setColor( Graphics.getColorOfName( Graphics.BLACK ) ); g.drawString( " 終 了 ", DISPLAY_WIDTH/2-font.stringWidth(" 終 了 ")/2, 60 ); int hit_rate = (int)100*score/appear_sum; g.drawString( "HIT 率"+hit_rate+"%", 0, 60+FONT_HEIGHT ); } g.unlock( true ); } public void run(){ while( scene == PLAY ){ for( int i = 0; i < 9; i++ ){ if( target_state[i] != INIT_VALUE ){ --target_state[i]; if( target_state[i] == HIT_TARGET_END || target_state[i] == MISS_SHOOT_END ){ shoot = false; target_state[i] = RETURN_TARGET_0; } } } if( --next_appear <= 0 ){ next_appear = Math.abs(rand.nextInt())%9; if( target_state[next_appear] == INIT_VALUE ){ target_state[next_appear] = APPEAR_TARGET; ++appear_sum; } next_appear = Math.abs(rand.nextInt()) % appear_rate; } if( hit != INIT_VALUE ){ if( !shoot ){ if( target_state[hit] >= 0 ){ target_state[hit] = HIT_TARGET; ringSound(); ++score; } else if( target_state[hit] == INIT_VALUE ){ target_state[hit] = MISS_SHOOT_1; ringSound(); } } hit = INIT_VALUE; shoot = true; } repaint(); try{Thread.sleep( 100 ); }catch(InterruptedException e){} } } public void processEvent( int type, int param ){ if( type == Display.KEY_PRESSED_EVENT ){ if( param == Display.KEY_SOFT1 && (scene == PLAY || scene == END)){ stopTimer(); initCanvas(); repaint(); } else if( param == Display.KEY_SOFT2 ){ scene = INIT_VALUE; IApplication.getCurrentApp().terminate(); } else if( scene == TITLE ){ if( param == Display.KEY_UP ){ if( --selected_mode < 0 ) selected_mode = 2; repaint( 0, 60-FONT_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT - (60-FONT_HEIGHT) ); ringSound(); } else if( param == Display.KEY_DOWN ){ if( ++selected_mode > 2 ) selected_mode = 0; repaint( 0, 60-FONT_HEIGHT, DISPLAY_HEIGHT - (60-FONT_HEIGHT) ); ringSound(); } else if( param == Display.KEY_SELECT ){ startGame(); } } else if( scene == PLAY ){ for( int i = 0; i < 9; i++ ){ if( param == Display.KEY_1+i ){ DISPLAY_WIDTH, hit = i; } } } } else if( type == Display.TIMER_EXPIRED_EVENT ){ if( param == 1 ){ scene = END; repaint(); } } } public void startGame(){ setBackground( Graphics.getColorOfName( Graphics.WHITE ) ); setAppearRate(); startTimer(); scene = PLAY; Thread thread = new Thread( this ); thread.start(); } public void setAppearRate(){ if( selected_mode == 0 ) appear_rate = EASY_MODE_RATE; if( selected_mode == 1 ) appear_rate = NORMAL_MODE_RATE; if( selected_mode == 2 ) appear_rate = EXPERT_MODE_RATE; } public int calcTargetState( int hole ){ if( target_state[hole] > HIT_TARGET_END ) return 6; else if( target_state[hole] > MISS_SHOOT_END ){ if( target_state[hole] > MISS_SHOOT_2 ) return 7; else return 8; } else if( target_state[hole] < RETURN_TARGET_1 || target_state[hole] >= APPEAR_TARGET_1 ) return 0; else if( target_state[hole] < RETURN_TARGET_2 || target_state[hole] >= APPEAR_TARGET_2 ) return 1; else if( target_state[hole] < RETURN_TARGET_3 || target_state[hole] >= APPEAR_TARGET_3 ) return 2; else if( target_state[hole] < RETURN_TARGET_4 || target_state[hole] >= APPEAR_TARGET_4 ) return 3; else if( (target_state[hole]<APPEAR_TARGET_6 target_state[hole]>=APPEAR_TARGET_7) || target_state[hole] >= APPEAR_TARGET_5 ) return 4; else return 5; } && public void startTimer(){ if(!timer_flag){ timer = ShortTimer.getShortTimer( this, 1, 25000, false ); timer.start(); timer_flag = true; } } public void stopTimer(){ if(timer_flag){ timer.stop(); try{ timer.dispose(); }catch(Exception e){} timer_flag = true; } } public void ringSound(){ ap.play(); } } 9.2 カーレース アプリ 画面 画像 カー レースアプ リプログラ ムソース import java.io.*; import java.util.*; import javax.microedition.io.Connector; import com.nttdocomo.ui.*; //アプリカート(キャンバス) final class AppliCartCanvas extends Canvas { //スタートフラグ //-------------------private static boolean start; //描画 public void paint(Graphics g) { } //実行 void exe() { //==================== //変数の宣言 //==================== //グラフィックスの取得 //-------------------Graphics g=getGraphics(); //システム変数 //-------------------int i,j,key; //ワーク変数 int scene =0;//シーン(0:タイトル,1:リタイヤ,2:完走,3:プレイ準備,4:プレイ) int anime =0;//アニメ int score =0;//スコア int hiscore =0;//ハイスコア int speed =1;//スピード long startTime=0;//スタート時間 //オブジェクト //-------------------Image[] image=new Image[6];//イメージ Random rand =new Random();//乱数 String[] msg ={ //メッセージ "アプリカート", "リタイヤ!!!", "完走"}; //フォント //-------------------Font font=Font.getFont(Font.FACE_MONOSPACE│Font.SIZE_SMALL); g.setFont(font); //プレイヤーの車の変数 //-------------------int myX=60;//位置 X //敵の車の変数 //-------------------int[] tekiX =new int[4]; //位置 X int[] tekiY =new int[4]; //位置 Y int[] tekiVY =new int[4]; //速さ Y int[] tekiState={-1,-1,-1,-1};//状態(-1:無,0:小,1:中,2:大) try { //画像ファイルの読み込み //-------------------MediaImage m; for (i=5;i>=0;i--) { m=MediaManager.getImage("resource:///"+i+".gif"); m.use(); image[i]=m.getImage(); } //ハイスコアの読み込み //-------------------hiscore=readHiscore(); //ソフトラベル //-------------------setSoftLabel(SOFT_KEY_1,"終了"); setSoftLabel(SOFT_KEY_2,"スタート"); //シーンをタイトルに移す //-------------------scene=0; start=false; while (true) { //==================== //背景の描画 //==================== //ロック //-------------------g.lock(); //地面 //-------------------i=anime%4*5; g.setColor(g.getColorOfRGB(255,150,0)); g.fillRect(0,65,120,55); g.setColor(g.getColorOfRGB(255,210,120)); g.fillRect(60, 40+i,60,10); g.fillRect( 0, 50+i,60,10); g.fillRect(60, 60+i,60,10); g.fillRect( 0, 70+i,60,10); g.fillRect(60, 80+i,60,10); g.fillRect( 0, 90+i,60,10); g.fillRect(60,100+i,60,10); g.fillRect( 0,110+i,60,10); //ライン //-------------------g.setColor(g.getColorOfRGB(255,100,0)); g.drawLine(0, 120,35,65); g.drawLine(120,120,85,65); //空 //-------------------g.setColor(g.getColorOfRGB(9,192,255)); g.fillRect(0,0,120,65); g.drawImage(image[5],0,45); //==================== //プレイ //==================== if (scene==4) { for (j=3;j>=0;j--) { //敵の車の表示 //-------------------if (tekiState[j]>=0) { //移動 tekiY[j]+=(tekiVY[j]+tekiState[j])*speed; tekiX[j]+=(rand.nextInt()>>>1)%8-4; if (tekiX[j]<50) { tekiX[j]-=1; } else if (tekiX[j]<70) { tekiX[j]+=1; } //状態 if (tekiY[j]>120) { tekiState[j]=-1; score++; } else if (tekiY[j]>90) { tekiState[j]=2; } else if (tekiY[j]>70) { tekiState[j]=1; } else { tekiState[j]=0; } //表示 if (tekiState[j]>=0) { g.drawImage(image[tekiState[j]], tekiX[j]-image[tekiState[j]].getWidth()/2, tekiY[j]); } } //敵の車の出現 //-------------------else if ((rand.nextInt()>>>1)%100<5) { tekiX[j] =35+(rand.nextInt()>>>1)%42; tekiY[j] =60; tekiVY[j]=(rand.nextInt()>>>1)%4+1; tekiState[j]=0; } //プレイヤーの車と敵の車の衝突判定 //-------------------if (tekiState[j]==2 && (myX-tekiX[j])*(myX-tekiX[j])+ (95 -tekiY[j])*(95-tekiY[j])<200) { //点滅処理 for (i=6;i>=0;i--) { if (i%2==0) { g.setColor(g.getColorOfName(g.RED)); } else { g.setColor(g.getColorOfName(g.WHITE)); } g.fillRect(0,0,120,130); Thread.yield(); g.unlock(true); g.lock(); } //シーンをリタイヤに移す if (hiscore<score) hiscore=score; writeHiscore(hiscore); scene=1; start=false; } } //プレイヤーの車の操作 //-------------------//キー状態の取得 key=getKeypadState(); //左へ移動 if (((1<<Display.KEY_LEFT│1<<Display.KEY_1)&key)!=0) { myX-=10; if (myX<10) myX=10; } //右へ移動 else if (((1<<Display.KEY_RIGHT│1<<Display.KEY_5)&key)!=0) { myX+=10; if (myX>110) myX=110; } //ターボ if (((1<<Display.KEY_SELECT│1<<Display.KEY_POUND)&key)!=0) { speed=2; } //通常スピード else { speed=1; } //プレイヤーの車の表示 //-------------------g.drawImage(image[3],myX-12,95); //時間とスコアの表示 //-------------------i=(int)((30000+startTime-System.currentTimeMillis())/1000); if (i<=0) { //シーンを完走に移す。 i=0; if (hiscore<score) hiscore=score; writeHiscore(hiscore); scene=2; start=false; } g.setColor(g.getColorOfName(g.BLUE)); g.drawString("時間:"+i,2,13); g.drawString(score+"台抜き", 118-font.stringWidth(score+"台抜き"),13); //アニメ //-------------------anime+=speed; if (anime>99999) anime=0; } //==================== //プレイ準備 //==================== else if (scene==3) { //各種変数の初期化 //-------------------anime=0; score=0; speed=1; myX =60; for (i=3;i>=0;i--) { tekiX[i] =0; tekiY[i] =0; tekiVY[i] =0; tekiState[i]=-1; } startTime=System.currentTimeMillis(); //シーンをプレイに移す //-------------------scene=4; } //==================== //タイトル・リタイヤ・完走 //==================== else if (scene<=2) { //メッセージの表示 //-------------------i=(120-font.stringWidth(msg[scene]))/2; g.setColor(g.getColorOfName(g.RED)); g.drawString(msg[scene],i+1,30); g.drawString(msg[scene],i-1,30); g.drawString(msg[scene],i, 31); g.drawString(msg[scene],i, 29); g.setColor(g.getColorOfName(g.WHITE)); g.drawString(msg[scene],i, 30); //スコアとハイスコアの表示 //-------------------g.setColor(g.getColorOfName(g.RED)); g.drawString("ハイスコア:"+hiscore,40, 48); g.drawString("スコア :"+score, 40, 61); //イメージの表示 //-------------------if (scene==1) { g.drawImage(image[4],myX-24,95); } else { g.drawImage(image[3],myX-12,95); } //スタート //-------------------if (start) scene=3; } //==================== //その他の処理 //==================== //画面外塗り潰し //-------------------g.setColor(g.getColorOfName(g.BLACK)); g.fillRect(0,120,getWidth(),getHeight()-119); g.fillRect(120,0,getWidth()-119,120); //アンロック //-------------------- g.unlock(true); //スリープ時間は機種ごとに調整 //-------------------Thread.yield(); //==================== } } catch (Exception e) { } } //ハイスコアの読み込み private static int readHiscore() { InputStream in =null; int result=0; try { in=Connector.openInputStream("scratchpad:///0"); result=in.read(); in.close(); } catch (Exception e2) { try { if (in!=null) in.close(); } catch (Exception e) { } } return result; } //ハイスコアの書き込み private static void writeHiscore(int hiscore) { OutputStream out=null; try { out=Connector.openOutputStream("scratchpad:///0"); out.write(hiscore); out.flush(); out.close(); } catch (Exception e) { try { if (out!=null) out.close(); } catch (Exception e2) { } } } //イベント処理 public synchronized void processEvent(int type, int param) { //キープレスイベント //-------------------if (type ==Display.KEY_PRESSED_EVENT) { //ソフトキー1 if (param==Display.KEY_SOFT1) { //終了 IApplication.getCurrentApp().terminate(); } //ソフトキー2 else if (param==Display.KEY_SOFT2) { //スタートフラグ start=true; } } } } 9.3 情報発信アプリ 画面画像 お知 らせアプリ プログラム ソース //#ifdef p503i // #include "p503i.h" //#endif //#ifdef f503i // #include "f503i.h" //#endif //#ifdef n503i // #include "n503i.h" //#endif //#ifdef so503i //#include "so503i.h" //#endif //#ifdef d503i // #include "d503i.h" //#endif //#ifdef p503is // #include "p503is.h" //#endif import java.io.*; import java.util.*; import javax.microedition.io.Connector; import com.nttdocomo.ui.*; import com.nttdocomo.io.HttpConnection; //お知らせアプリ(キャンバス) final class OsiraseAppliCanvas extends Canvas { private static boolean keyPressed;//キープレスフラグ //描画 public void paint(Graphics g) { } //実行 void exe() { //グラフィックスの取得 //-------------------Graphics g=getGraphics(); //変数の宣言 //-------------------int i,j,k; //ワーク変数 String str; //ワーク文字列 Image image[];//イメージ String[] data; //データ String[] text=new String[3]; //フォント //-------------------Font font=Font.getFont(Font.SIZE_SMALL│Font.FACE_MONOSPACE); g.setFont(font); //ソフトラベル //-------------------setSoftLabel(SOFT_KEY_1,"終了"); //イメージの読み込み //-------------------image=new Image[3]; try { MediaImage m; for (i=2;i>=0;i--) { m=MediaManager.getImage("resource:///"+i+".gif"); m.use(); image[i]=m.getImage(); } } catch (Exception e) { } //最新情報の取得 //-------------------str=read(IApplication.getCurrentApp().getArgs()[0]); data=parseString(str,'¥n'); //最新情報かどうかのチェック //-------------------if (checkDate(data[0])) { //バイブレーション //-------------------PhoneSystem.setAttribute(64,1); wait(2000); PhoneSystem.setAttribute(64,0); wait(300); //音の再生 //-------------------try { MediaSound sound; sound=MediaManager.getSound("resource:///sound.mld"); sound.use(); AudioPresenter audio; audio=AudioPresenter.getAudioPresenter(); audio.setSound(sound); audio.play(); } catch (Exception e) { } //メッセージの表示 //-------------------for (i=1;i<data.length;i++) { if (data[i].length()<2) continue; //クリア //-------------------g.lock(); g.setColor(g.getColorOfRGB(180,255,180)); g.fillRect(0,0,120,120); g.drawImage(image[0],(120-120)/2,120-60); g.unlock(true); wait(500); //吹き出し //-------------------g.lock(); g.drawImage(image[1],(120-120)/2+5,5); if (data[i].charAt(0)=='0') { g.drawImage(image[2],(120-120)/2+20,64); } else { g.drawImage(image[2],(120-120)/2+89,64); } g.unlock(true); wait(500); //メッセージ //-------------------keyPressed=false; str =data[i].substring(1); g.setColor(0); //テキスト //-------------------k=0; int l; for (j=0;j<3;j++) { if (k<str.length()) { l=k; k=font.getLineBreak(str,k,str.length()-k,98); text[j]=str.substring(l,k); }else { text[j]=""; } } for (j=0;j<3;j++) { for (k=1;k<=text[j].length();k++) { g.drawString(text[j].substring(0,k), (120-120)/2+12,j*13+23); if (!keyPressed) wait(50); } } //矢印 //-------------------g.setColor(g.getColorOfRGB(255,170,0)); g.drawString("▼",(120-120)/2+96,62); //キープレス待ち //-------------------keyPressed=false; while (!keyPressed) wait(300); } } //終了 //-------------------IApplication.getCurrentApp().terminate(); } //==================== //通信 //==================== //ネットからテキストを読み込む private String read(String url) { //ストリーム //-------------------InputStreamReader in=null; HttpConnection c =null; try { //接続 //-------------------c=(HttpConnection)Connector.open(url,Connector.READ,true); c.setRequestMethod(HttpConnection.GET); c.connect(); in=new InputStreamReader(c.openInputStream()); //読み込み //-------------------StringBuffer sb=new StringBuffer(500); int i=in.read(); while (i>=0) { sb.append((char)i); i=in.read(); } //切断 //-------------------in.close(); c.close(); return sb.toString(); } catch (Exception e) { //例外処理 //-------------------try { if (in!=null) in.close(); if (c !=null) c.close(); } catch (Exception e2) { } return ""; } } //==================== //文字列を任意の文字で分割 //==================== private static String[] parseString(String str,char sep) { int i,j,size; String[] result; //最後尾に分割文字 //-------------------if (str.equals("")││str.charAt(str.length()-1)!=sep) str+=sep; //サイズを得る //-------------------size=0; i=str.indexOf(sep); while (i>=0) { size++; i=str.indexOf(sep,i+1); } //分割する //-------------------result=new String[size]; size=0; j=0; i=str.indexOf(sep); while (i>=0) { result[size++]=str.substring(j,i); j=i+1; i=str.indexOf(sep,j); } return result; } //==================== //スクラッチパッド //==================== //まだ知らせていない情報かどうかを調べる private boolean checkDate(String date) { InputStream in OutputStream out String =null; =null; prevDate="0"; //前回更新日の取得 //-------------------try { in=Connector.openInputStream("scratchpad:///0;pos=0"); byte[] d=new byte[10]; in.read(d); prevDate=new String(d); in.close(); } catch (Exception e) { try { if (in!=null) in.close(); } catch (Exception e2) { } } if (date.equals(prevDate)) return false; //更新日を上書き //-------------------try { out=Connector.openOutputStream("scratchpad:///0;pos=0"); out.write(date.getBytes()); out.flush(); out.close(); } catch (Exception e) { try { if (out!=null) out.close(); } catch (Exception e2) { } } return true; } //スリープ private static void wait(int time) { try { Thread.sleep(time); } catch (Exception e) { } } //キーイベントの処理 public void processEvent(int type, int param) { //キープレスイベント //-------------------if (type==Display.KEY_PRESSED_EVENT) { //終了 if (param==Display.KEY_SOFT1) { IApplication.getCurrentApp().terminate(); } //キープレスフラグ else { keyPressed=true; } } } }
© Copyright 2024 Paperzz