このページではGWTでゲーム作成するときに必要となるであろう情報についてまとめています。GWTで作ったサンプルとプログラムをお見せし、具体的に説明したいと思います。
まず、GWTで作成したサンプルをお見せします。Java Scriptを利用しているので、Java Scriptを利用できないと機能しません。矢印キーやクリック、ダブルクリックに反応します。もし、反応しないのであれば、アクティブになっていないのかもしれません。サンプルをクリックしてアクティブにしてください。各要素については途中で部分的に取り上げて説明します。実際のプログラムは最後に掲載します。
Eclipseへのインストール
Eclipseへのインストールの手順について説明します。
- Eclipse マーケットプレースを選択
ヘルプ>Eclipse マーケットプレースを選択します。
 - Eclipse マーケットプレースでGWTを検索
赤枠の箇所にGWTと入力し、緑枠の"Go"をクリックします。
 - GWT Eclipse pluginをインストール
赤枠の箇所をクリックして、出てきた画面でインストールするものを選択してインストールします。画像では赤枠の箇所はインストール済みになっていますが、インストールしていなければインストールと表示されています。
.gif)
私の場合、Eclipse 4.8(Photon)に導入しましたが、SDK2.7.0、SDK 2.8.1を両方インストールしようとすると、エラーでできませんでした。結局、SDK 2.8.1しかインストールしませんでした。
 
GWTアプリケーションプロジェクト作成の手順
GWTアプリケーションプロジェクトの作成手順について説明します。
- GWTアプリケーションプロジェクトを新規作成するためのウィンドウを表示する
手順は2通りあります。まず1つ目について説明します。
ファイル>新規>プロジェクトを選択します。

次にウィザード選択画面で赤枠の"GWT Web Application Project"をクリックします。次に、緑枠の"次へ"をクリックします。

次に2つ目について説明します。
緑枠の箇所をクリックして出てきたメニューの"New GWT Application Project"を選択します。
 - 必要項目を記入してGWTアプリケーションプロジェクトを新規作成
1のどちらの方法でも下の画像のようなウィンドウが出てきます。青枠部分の記入、選択を行います。パッケージ名にルールがあるのかは不明です。好きに入れて動かないということはなさそうです。また、緑枠の"プロジェクト・サンプル・コードを生成する"は必ずチェックを入れてください。私の環境ではこれを外すとエラーが発生して作成できませんでした。(2018/08/26現在)最後に、赤枠部分の"完了"をクリックします。
 
プログラム作成前の作成されたGWTアプリケーションプロジェクトの調整
プログラムの作成の前に行う作成されたGWTアプリケーションプロジェクトの調整について説明します。
上の新規GWTアプリケーションプロジェクトの作成手順で作成すると、下の画像のような構成で自動で作成されます。
もし、サーバー側で何か処理をする必要がなく、サイトにアクセスしてきたユーザーのパソコン内でJava Scriptが処理を実行してくれればよいのであれば、client以外のものは不要です。上の画像で言うと、howToCreate.server、howToCreate.sharedは不要です。サンプルコードも不要なので削除します。上の画像で言うと、howToCreate.client内のGreetingService.java、GreetingServiceAsync.javaは不要です。それらを実行すると下の画像のような構成になります。
サーバーと連携する場合に必要なものは削除したので他のファイルの中身を調整する必要があります。以下でそれを説明します。
- gwt.xmlの調整
このサンプルで言うと、howToCreate.HowToCreateAGame.gwt.xmlを調整します。下の画像の青枠部を削除します。
変更前
.gif)
変更後
.gif)
 - web.xmlの調整
このサンプルで言うと、war.WEB-INF.lib.web.xmlを調整します。下の画像の青枠部を削除します。
変更前
.gif)
変更後
.gif)
 
また、GWTアプリケーションプロジェクトをコンパイルするとキャッシュがTempフォルダに溜まっていきます。何もしなければ普段のインターネットのキャッシュと同様に保存されますが、キャッシュのせいで変更が反映されないということがあるようです。その場合にキャッシュを削除したくなるので、保存場所を指定しておきます。デバッグの構成、あるいは実行の構成からGWT Development modeの設定を行います。GWT Development modeで実行したことがないと設定できないようなので、その場合は後の記述を見ながら実行してください。
設定前
.gif)
設定後
.gif)
これでいよいよGWTアプリケーションの作成に入ることができます。
GWTアプリケーションのmain関数的な関数
このサンプルで言うと、howToCreate.client.HowToCreateAGame.java内のpublic void onModuleLoad()がmain関数的な関数です。サンプルコードにも自動的に作成されていますので、参考になります。
public class HowToCreateAGame implements EntryPoint,AnimationCallback {
	private AnimationHandle handler;
	
	public void onModuleLoad()
	{
		init();
		if(bInitHasFailed==true)
		{
			return;
		}
	    execute(0);
	//        executeByTimer();
	}
	
	@Override
	public void execute(double timestamp)
	{
		handler=AnimationScheduler.get().requestAnimationFrame(this);
		run();
	}
    private void executeByTimer()
    {
    	final Timer timer = new Timer()
        {
            @Override
            public void run()
            {
            	loopProcess();
            }
        };
        timer.scheduleRepeating(1);
    }
}
onModuleLoad()内では初期化とループ(execute関数)をしています。executeとexecuteByTimerはどちらもループ処理ができますが、Timerを使うより、AnimationSchedulerを使うほうがパフォーマンス的には良いようです。timeStampはどのように使うのかわかりません。何を入れてもとりあえずループするようです。AnimationCallbackをimplementsしていることにご注意ください。Timerを使うときはAnimationCallbackをimplementsする必要はありません。
init()は初期化の関数、loopProcess()はループ処理の関数です。何もOverrideをしていない、自作の関数で、今の説明では重要ではないです。中身が知りたければ、最後に載せたサンプルのコードをご覧ください。
canvasの説明
canvasについて説明します。canvasは図形の描画や画像の描画、文字の描画ができます。ゲーム画面として使うことになると思います。
初期化は以下の通りです。howToCreate.client.HowToCreateAGame.java内のinit関数内でprepareCanvas()を呼び出しています。
public class HowToCreateAGame implements EntryPoint,AnimationCallback {
	Canvas canvas;
    Context2d context;
    public static final int canvasHeight = 300;
    public static final int canvasWidth = 300;
    static final String divTagId = "canvasExample"; // must match div tag in html file
    
private void init()
    {
    	prepareCanvas();
    }
private void prepareCanvas()
    {
    	canvas = Canvas.createIfSupported();
        if (canvas == null)
        {
              RootPanel.get().add(new Label("Sorry, your browser doesn't support the HTML5 Canvas element"));
              bInitHasFailed=true;
              return;
        }
        canvas.setStyleName("canvasExample");
        canvas.setWidth(canvasWidth + "px");
        canvas.setCoordinateSpaceWidth(canvasWidth);
        canvas.setHeight(canvasHeight + "px");
        canvas.setCoordinateSpaceHeight(canvasHeight);
        context = canvas.getContext2d();
        KeyEventListener=new KeyEventListener();
        MouseEventListener=new MouseEventListener();
        canvas.addKeyDownHandler(KeyEventListener);
        canvas.addKeyUpHandler(KeyEventListener);
        canvas.addClickHandler(MouseEventListener);
        canvas.addDoubleClickHandler(MouseEventListener);
        canvas.setFocus(true);
        RootPanel.get( divTagId ).add(canvas);
    }
}
注意点を以下に記載します。
1つ目は、canvasはコンストラクタを呼び出すのではなく、(newするのではなく)createIfSupported関数で作成するということです。Canvasを作成できない場合、canvasがnullになるのでその対応もしておきます。
2つ目は、divタグのIDを設定するということです。divTagId="canvasExample"として定義して、RootPanel.get( divTagId ).add(canvas);というように設定しています。これでhtmlファイルを適切に作成することでcanvasをブラウザ上で表示することができます。またcssファイルを適切に作成することで見た目の調整を行うことができます。
サンプルコードのhtmlファイル
.gif)
上から変更して今回のサンプル用にしたhtmlファイル
.gif)
サンプルコードのcssファイル
.gif)
上から変更して今回のサンプル用にしたcssファイル
.gif)
htmlに何を書いてcssに何を書くのかということが分からなければ、とりあえずメモ帳に書いても変わらないことはhtmlに、変わることはcssに記載するというように理解すればいいと思います。例えば、太文字にしたいというときにはメモ帳に太文字はないのでcssを調整することで実現します。また、中央揃えにしたいというときにも同様にcssを調整することで実現します。
このように、見た目の調整はすべてcssを調整することで実現します。見た目を調整したいのかどうかを考えて見た目を調整したければcssでどのように調整すればいいかgoogleなどで調べ、見た目を調整したいのでなければhtmlでどのように調整すればいいかgoogleなどで調べればいいと思います。cssやhtmlについて一生懸命勉強しなくても、必要なときに必要なことだけ調べればいいと思います。
3つ目は、キー入力やクリックのイベントの取得の方法です。canvas.addXXXHandler(引数)の引数にXXXHandlerをimplementsしたクラスを入れることによってイベントを取得できるようにしています。一例としてKeyEventListenerの中身は次のようになっています。
public class KeyEventListener implements KeyUpHandler,KeyDownHandler{
	@Override
	public void onKeyDown(KeyDownEvent event)
	{
		event.preventDefault();
		switch(event.getNativeKeyCode())
		{
			case KeyCodes.KEY_LEFT: Key.left=true; break;
			case KeyCodes.KEY_RIGHT: Key.right=true; break;
			case KeyCodes.KEY_UP: Key.up=true; break;
			case KeyCodes.KEY_DOWN: Key.down=true; break;
			case KeyCodes.KEY_S: Key.s=true; break;
		}
	}
	@Override
	public void onKeyUp(KeyUpEvent event)
	{
		event.preventDefault();
		switch(event.getNativeKeyCode())
		{
			case KeyCodes.KEY_LEFT: Key.left=false; break;
			case KeyCodes.KEY_RIGHT: Key.right=false; break;
			case KeyCodes.KEY_UP: Key.up=false; break;
			case KeyCodes.KEY_DOWN: Key.down=false; break;
			case KeyCodes.KEY_S: Key.s=false; break;
		}
	}
}
Key.leftやKey.rightはpublicのstatic boolean変数でキーが押されているとtrue、キーが押されていないとfalseになるプログラムになっています。
また、event.preventDefault()でイベント発生時に通常のブラウザの動作を行わないようにしています。したがって、今の矢印キーの場合、スクロールされなくなっています。
4つ目はcanvasのContext2dを取得しておくことです。これをしないと図形や文字が描画できません。Context2dのcontextに取得して代入しています。具体的にはcontext = canvas.getContext2d()としています。
canvasへの描画
canvasへの描画について説明します。
まず、共通することとして塗りつぶしの色を指定するときはcontext.setFillStyle(CssColor.make("rgba(255,0,0,1)")、枠線の色を指定するときはcontext.setStrokeStyle(CssColor.make("rgba(255,0,0,1)")とします。色の指定はCssColor.make("rgba(255,0,0,1)")というようにrgb値とアルファ値(透明度)を指定します。アルファ値は0~1の値を設定します。0だと完全に透明で見えなくなります。1だと完全に不透明です。今回のサンプルでは青色の三角形でアルファ値0.5を設定しました。
図形の線の太さはcontext.setLineWidth(double lineWidth)で指定できます。lineWidthは太さを表します。
- 線の描画
context.setStrokeStyle(Color);
context.beginPath();
context.moveTo(dx1, dy1);
context.lineTo(dx2, dy2);
context.closePath();
context.Stroke();
と書きます。context.moveTo(x1,y1)は座標x1、y1を始点とします。その点からcontext.lineTo(x2,y2)は始点からx2、y2まで線を引くと宣言します。context.Stroke()で実際に線が引かれます。
線を定義する直前にcontext.beginPath()、線を定義した直後にcontext.closePath()を書くのを忘れないようにしましょう。
 - 多角形の描画
塗りつぶしの多角形を描画する場合は
context.setFillStyle(Color);
context.beginPath();
if(dVertexX.length!=dVertexY.length)
{
return;
}
context.moveTo(dVertexX[0], dVertexY[0]);
for(int i=1;i<dVertexX.length;i++)
{
context.lineTo(dVertexX[i], dVertexY[i]);
}
context.closePath();
context.fill();
枠だけの多角形を描画する場合は
context.setStrokeStyle(Color);
context.beginPath();
if(dVertexX.length!=dVertexY.length)
{
return;
}
context.moveTo(dVertexX[0], dVertexY[0]);
for(int i=1;i<dVertexX.length;i++)
{
context.lineTo(dVertexX[i], dVertexY[i]);
}
context.closePath();
context.Stroke();
とします。
基本的には線を引いていき、図形を描いているイメージです。1つ前の線の描画も参照ください。
dVertexX,dVertexYは多角形の各頂点の座標を格納しています。
 - 四角形の描画
枠だけの四角形を描く場合は
context.setStrokeStyle(CssColor.make("rgba(255,0,0,1)"));
context.strokeRect(10, 10, 100, 40);
塗りつぶしの四角形を描く場合は
context.setFillStyle(CssColor.make("rgba(255,0,0,1)"));
context.fillRect(10, 10, 100, 40);
のようにします。 context.strokeRect(x,y,width,height)、context.fillRect(x,y,width,height)となっており、x、yは左上頂点の座標、widthが四角形の幅、heightが四角形の高さです。
 - 円、円弧の描画
context.arc(x,y,radius,startAngle,endAngle)でx、yが円の中心の座標、radiusは円の半径、startAngle,endAngleは描かれる円周の範囲を指定していてこの差が2PIを超えると円を描きます。
枠だけの円、円弧を描く場合は
context.setStrokeStyle(CssColor.make("rgba(255,0,0,1)"));
context.beginPath();
context.arc(190, 75, 25, Math.PI/4, Math.PI*3/4);
context.closePath();
context.stroke();
塗りつぶしの円、円弧を描く場合は
context.setFillStyle(CssColor.make("rgba(0,0,255,1)"));
context.beginPath();
context.arc(190, 75, 25, Math.PI/4, Math.PI*3/4);
context.closePath();
context.fill();
のようにします。
context.arcは5つ変数を指定できて、5つ目の変数は時計回りか反時計回りかを指定できます。context.arc(190, 75, 25, Math.PI/4, Math.PI*3/4)はcontext.arc(190, 75, 25, Math.PI/4, Math.PI*3/4,false)と同じ時計周りに線が引かれて、context.arc(190, 75, 25, Math.PI/4, Math.PI*3/4,true)は半時計周りに線が引かれます。角度も時計回りが正になるか、反時計回りが正になるかこの変数に影響されるようです。 - 文字の描画
文字の色を指定し、フォントを指定し、所定の位置に指定した文字列を描画する例です。
context.setFillStyle(CssColor.make("rgba(0,0,0,1)"));
context.setFont("bold 12pt 'Arial'");
context.fillText("draw string test", 10, 20);
context.fillText(str,x,y)でstrが描画する文字列、x、yが描画する位置です。
ちなみに、文字列の幅は
TextMetrics TM
double dLength
context.setFont("bold 12pt 'Arial'");
TM=context.measureText("draw string test");
dLength=TM.getWidth();
とすると取得できます。
 - 画像の描画
まず、画像のロードの方法です。/war/img/test.pngというファイルをロードするとします。htmlファイルからの相対パスで指定できるようです。絶対パスだと一番上からの指定になるようです。今だとwarからですが、アップロードするとindex.htmlのある位置からになると思います。相対パスで指定するのがよいと思います。
Image test;
test=new Image("./img/test.png");
画像の描画は以下の3通りです。
context.drawImage(ImageElement.as(test.getElement()),dx,dy)
context.drawImage(ImageElement.as(test.getElement()),dx,dy,dw,dh)
context.drawImage(ImageElement.as(test.getElement()),sx,sy,sw,sh,dx,dy,dw,dh)
dx、dyは画像を張り付ける位置の左上隅のx、y座標、dw、dhは画像を張り付けるときの幅、高さを表します。
また、sx、sy、sw、shを指定した場合は画像の一部を抜き出して張り付けられて、これらの引数によって抜き出す位置を指定します。sx、syは画像の抜き出す位置の左上隅のx、y座標、sw、shは画像の抜き出す範囲の幅、高さを表します。
 
音声ファイルの操作
音声ファイルの操作について説明します。
まず、音声ファイルのロードについて説明します。war/sound/test.mp3というファイルをロードするとします。画像ファイルと同様相対ファイルで指定するのがいいと思います。
public void load()
{
Audio Test;
Test=Audio.createIfSupported();
if(Test==null)
{
	return false;
}
Test.setSrc("./sound/test.mp3");
}
コンストラクタではなく、Audio.createIfSupported()を呼び出します。Audioを作成できない場合、Testがnullになるのでその対応もしておきます。音声ファイルの指定はTest.setSrcで行います。
他に使う関数としては、以下が挙げられます。
- 音声ファイルを再生する関数
Test.play() - 音声ファイルを停止する関数
Test.pause() - 音声ファイルの再生を開始する時間を指定する関数
Test.setCurrentTime(time)
timeに再生する時間をdouble型で渡します。 - 音声ファイルが停止中かどうか確認する関数
Test.isPaused()
停止中ならtrue、再生中ならfalseが返ってきます。 
デバッグ
デバッグの方法について説明します。
- デバッグしたいプロジェクトのファイル上で右クリックし、GWT Development mode with Jettyを選択し、デバッグを実行

 - HTMLファイルを開く
しばらく待つと、"開発モード"タブにURLが表示される(青枠)ので、ダブルクリックする。あるいは、右クリックで出現したメニューから詳細を指定して選択する。
 - デバッグ作業
ブラウザでHTMLファイルが開かれるのでF12でデバッグモードに移行し、デバッグします。私の環境(Chromium)で実行すると、javaのプログラム上でBreakPointを設定して処理を停止することができました。chromeだからかもしれません。
 - デバッグ終了
デバッグが終わったら、HTMLファイルを閉じてGWT Development mode with Jettyを停止します。下の画像の青枠をクリックします。
 
Chromeをダウンロードしてeclipse.iniに-Dchrome.location=C:/Program Files/xxxx/chrome.exeなどとchromeファイルのパスを指定して、デバッグに使用するのが一番いいかもしれません。
サーバーにアップロードするJava Scriptを作成
プログラムが完成したら、サーバーにアップロードするためのJava Scriptを作成します。下の画像のように、Java 
Scriptを作成したいプロジェクトのファイル上で右クリックしてGWT Compilerで実行するだけです。
サーバーにアップロードする
サーバーにアップロードすべきものは下の画像の青枠で囲ったものです。つまり、サンプルの場合、warフォルダー内にあるJava Scriptファイルの入ったhowtocreateagameフォルダ、imgフォルダ、soundフォルダ、HowToCreateAGame.css、HowToCreateAGame.htmlとなります。実際は、cssファイルとhtmlファイルはこのホームページに合わせて別のものを用意しましたが、このように適切に編集したものでも問題ありません。
その他
その他に分かったことについて説明します。
- testフォルダ
Javaプログラムの単体テストJunit testを行うためのソースフォルダのようです(下図青枠)。
 - 配列のArrayIndexOutOfBoundsExceptionエラーが出ない
Java Scriptの仕様に引っ張られているのかもしれない。
 - 乱数について
java.util.Randomは使えないようです。Math.Randomは使えます。
 - GWTとAWTのプログラムを混在させる
GWTはawtパッケージ内に定義されたものは使えません。AppletやGraphics、Point、Rectangleなどがそうです。普通にコンパイルするとエラーになりますが、@GWTIncompatible(value="xxx")というアノテーションをつけると、GWTコンパイル時に無視されるようになります。xxxは例えば、メソッドを無視するときはMETHOD、フィールド変数を無視するときはFIELD、クラスを無視するときはCLASSとします。Appletからの移行時などに使えば、Appletを実行して確認して、GWTでも実行して確認するということができます。
 
サンプルのプログラム
参考のため、サンプルのプログラムを掲載します。このプログラムについてはGWTのプログラムを作成するためなら自由に複製して使っていただいて構いませんが、自己責任でご利用ください。
howToCreate.client.HowToCreateAGame.java
package howToCreate.client;
import com.google.gwt.animation.client.AnimationScheduler;
import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback;
import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle;
import com.google.gwt.canvas.client.Canvas;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.canvas.dom.client.CssColor;
import com.google.gwt.canvas.dom.client.TextMetrics;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import howToCreate.client.bgm.BGM;
import howToCreate.client.hiddenCommand.HiddenCommand;
import howToCreate.client.image.ArrowKeyImage;
import howToCreate.client.key.KeyEventListener;
import howToCreate.client.mouse.MouseEventDrawer;
import howToCreate.client.mouse.MouseEventListener;
import howToCreate.client.movingRect.XMovingRect;
import howToCreate.client.polygonDrawer.PolygonDrawer;
/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class HowToCreateAGame implements EntryPoint,AnimationCallback {
	Canvas canvas;
    Context2d context;
    public static final int canvasHeight = 300;
    public static final int canvasWidth = 300;
    static final String divTagId = "canvasExample"; // must match div tag in html file
    private AnimationHandle handler;
    private KeyEventListener KeyEventListener;
    private MouseEventListener MouseEventListener;
    private boolean bInitHasFailed;
    private boolean bSupportAudio;
    private XMovingRect Rect;
    private double[] dStrokeTriangleVertexX;
    private double[] dStrokeTriangleVertexY;
    private double[] dFillTriangleVertexX;
    private double[] dFillTriangleVertexY;
    private double[] dStrokeLinePointX;
    private double[] dStrokeLinePointY;
    private double[] dFillLinePointX;
    private double[] dFillLinePointY;
    private TextMetrics TM;
    private HiddenCommand hiddenCommand;
    public void onModuleLoad()
    {
    	init();
    	if(bInitHasFailed==true)
		{
			return;
		}
        execute(0);
//        executeByTimer();
    }
    private void init()
    {
    	prepareCanvas();
    	bSupportAudio=BGM.loadAudio();
    	ArrowKeyImage.loadImage();
    	Rect=new XMovingRect(30.0,100.0,10.0,CssColor.make("rgba(0,255,0,1"));
    	hiddenCommand=new HiddenCommand();
    	dStrokeTriangleVertexX=new double[3];
    	dStrokeTriangleVertexY=new double[3];
    	dFillTriangleVertexX=new double[3];
    	dFillTriangleVertexY=new double[3];
    	dStrokeLinePointX=new double[2];
    	dStrokeLinePointY=new double[2];
    	dFillLinePointX=new double[2];
    	dFillLinePointY=new double[2];
    	dStrokeTriangleVertexX[0]=22.5;
    	dStrokeTriangleVertexX[1]=85;
    	dStrokeTriangleVertexX[2]=53.75;
    	dStrokeTriangleVertexY[0]=50;
    	dStrokeTriangleVertexY[1]=50;
    	dStrokeTriangleVertexY[2]=100;
    	dFillTriangleVertexX[0]=dStrokeTriangleVertexX[0]+(dStrokeTriangleVertexX[1]-dStrokeTriangleVertexX[0])/2+10.0;
    	dFillTriangleVertexX[1]=dStrokeTriangleVertexX[1]+(dStrokeTriangleVertexX[1]-dStrokeTriangleVertexX[0])/2+10.0;
    	dFillTriangleVertexX[2]=dStrokeTriangleVertexX[2]+(dStrokeTriangleVertexX[1]-dStrokeTriangleVertexX[0])/2+10.0;
    	dFillTriangleVertexY[0]=100;
    	dFillTriangleVertexY[1]=100;
    	dFillTriangleVertexY[2]=50;
    	dStrokeLinePointX[0]=10;
    	dStrokeLinePointX[1]=30;
    	dStrokeLinePointY[0]=20;
    	dStrokeLinePointY[1]=20;
    	dFillLinePointX[0]=10;
    	dFillLinePointX[1]=30;
    	dFillLinePointY[0]=40;
    	dFillLinePointY[1]=40;
    }
	@Override
	public void execute(double timestamp)
	{
		handler=AnimationScheduler.get().requestAnimationFrame(this);
		run();
	}
    private void executeByTimer()
    {
    	final Timer timer = new Timer()
        {
            @Override
            public void run()
            {
            	loopProcess();
            }
        };
        timer.scheduleRepeating(1);
    }
    private void run()
    {
    	loopProcess();
    }
    private void loopProcess()
    {
    	context.clearRect(0, 0, canvasWidth, canvasHeight);
    	PolygonDrawer.DrawStrokePolygon(context, dStrokeLinePointX, dStrokeLinePointY, CssColor.make("rgba(255,0,0,1)"));
    	context.setFillStyle(CssColor.make("rgba(0,0,0,1)"));
    	context.setFont("bold 12pt 'Arial'");
    	TM=context.measureText("draw string test");
    	context.fillText("draw string test", (canvasWidth-TM.getWidth())/2, 20);
    	Rect.Draw(context);
    	PolygonDrawer.DrawStrokePolygon(context, dStrokeTriangleVertexX, dStrokeTriangleVertexY, CssColor.make("rgba(255,0,0,1)"));
    	PolygonDrawer.DrawFillPolygon(context, dFillTriangleVertexX, dFillTriangleVertexY, CssColor.make("rgba(0,0,255,0.5)"));
    	context.setStrokeStyle(CssColor.make("rgba(255,0,0,1)"));
    	context.beginPath();
    	context.arc(190, 75, 25, Math.PI/4, Math.PI*3/4);
    	context.closePath();
    	context.stroke();
    	context.setFillStyle(CssColor.make("rgba(0,0,255,1)"));
    	context.beginPath();
    	context.arc(250, 75, 25, Math.PI/4, Math.PI*3/4,true);
    	context.closePath();
    	context.fill();
    	context.setFillStyle(CssColor.make("rgba(0,0,0,1)"));
    	context.setFont("bold 10pt 'Arial'");
    	if(bSupportAudio==true&&BGM.isPaused()==true)
    	{
    		TM=context.measureText("Press s button to turn on the BGM.");
    		context.fillText("Press s button to turn on the BGM.", (canvasWidth-TM.getWidth())/2, 120);
    	}
    	else if(bSupportAudio==true)
    	{
    		TM=context.measureText("Press s button to turn off the BGM.");
    		context.fillText("Press s button to turn off the BGM.", (canvasWidth-TM.getWidth())/2,120);
    	}
    	BGM.play();
    	BGM.pause();
    	ArrowKeyImage.DrawKeys(context, 10.0, canvasHeight-70.0);
    	MouseEventDrawer.Draw(context, 110, canvasHeight-70.0);
    	hiddenCommand.DrawCorrectlyPushedCommand(context);
    	hiddenCommand.Judge();
    	if(hiddenCommand.hasInputHiddenCommand()==true)
    	{
    		context.setFillStyle(CssColor.make("rgba(0,0,0,1)"));
        	context.setFont("bold 14pt 'Arial'");
    		TM=context.measureText("Congraturations!");
    		context.fillText("Congraturations!", (canvasWidth-TM.getWidth())/2,150);
    		TM=context.measureText("You found the hidden command.");
    		context.fillText("You found the hidden command.", (canvasWidth-TM.getWidth())/2,170);
    	}
    	MouseEventListener.buildClickEventFlag();
    	MouseEventListener.breakClickFlag();
    	MouseEventListener.breakDoubleClickFlag();
    }
    private void prepareCanvas()
    {
    	canvas = Canvas.createIfSupported();
        if (canvas == null)
        {
              RootPanel.get().add(new Label("Sorry, your browser doesn't support the HTML5 Canvas element"));
              bInitHasFailed=true;
              return;
        }
        canvas.setStyleName("canvasExample");
        canvas.setWidth(canvasWidth + "px");
        canvas.setCoordinateSpaceWidth(canvasWidth);
        canvas.setHeight(canvasHeight + "px");
        canvas.setCoordinateSpaceHeight(canvasHeight);
        context = canvas.getContext2d();
        KeyEventListener=new KeyEventListener();
        MouseEventListener=new MouseEventListener();
        canvas.addKeyDownHandler(KeyEventListener);
        canvas.addKeyUpHandler(KeyEventListener);
        canvas.addClickHandler(MouseEventListener);
        canvas.addDoubleClickHandler(MouseEventListener);
        canvas.setFocus(true);
        RootPanel.get( divTagId ).add(canvas);
    }
}
howToCreate.client.bgm.BGM.java
package howToCreate.client.bgm;
import com.google.gwt.media.client.Audio;
import howToCreate.client.key.Key;
public class BGM {
	private final static long lOnOffInterval=200;
	private static Audio BGM;
	private static long lTurnOnOffTime;
	public static boolean loadAudio()
	{
		BGM=Audio.createIfSupported();
		if(BGM==null)
		{
			return false;
		}
		BGM.setSrc("./sound/pastelHouse.mp3");
		BGM.setLoop(true);
		lTurnOnOffTime=-1-lOnOffInterval;
		return true;
	}
	public static void play()
	{
		if(BGM.isPaused()==false)
		{
			return;
		}
		if(System.currentTimeMillis()<=lTurnOnOffTime+lOnOffInterval)
		{
			return;
		}
		if(Key.s==true)
		{
			BGM.play();
			lTurnOnOffTime=System.currentTimeMillis();
		}
	}
	public static void pause()
	{
		if(BGM.isPaused()==true)
		{
			return;
		}
		if(System.currentTimeMillis()<=lTurnOnOffTime+lOnOffInterval)
		{
			return;
		}
		if(Key.s==true)
		{
			BGM.pause();
			BGM.setCurrentTime(0.0);
			lTurnOnOffTime=System.currentTimeMillis();
		}
	}
	public static boolean isPaused()
	{
		return BGM.isPaused();
	}
}
howToCrate.client.image.ArrowKeyImage.java
package howToCreate.client.image;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.dom.client.ImageElement;
import com.google.gwt.user.client.ui.Image;
import howToCreate.client.key.Key;
public class ArrowKeyImage {
	private static Image ArrowKeyImage;
	public static void loadImage()
	{
		ArrowKeyImage=new Image("./img/ArrowKey.png");
	}
	public static void DrawKeys(Context2d context,double dDrawnX,double dDrawnY)
	{
		if(Key.up==true)
		{
			drawUpKey(context,dDrawnX+30.0,dDrawnY);
		}
		if(Key.left==true)
		{
			drawLeftKey(context,dDrawnX,dDrawnY+30.0);
		}
		if(Key.down==true)
		{
			drawDownKey(context,dDrawnX+30.0,dDrawnY+30.0);
		}
		if(Key.right==true)
		{
			drawRightKey(context,dDrawnX+60.0,dDrawnY+30.0);
		}
	}
	public static void drawUpKey(Context2d context,double dDrawnX,double dDrawnY)
	{
		context.drawImage(ImageElement.as(ArrowKeyImage.getElement()), 30.0, 0.0, 30.0, 30.0, dDrawnX, dDrawnY, 30.0, 30.0);
	}
	public static void drawLeftKey(Context2d context,double dDrawnX,double dDrawnY)
	{
		context.drawImage(ImageElement.as(ArrowKeyImage.getElement()), 0.0, 30.0, 30.0, 30.0, dDrawnX, dDrawnY, 30.0, 30.0);
	}
	public static void drawDownKey(Context2d context,double dDrawnX,double dDrawnY)
	{
		context.drawImage(ImageElement.as(ArrowKeyImage.getElement()), 30.0, 30.0, 30.0, 30.0, dDrawnX, dDrawnY, 30.0, 30.0);
	}
	public static void drawRightKey(Context2d context,double dDrawnX,double dDrawnY)
	{
		context.drawImage(ImageElement.as(ArrowKeyImage.getElement()), 60.0, 30.0, 30.0, 30.0, dDrawnX, dDrawnY, 30.0, 30.0);
	}
}
howToCreate.client.key.Key.java
package howToCreate.client.key;
public class Key
{
	static public boolean left,right,up,down,s;
}
howToCreate.client.key.KeyEventListener.java
package howToCreate.client.key;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
public class KeyEventListener implements KeyUpHandler,KeyDownHandler{
	@Override
	public void onKeyDown(KeyDownEvent event)
	{
		event.preventDefault();
		switch(event.getNativeKeyCode())
		{
			case KeyCodes.KEY_LEFT: Key.left=true; break;
			case KeyCodes.KEY_RIGHT: Key.right=true; break;
			case KeyCodes.KEY_UP: Key.up=true; break;
			case KeyCodes.KEY_DOWN: Key.down=true; break;
			case KeyCodes.KEY_S: Key.s=true; break;
		}
	}
	@Override
	public void onKeyUp(KeyUpEvent event)
	{
		event.preventDefault();
		switch(event.getNativeKeyCode())
		{
			case KeyCodes.KEY_LEFT: Key.left=false; break;
			case KeyCodes.KEY_RIGHT: Key.right=false; break;
			case KeyCodes.KEY_UP: Key.up=false; break;
			case KeyCodes.KEY_DOWN: Key.down=false; break;
			case KeyCodes.KEY_S: Key.s=false; break;
		}
	}
}
howToCreate.client.mouse.Mouse.java
package howToCreate.client.mouse;
public class Mouse {
	static public boolean click,doubleClick;
}
howToCreate.client.mouse.MouseClickFlagBreaker.java
package howToCreate.client.mouse;
public class MouseClickFlagBreaker {
	private static long lInterval=500;
	private long lClickedTime;
	private long lDoubleClickedTime;
	public void setClickedTime()
	{
		lClickedTime=System.currentTimeMillis();
	}
	public void setDoubleClickedTime()
	{
		lDoubleClickedTime=System.currentTimeMillis();
	}
	public void breakClickFlag()
	{
		if(Mouse.click==false)
		{
			return;
		}
		if(System.currentTimeMillis()>lClickedTime+lInterval)
		{
			Mouse.click=false;
		}
	}
	public void breakDoubleClickFlag()
	{
		if(Mouse.doubleClick==false)
		{
			return;
		}
		if(System.currentTimeMillis()>lDoubleClickedTime+lInterval)
		{
			Mouse.doubleClick=false;
		}
	}
}
howToCreate.client.mouse.MouseEventDrawer.java
package howToCreate.client.mouse;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.canvas.dom.client.CssColor;
public class MouseEventDrawer {
	public static void Draw(Context2d context,double dDrawnX,double dDrawnY)
	{
		if(Mouse.click==true)
		{
			context.setFillStyle(CssColor.make("rgba(237,201,0,1)"));
			context.fillRect(dDrawnX, dDrawnY, 30, 60);
		}
		if(Mouse.doubleClick==true)
		{
			context.setFillStyle(CssColor.make("rgba(255,0,0,1)"));
			context.fillRect(dDrawnX+30, dDrawnY, 30, 60);
		}
	}
}
howToCreate.client.mouse.MouseEventListener.java
package howToCreate.client.mouse;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.DoubleClickEvent;
import com.google.gwt.event.dom.client.DoubleClickHandler;
public class MouseEventListener implements ClickHandler,DoubleClickHandler{
	private static final int lFlagHoldTime=200;
	private MouseClickFlagBreaker mouseClickFlagBreaker=new MouseClickFlagBreaker();
	private long lClickEventTime;
	private boolean bDoubleClickedFlag;
	private boolean bClickedFlag;
	@Override
	public void onDoubleClick(DoubleClickEvent event)
	{
		bDoubleClickedFlag=true;
		lClickEventTime=System.currentTimeMillis();
		mouseClickFlagBreaker.setDoubleClickedTime();
	}
	@Override
	public void onClick(ClickEvent event)
	{
		bClickedFlag=true;
		lClickEventTime=System.currentTimeMillis();
		mouseClickFlagBreaker.setClickedTime();
	}
	public void buildClickEventFlag()
	{
		if(System.currentTimeMillis()<=lClickEventTime+lFlagHoldTime)
		{
			return;
		}
		if(bDoubleClickedFlag==true)
		{
			Mouse.doubleClick=true;
			bClickedFlag=false;
			bDoubleClickedFlag=false;
			return;
		}
		if(bClickedFlag==true)
		{
			Mouse.click=true;
			bClickedFlag=false;
			bDoubleClickedFlag=false;
		}
	}
	public void breakDoubleClickFlag()
	{
		mouseClickFlagBreaker.breakDoubleClickFlag();
	}
	public void breakClickFlag()
	{
		mouseClickFlagBreaker.breakClickFlag();
	}
}
howToCreate.client.movingRect.XMovingRect.java
package howToCreate.client.movingRect;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.canvas.dom.client.CssColor;
import howToCreate.client.HowToCreateAGame;
public class XMovingRect {
	private static final long lMoveInterval=10;
	private double x;
	private double y;
	private double width;
	private double height;
	private CssColor Color;
	private long lMovedTime;
	private int iMoveCounter;
	public XMovingRect(double dDrawnY,double dWidth,double dHeight,CssColor Color)
	{
		y=dDrawnY;
		width=dWidth;
		height=dHeight;
		this.Color=Color;
		lMovedTime=-201;
		iMoveCounter=0;
	}
	public void Draw(Context2d context)
    {
		if(System.currentTimeMillis()>lMovedTime+lMoveInterval)
		{
			lMovedTime=System.currentTimeMillis();
			iMoveCounter++;
		}
		x=(HowToCreateAGame.canvasWidth-width)/2+(HowToCreateAGame.canvasWidth-width)/4*Math.sin(iMoveCounter*0.1);
		context.setStrokeStyle(Color);
    	context.strokeRect(x, y, width, height);
    }
}
howToCreate.client.polygonDrawer.PolygonDrawer.java
package howToCreate.client.polygonDrawer;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.canvas.dom.client.CssColor;
public class PolygonDrawer {
	public static void DrawFillPolygon(Context2d context,double[] dVertexX,double[] dVertexY,CssColor Color)
	{
		context.setFillStyle(Color);
		DrawPolygon(context,dVertexX,dVertexY);
		context.fill();
	}
	public static void DrawStrokePolygon(Context2d context,double[] dVertexX,double[] dVertexY,CssColor Color)
	{
		context.setStrokeStyle(Color);
		DrawPolygon(context, dVertexX, dVertexY);
		context.stroke();
	}
	private static void DrawPolygon(Context2d context,double[] dVertexX,double[] dVertexY)
	{
		context.beginPath();
		if(dVertexX.length!=dVertexY.length)
		{
			return;
		}
		context.moveTo(dVertexX[0], dVertexY[0]);
		for(int i=1;i<dVertexX.length;i++)
		{
			context.lineTo(dVertexX[i], dVertexY[i]);
		}
		context.closePath();
	}
}
