1. HOME»
  2. プログラミング・Web»
  3. JavaScript»
  4. JavaScriptでRPGを作ろう!スマホにも対応したゲームの作り方

JavaScriptでRPGを作ろう!スマホにも対応したゲームの作り方

りこ

つぎはゲームエンジンを作るんだったよね

山田

うむ。そうだべよ
といっても、ゲームエンジンから作っていくんじゃなくて、ゲームを作りながらゲームエンジンも作っていく、というふうにやっていくべ

りこ

そっか! かたちにできたほうが分かりやすいし、やる気も出るね

アル

はやく作ろうよ!

目次
  1. Canvasキャンバスを使ってみよう!
  2. メインループをつくろう!
  3. スプライト(画像)を表示してみよう!

Canvasキャンバスを使ってみよう!

つづいて、Canvasキャンバスを使って、RPGのもとになるプログラムを作っていきます。

山田

復習ふくしゅうもかねて、Canvasについて学んでいくべ
ではりこちゃん。キャンバスってなんだべ?

りこ

えっと、油絵あぶらえとかでつかったりするよね

山田

そう。油絵で絵を描くときのぬのだべ
油絵をよく知らない人はピンとこないかもしれないべけど……絵は紙に描くべな。油絵では布に描くんだべ

りこ

私、油絵はやったことなくて……

山田

うむ。紙のことだと思えばいいべよ

アル

そんなのプログラミング関係かんけいあるの?

山田

ふっふっふ。このキャンバスが、HTML5のCanvasと同じ意味なんだべ

りこ

そうなの?

山田

つまりキャンバスは絵が描かれる範囲はんいのこと……Canvasはブラウザに絵が描かれる範囲のことなんだべ

りこ

あ、じゃあCanvasって、紙のことなんだ!

山田

そうだべ。そしてその紙の中では、絵とか文字とかを自由に動かせるんだべ
だからその紙に画像をいたり、文字を表示ひょうじさせたりして、ゲームが作れるんだべよ

アル

なるほど、そういうことだったのか!

山田

じゃあ、これからCanvasを使ってみたいだべけど、これはゲームエンジンの方で使いたいんだべ

りこ

game.jsに書いていくってことね!

山田

そうだべ。まずはGameクラスをつくるべよ
ではgame.jsをこのようにかきかえるんだべ

js/engine/game.js

'use strict'

/**
 * ゲームづくりの基本となるクラス
 */
class Game {
}
アル

入力したよ!

山田

では、コンストラクタをつくっていくべよ

アル

コンストラクタってなんだっけ?

山田

そのクラスがつかわれたときに実行じっこうされるものだべよ
初期化しょきかをするときに使えるべよ

りこ

えっと、たしか……Canvasを使ってたような

山田

そうだべ!
Gameクラスのコンストラクタでは、Canvasを使うための準備じゅんびをしていくべよ
では、game.jsを、つぎのようにしてほしいんだべ

js/engine/game.js

'use strict'

/**
 * ゲームづくりの基本となるクラス
 */
class Game {

	/**
	 * 引数
	 * width : ゲームの横幅
	 * height : ゲームの縦幅
	 */
	constructor( width, height ) {
		//canvas要素を作成
		this.canvas = document.createElement( 'canvas' );
		//作成したcanvas要素をbodyタグに追加
		document.body.appendChild( this.canvas );
		//canvasの横幅(ゲームの横幅)を設定。もし横幅が指定されていなければ320を代入
		this.canvas.width = width || 320;
		//canvasの縦幅(ゲームの縦幅)を設定。もし縦幅が指定されていなければ320を代入
		this.canvas.height = height || 320;
	} //constructor() 終了

}
りこ

これでCanvasを使う準備じゅんびができたのね

山田

そうだべ。まだブラウザにはなにも反映はんえいされないべから、続きを作っていくべよ

メインループをつくろう!

つづいて、かんたんにメインループをつくるための機能きのうを、Gameクラスに作っていきます。

山田

では、りこちゃん。ゲームプログラミングに大切なものはなんだったべ?

りこ

えっと、変数へんすう条件分岐じょうけんぶんきかえし?

山田

そうだべ。とくに繰り返しは、ゲーム内でつねに行われている場合が多いべ

りこ

あ、メインループってやつだ!

山田

うむ。つぎは、メインループするための機能を、Gameクラスに組み込むべよ

js/engine/game.js

'use strict'

/**
 * ゲームづくりの基本となるクラス
 */
class Game {

	/**
	 * 引数
	 * width : ゲームの横幅
	 * height : ゲームの縦幅
	 */
	constructor( width, height ) {
		//canvas要素を作成
		this.canvas = document.createElement( 'canvas' );
		//作成したcanvas要素をbodyタグに追加
		document.body.appendChild( this.canvas );
		//canvasの横幅(ゲームの横幅)を設定。もし横幅が指定されていなければ320を代入
		this.canvas.width = width || 320;
		//canvasの縦幅(ゲームの縦幅)を設定。もし縦幅が指定されていなければ320を代入
		this.canvas.height = height || 320;
	} //constructor() 終了

	/**
	 * startメソッドを呼び出すことで、メインループが開始される
	 */
	start() {
		//メインループを呼び出す
		this._mainLoop();
	} //start() 終了

	/**
	 * メインループ
	 */
	_mainLoop() {
		//画家さん(コンテキスト)を呼ぶ
		const ctx = this.canvas.getContext( '2d' );
		//塗りつぶしの色に、黒を指定する
		ctx.fillStyle = '#000000';
		//左上から、画面のサイズまでを、塗りつぶす
		ctx.fillRect( 0, 0, this.canvas.width, this.canvas.height );

		//自分自身(_mainLoop)を呼び出して、ループさせる
		requestAnimationFrame( this._mainLoop.bind( this ) );
	} //_mainLoop() 終了

}
アル

メインループまであらかじめ作っておくんだね
これで、ゲームを作るたびにループさせる必要ひつようがないんだ

アル

ところで、36行目のコンテキストってなに?

山田

キャンバスに絵をかいてくれる画家がかさんのことだべよ
今回は2Dゲームを作るべから、2D得意とくいな画家さんを呼んだんだべ

りこ

そっか。紙があっても、かいてくれる人がいないといけないもんね

山田

そうなんだべよ
main.jsからこのGameクラスのstartメソッドを呼び出すだけで、かんたんにループできてしまうんだべよ

では、実際じっさいにmain.jsを、このように書きえてほしいべ

js/main.js

'use strict'

//ブラウザがページを完全に読みこむまで待つ
addEventListener( 'load', () => {

	//変数gameに、あなたはゲームですよ、と教える
	const game = new Game();

	//gameに、ゲームをスタートして、とお願いする
	game.start();

} );

書き換えたら、ブラウザを再読み込みしてみてください。

真っ黒な四角が表示される!
りこ

わぁ、真っ黒な四角形が表示されたよ!

山田

これがキャンバス。つまり、絵をかくための紙だべよ。そしてこの中を、キャラクターが自由に動き回れるようになるんだべ

たったこれだけのソースで、320px × 320pxの、ゲームの画面がつくれてしまうんだべよ
ちなみに、ゲームの画面のサイズを変更したいときは、7行目をつぎのようにすればいいべ

js/main.js

'use strict'

//ブラウザがページを完全に読みこむまで待つ
addEventListener( 'load', () => {

	//変数gameに、あなたはゲームですよ、と教える
	const game = new Game( 1136, 640 );

	//gameに、ゲームをスタートして、とお願いする
	game.start();

} );
山田

このように、横幅よこはば縦幅たてはばのサイズを入力するんだべ
これで大きな画面サイズのゲームをつくることもできるんだべ

ただ今回はデフォルトの320px × 320pxで、作っていこうと思ってるべ

スプライト(画像)を表示してみよう!

つづいて、スプライト(画像)を表示するための機能きのうを作っていきましょう。

山田

つぎはスプライトを表示する機能をつくるべよ!
engineフォルダのなかに、sprite.jsというファイルを作成するべ

rpg/
|-- img (省略)/
|-- index.html
|-- js/
|   |-- engine/
|   |   |-- game.js
|   |   `-- sprite.js (追加)
|   `-- main.js
`-- sound (省略)/
アル

追加したけど……えっと、スプライトってなんだっけ?

山田

画像を動かす仕組みのことだべけど、ここでは動かす画像のことを言っているべよ

背景はいけいとわたすの画像を別々にして、あわせることで、こんなふうに動くアニメーションができるんだべ
この仕組しくみが、スプライトだべよ

りこ

キャラクターでスプライトの仕組みを使えば、背景の中を自由に動かせるのね!

アル

sprite.jsをつくったら、index.htmlから読みこむんだよね?

山田

そうだべよ。index.htmlに、このように追加ついかしてほしいべよ

index.html

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>くろねこラビリンス</title>
	<style>
		* {
			padding: 0;
			margin: 0;
		}
		canvas {
			display: block;
		}
	</style>
</head>
<body>
	<script src="js/engine/sprite.js"></script>
	<script src="js/engine/game.js"></script>
	<script src="js/main.js"></script>
</body>
</html>
山田

じゃあ、まずはスプライトを好きな位置いちに表示できる機能をつくってみるべよ
sprite.jsには、このように書いてほしいんだべ

js/engine/sprite.js

'use strict'

/**
 * スプライトに関してのクラス
 */
class Sprite {

	/**
	 * 引数
	 * img : 画像ファイルまでのパス
	 */
	constructor( img ) {
		//this.imgに、あなたは画像ですよ、と教える
		this.img = new Image();
		//this.img.srcに画像ファイルまでのパスを代入
		this.img.src = img;
		//画像の初期位置
		this.x = this.y = 0;
	} //constructor() 終了

	/**Gameクラスのメインループからずっと呼び出され続ける
	 *
	 * 引数
	 * canvas : 紙(キャンバス)
	 */
	update( canvas ) {
		//画像などを画面に表示するためのメソッドを呼び出す
		this.render( canvas );
	} //update() 終了

	/**
	 * 画像などを画面に表示するためのメソッド
	 *
	 * 引数
	 * canvas : 紙(キャンバス)
	 */
	render( canvas ) {
		//画家さん(コンテキスト)を呼ぶ
		const _ctx = canvas.getContext( '2d' );
		//画家さんに、絵を描いてとお願いする
		_ctx.drawImage( this.img, this.x, this.y );
	} //render() 終了

}
アル

26行目の、update( canvas ) {}の部分はなに?

山田

updateメソッドだべな。これはGameクラスのメインループからつねに呼び出して使うメソッドだべ
例えば、スプライトの表示や移動のような、常に更新こうしんしたいものを作るんだべ

りこ

そっか。スプライトはいつでも動けるように準備じゅんびしておかなくちゃいけないもんね

山田

そして、updateメソッドからは、renderメソッドを呼んでるんだべ
renderメソッドには、スプライトを表示するプログラムをかいていて、これを常に呼び出していることになるんだべ
つまり、画家さんには、すごいスピードでなんども絵をかいてもらうんだべよ

りこ

そんなことしたら画家さんがかわいそうよ

山田

そうだべな……でも画家さんは、絵をかくのが大好きだから、よろこんで描いてくれるんだべよ

りこ

そうなんだ……画家さんはそれがしあわせなんだよね

山田

わたすも、プログラミングをこうやって教えているときがしあわせだべよ

りこ

ふふっ、私もプログラミングしてるときはしあわせかも

山田

それはうれしいべ。じゃあ、つづきだべよ
さっきの話だべけど、いまつくったSpriteクラスのupdateメソッドは、つねにGameクラスのメインループから呼び出される必要ひつようがあるべ

アル

じゃあ、メインループにupdateメソッドを呼び出すおねがいを書けばいいんだ

山田

うむ。では、game.jsに、こんなふうに追加してほしいんだべ

js/engine/game.js

'use strict'

/**
 * ゲームづくりの基本となるクラス
 */
class Game {

	/**
	 * 引数
	 * width : ゲームの横幅
	 * height : ゲームの縦幅
	 */
	constructor( width, height ) {
		//canvas要素を作成
		this.canvas = document.createElement( 'canvas' );
		//作成したcanvas要素をbodyタグに追加
		document.body.appendChild( this.canvas );
		//canvasの横幅(ゲームの横幅)を設定。もし横幅が指定されていなければ320を代入
		this.canvas.width = width || 320;
		//canvasの縦幅(ゲームの縦幅)を設定。もし縦幅が指定されていなければ320を代入
		this.canvas.height = height || 320;

		//ゲームに登場する全てのもの(オブジェクト)を入れるための配列
		this.objs = [];
	} //constructor() 終了

	/**
	 * startメソッドを呼び出すことで、メインループが開始される
	 */
	start() {
		//メインループを呼び出す
		this._mainLoop();
	} //start() 終了

	/**
	 * メインループ
	 */
	_mainLoop() {
		//画家さん(コンテキスト)を呼ぶ
		const ctx = this.canvas.getContext( '2d' );
		//塗りつぶしの色に、黒を指定する
		ctx.fillStyle = '#000000';
		//左上から、画面のサイズまでを、塗りつぶす
		ctx.fillRect( 0, 0, this.canvas.width, this.canvas.height );

		//ゲームに登場する全てのもの(オブジェクト)の数だけ繰り返す
		for ( let i=0; i<this.objs.length; i++ ) {
			//スプライトやテキストなど、すべてのオブジェクトのupdateメソッドを呼び出す
			this.objs[i].update( this.canvas );
		}

		//自分自身(_mainLoop)を呼び出して、ループさせる
		requestAnimationFrame( this._mainLoop.bind( this ) );
	} //_mainLoop() 終了

}
りこ

うーん。オブジェクトの数だけかえして、updateメソッドを呼び出す……どういうことだろう

山田

スプライト画像もオブジェクトのひとつだべ
つまり、スプライト画像を表示したいとき、そのスプライトをどんどんオブジェクトとしてゲームに追加していけば、すべてのオブジェクトを表示してくれるんだべ

アル

あ、たしかに、Spriteクラスのrenderメソッドには、スプライト画像を画家さんに描いてもらうお願いごとが書かれてたよね

そのrenderメソッドは、updateメソッドから呼ばれてたから……

りこ

そっか。メインループでずっとそのupdateメソッドを呼び出してるから、スプライト画像が表示されるのね

アル

でもちょっと待って。オブジェクトとしてゲームに追加ってどうすればいいのさ

山田

あわてなさんなだべ
これから、かんたんにそれができてしまう機能をつくっていくべよ
game.jsに、こんなふうに書いてほしいんだべ

js/engine/game.js

'use strict'

/**
 * ゲームづくりの基本となるクラス
 */
class Game {

	/**
	 * 引数
	 * width : ゲームの横幅
	 * height : ゲームの縦幅
	 */
	constructor( width, height ) {
		//canvas要素を作成
		this.canvas = document.createElement( 'canvas' );
		//作成したcanvas要素をbodyタグに追加
		document.body.appendChild( this.canvas );
		//canvasの横幅(ゲームの横幅)を設定。もし横幅が指定されていなければ320を代入
		this.canvas.width = width || 320;
		//canvasの縦幅(ゲームの縦幅)を設定。もし縦幅が指定されていなければ320を代入
		this.canvas.height = height || 320;

		//ゲームに登場する全てのもの(オブジェクト)を入れるための配列
		this.objs = [];
	} //constructor() 終了

	/**
	 * startメソッドを呼び出すことで、メインループが開始される
	 */
	start() {
		//メインループを呼び出す
		this._mainLoop();
	} //start() 終了

	/**
	 * メインループ
	 */
	_mainLoop() {
		//画家さん(コンテキスト)を呼ぶ
		const ctx = this.canvas.getContext( '2d' );
		//塗りつぶしの色に、黒を指定する
		ctx.fillStyle = '#000000';
		//左上から、画面のサイズまでを、塗りつぶす
		ctx.fillRect( 0, 0, this.canvas.width, this.canvas.height );

		//ゲームに登場する全てのもの(オブジェクト)の数だけ繰り返す
		for ( let i=0; i<this.objs.length; i++ ) {
			//スプライトやテキストなど、すべてのオブジェクトのupdateメソッドを呼び出す
			this.objs[i].update( this.canvas );
		}

		//自分自身(_mainLoop)を呼び出して、ループさせる
		requestAnimationFrame( this._mainLoop.bind( this ) );
	} //_mainLoop() 終了

	/**
	 * オブジェクトをゲームに追加できるようになる、addメソッドを作成
	 *
	 * 引数
	 * obj : 追加したいオブジェクト
	 */
	add( obj ) {
		//this.objs配列の末尾に、objの値を追加
		this.objs.push( obj );
	} //add() 終了

}
りこ

これでスプライトの機能をつかえるようになったの?

山田

ふっふっふ、使えてしまうんだべよ。ではmain.jsにこう追加してみてほしいんだべ

js/main.js

'use strict'

//ブラウザがページを完全に読みこむまで待つ
addEventListener( 'load', () => {

	//変数gameに、あなたはゲームですよ、と教える
	const game = new Game();

	//変数yamadaに、あなたは山田先生のスプライト画像ですよ、と教える
	const yamada = new Sprite( 'img/yamada.png' );
	//gameに、山田先生のスプライト画像を表示して、とお願いする
	game.add( yamada );

	//gameに、ゲームをスタートして、とお願いする
	game.start();

} );

では、ブラウザを再読み込みしてみましょう!

山田先生の画像が表示される
りこ

きゃー、山田先生の画像が表示された! しかも先生がたくさんいる!

アル

yamada.pngの全体ぜんたいが表示されちゃってるんだ……これじゃあ、ゲームにはならないよね

山田

やっぱりスプライト画像の表示には、画像のここからここまで、といった範囲はんいを指定する機能がほしいべな

そんなわけで、Spriteクラスに、その機能を追加していくべよ

js/engine/sprite.js

'use strict'

/**
 * スプライトに関してのクラス
 */
class Sprite {

	/**
	 * 引数
	 * img : 画像ファイルまでのパス
	 * width : 画像の表示する範囲(横幅)
	 * height : 画像の表示する範囲(縦幅)
	 */
	constructor( img, width, height ) {
		//this.imgに、あなたは画像ですよ、と教える
		this.img = new Image();
		//this.img.srcに画像ファイルまでのパスを代入
		this.img.src = img;
		//画像の初期位置
		this.x = this.y = 0;
		//画像を表示する範囲の横幅。引数widthが指定されていない場合、this.widthに32を代入
		this.width = width || 32;
		//画像を表示する範囲の縦幅。引数heightが指定されていない場合、this.heightに32を代入
		this.height = height || 32;
		//何番目の画像を表示するか
		this.frame = 0;
	} //constructor() 終了

	/**Gameクラスのメインループからずっと呼び出され続ける
	 *
	 * 引数
	 * canvas : 紙(キャンバス)
	 */
	update( canvas ) {
		//画像などを画面に表示するためのメソッドを呼び出す
		this.render( canvas );
	} //update() 終了

	/**
	 * 画像などを画面に表示するためのメソッド
	 *
	 * 引数
	 * canvas : 紙(キャンバス)
	 */
	render( canvas ) {
		//キャンバスの外にスプライトがあるとき、ここでこのメソッドを終了する
		if ( this.x < -1 * this.width || this.x > canvas.width ) return;
		if ( this.y < -1 * this.height || this.y > canvas.height ) return;

		//X,Y方向に、何番目の画像か
		const _frameX = this.frame % ( this.img.width / this.width );
		const _frameY = ~~( this.frame / ( this.img.width / this.width ) );

		//画家さん(コンテキスト)を呼ぶ
		const _ctx = canvas.getContext( '2d' );
		//画家さんに、絵を描いてとお願いする
		_ctx.drawImage(
			this.img,
			this.width * _frameX,
			this.height * _frameY,
			this.width,
			this.height,
			this.x,
			this.y,
			this.width,
			this.height
		);
	} //render() 終了

}

再び、ブラウザを再読み込みしてみましょう!

山田先生がちゃんと表示される
山田

21〜24行目で、画像の表示するはば設定せっていしているんだべよ
もし幅が指定していされていなければ、32に設定しているんだべ

ちなみに、さきほどのわたすの画像、ひとつのわたすは32pxで作られているんだべよ

アル

へぇ、じゃあ今回は、幅を指定しなくても大丈夫なんだね

りこ

26行目のthis.frame = 0;っていうのは?

山田

さきほどの画像には、12ものわたすがいたべな
0〜11までの数値すうち代入だいにゅうすることで、どのわたすを表示するか指定することができるんだべよ

さて、どうしてこの数値でどのわたすか選べるのかというと、それは50〜52行目を見てほしいんだべ

アル

ちょっと待って。52行目の~~ってどういう意味なのさ

山田

ふっふっふ。これは、小数を切り捨てて、というお願いなんだべよ

りこ

へぇ、これで小数を切り捨てられるんだ

山田

まず51行目では、元の画像の横幅を、表示したい部分の横幅で割り、this.frameの値をその値で割ったあまりを出すことで、横方向に何番目の画像かがわかるんだべ

りこ

えっと、たとえば0から数えて5番目の山田先生は、左から3番目……0から数えると2番目の山田先生だよね
元の画像の横幅は96px。表示したい部分の横幅は32pxだから、96÷32で3になるね

this.frameに5を代入してるから、5÷3で1余り2……ほんとだ、余りが2になってる!

アル

すごい! 52行目のほうも同じようになるの!?

山田

うむ。52行目のほうは、元の画像の横幅を、表示したい部分の横幅で割って、さらにthis.frameの値をその値で割って、小数を切り捨てることで、縦方向に何番目の画像かを出しているんだべよ

りこ

えっと、もしさっきと同じように0から数えて5番目の山田先生だったら、上から2番目で、0から数えると1番目だよね
同じように96÷32で3。this.frameの値は5だから、5÷3で1.666……になるよ
あ、小数は切り捨てちゃうから、1になって……すごい! ちゃんと1って答えが出る!

山田

ふっふっふ。どのわたすも、上からでも、左からでも、ちゃんと何番目のわたすかが分かってしまうべよ
注意点は、0から数える、ということだべ

次のページでは、山田先生がキー入力で動くようになります。

このシリーズの一覧はこちら

  1. 小学生からのプログラミング入門。プログラミングってなぁに?
  2. Scratchの使い方と、ゲーム作りの基礎知識を学ぼう! 小学生からのプログラミング入門
  3. Scratchでじゃんけんゲームを作ろう! 小学生からのプログラミング入門
  4. Scratchでシューティングゲームを作ろう! 小学生からのプログラミング入門
  5. Scratchでピアノ鍵盤を作って音を鳴らそう! 小学生からのプログラミング入門
  6. テキストエディタ(Visual Studio Code)をインストールしてみよう! 小学生からのプログラミング入門
  7. Visual Studio Codeを日本語化してみよう! 小学生からのプログラミング入門
  8. JavaScriptでおみくじを作ろう! 小学生からのプログラミング入門
  9. JavaScriptで今月の残り日数を計算してみよう! 小学生からのプログラミング入門
  10. JavaScriptで画像を表示してみよう! 小学生からのプログラミング入門
  11. JavaScriptで画像を移動してみよう! 小学生からのプログラミング入門
  12. 【JavaScript】キー入力でキャラを動かしてみよう! 小学生からのプログラミング入門
  13. 【JavaScript】ファイルを分けて管理してみよう! 小学生からのプログラミング入門
  14. 【JavaScript】オブジェクトを使ってみよう! 小学生からのプログラミング入門
  15. 【JavaScript】ゲームのメインループを作ってみよう! 小学生からのプログラミング入門
  16. 【JavaScript】キャラを決まった間隔ずつ動かす! 小学生からのプログラミング入門
  17. HTML5とCanvasを使ってみよう! 小学生からのプログラミング入門
  18. 【JavaScript】迷路やRPGで使えるマップを作ってみよう! 小学生からのプログラミング入門
  19. 【JavaScript】マップでキャラを動かせるようにしよう! 小学生からのプログラミング入門
  20. 【JavaScript】クラスの概念をしっかりと理解しよう! 小学生からのプログラミング入門
  21. 【JavaScript】プログラム全体をクラスを使って作ってみよう! 小学生からのプログラミング入門
  22. 【JavaScript】文字を表示するクラスを作ってみよう! 小学生からのプログラミング入門
  23. 【JavaScript】改行と一文字ずつ画面に表示する方法! 小学生からのプログラミング入門
  24. 【JavaScript】ノベルゲーム風にキー入力で文字を切り替える方法! 小学生からのプログラミング入門
  25. JavaScriptでRPGを作ろう!スマホにも対応したゲームの作り方
  26. webpackを使ってゲームエンジンを作ろう!(JSライブラリの作り方)
  27. WindowsにPythonをインストールしてみよう!小学生からのPython入門
  28. MacにPythonをインストールしてみよう!小学生からのPython入門
  29. Pythonでじゃんけんゲームを作ってみよう!小学生からのPython入門
  30. Pythonのtkinterを使って、ウィンドウを表示してみよう!
  31. Pythonのtkinterで、画像つきのおみくじゲームを作ろう!
オリジナルゲーム.com