とことんF#よぷよ! F# + XNA ゲームプログラミング入門

Preview:

Citation preview

とことんF#よぷよ! F# + XNA ゲームプログラミング入門

10.15.2011 #CLRH63 ぜくる

ぜくる (1978年生まれ)

Twitter : @zecl , はてな id:zecl

旭川市から来ました。

F#歴3年くらいの睡眠丌足なプログラマ。

プレイ中のゲーム : DARK SOULS(心が折れそうだ…

好きなパズルゲーム

倉庨番, ぷよぷよ, Dr.マリオ, ミスタードリラー, etc…

自己紹介

ブロック崩しゲーム

2Dシューティングゲーム(横/縦)

リバーシ(オセロ)

・・・

テトリス風ゲーム (F# + WindowsForm)

倉庨番風ゲーム (F# + Silverlight)

ぷよぷよ風ゲーム (F# + XNA) ← New!

つくったことのあるもの

きょうお話しする内容

株式会社コンパイルから発売されていた落ち物パズルゲームの人気シリーズ。

第1作目は1991年10月25日に発売されたMSX2版とファミコン ディスクシステム版。

2003年以降はセガが販売を行っている。

ぷよぷよ

ファミコン版のぷよぷよ

ぷよぷよ

人型のオブジェクトでプレイすることが可能。

とてもシュールです…

F# + XNA

「とことんF#よぷよ!」Demo

いきなりですが

わたしはゲームプログラマではありません。

XNA4.0で初めてXNAに触れました(初心者)

とことんF#よぷよ!にテストコードはございません。

とてもレガシーコードです(>_<)

モナドとか高度なお話はしません。怖くないです。

F#わからなくても大丈夫。雰囲気を感じてください。

おことわり

Microsoft 社の簡単かつ本格的なゲーム開発環境(無償) マルチプラットフォーム Windows, Xbox360, WindowsPhone

XNA開発に使用できる言語 一般的には、C# で開発します。 が、 .NET 上で動作する言語であれば、基本的になんでもOK。 (F#, VB, IronPython, IronRuby ...)

XNAとは

開発環境が無料である。

.NET Framework を軸とした移植性の高いコードを書ける

ゲーム開発に必要な枠組みや部品を提供してくれるので、ゲームをつくることだけに集中できる。すぐに作り始められる。

簡単なゲームであれば、難しいことを知らなくても割と大丈夫。

C#で書くことができるよ!

XNAのここが嬉しい!

XNAのここが嬉しい!

開発環境が無料である。

.NET Framework を軸とした移植性の高いコードを書ける

ゲーム開発に必要な枠組みや部品を提供してくれるので、ゲームをつくることだけに集中できる。すぐに作り始められる。

簡単なゲームであれば、難しいことを知らなくても割と大丈夫。

C#もいいけど、F#で書くこともできるよ!

カンスウガタゲンガー歓喜

絵が出せる。動かせる。

音が出せる。 文字が出せる。

入力を受け取れる(操作)。

ゲームデータの保存と読込み

ゲーム作りに必要なこと

19れんさ

ウィンドウの表示

ゲームループの仕組み(FPSの管理)

絵や音等、素材をロードする仕組み

描画の仕組み(SpriteBatch, SpriteFont)

データの圧縮, リソースの管理

入力を受け取る仕組み(キーボード、マウス、ゲームパッドなど)

ストレージを扱う仕組み

多くの補助的なクラスや関数 (Vector2, Color, Math…)

XNAのお膳立て

19れんさ

開始

ゲームの流れ

Initialize LoadContent BeginRun EndRun UnloadContent

終了

Update Draw

ゲームループ

プレイヤーの入力受付

ゲーム状態の更新 ゲーム状態に応じた画面への描画

データ読込 データ開放 初期化

開始

ゲームの流れ

Initialize LoadContent BeginRun EndRun UnloadContent

終了

Update Draw

ゲームループ

プレイヤーの入力受付

ゲーム状態の更新 ゲーム状態に応じた画面への描画

データ読込 データ開放 初期化

開始

ゲームの流れ

Initialize LoadContent BeginRun EndRun UnloadContent

終了

Update Draw

ゲームループ

プレイヤーの入力受付

ゲーム状態の更新 ゲーム状態に応じた画面への描画

データ読込 データ開放 初期化

SpriteBatch.Draw (Texture2D, Vector2, Color)

絵はどやって出すの?

なんかオーバーロードいっぱいあるけど、必要なときに必要なやつを適当に使えばいいよ!

SpriteBatch.Draw (Texture2D, Vector2, Color)

絵はどやって出すの?

なんかオーバーロードいっぱいあるけど、必要なときに必要なやつを適当に使えばいいよ!

SpriteBatch.DrawString (SpriteFont, String, Vector2, Color)

文字はどやって出すの?

なんかオーバーロードいっぱいあるけど、必要なときに必要なやつを適当に使えばいいよ!

SpriteBatch.DrawString (SpriteFont, String, Vector2, Color)

文字はどやって出すの?

なんかオーバーロードいっぱいあるけど、必要なときに必要なやつを適当に使えばいいよ!

素材はどうやって読み込むの?

Content.Load<アセットの種類>(アセット名)

例えば

・ this.Content.Load<Texture2D>(@"Content¥image¥player")

・ this.Content.Load<SoundEffect>(@"Content¥sound¥bgm")

素材はどうやって読み込むの?

Content.Load<アセットの種類>(アセット名)

例えば

・ this.Content.Load<Texture2D>(@"Content¥image¥player")

・ this.Content.Load<SoundEffect>(@"Content¥sound¥bgm")

音はどうやって出すの?

let bgm = this.Content.Load<SoundEffect>(@"Content¥sound¥bgm")

bgm.CreateInstance() .Play()

CreateInstanceメソッドで、サウンドの単一

再生、一時停止、または停止を制御可能なSoundEffectInstanceインスタンスが作られる。そして、あとはPlay()するだけ。

音はどうやって出すの?

let bgm = this.Content.Load<SoundEffect>(@"Content¥sound¥bgm")

bgm.Play()

ゲームロジックはどう書けば?

type Game1 () as this =

inherit Game()

override this.Update(gameTime) = …

override this.Draw(gameTime) = …

Update : プレーヤーやゲームの状態を更新する処理を書く

Draw :プレーヤーやゲームの状態に応じた描画処理を書く

ゲームロジックはどう書けば?

type Game1 () as this =

inherit Game()

override this.Update(gameTime) = …

override this.Draw(gameTime) = …

Update : プレーヤーやゲームの状態を更新する処理を書く

Draw :プレーヤーやゲームの状態に応じた描画処理を書く

ウィンドウを出す方法は?

module Program =

[<EntryPoint>]

let main (args : string[]) =

use game = new Game1()

game.Run()

0 GameクラスのRun

メソッドを呼ぶだけ!

ウィンドウを出す方法は?

module Program =

[<EntryPoint>]

let main (args : string[]) =

use game = new Game1()

game.Run()

0 GameクラスのRun

メソッドを呼ぶだけ!

入力を受取りたいんだけど?

Keyboard.GetState().IsKeyDown (Keys)

Keyboard.GetState().IsKeyUp(Keys)

Keyboard.GetState().GetPressedKeys()

さまざまなメソッドで、キーボードの入力状態を容易に取得できる。

マウス、ゲームパッド(Xbox360コントローラー)、タッチや加速度センサー(WindowsPhone)なども、プラットフォームや環境に応じて容易に取得できす。

入力を受取りたいんだけど?

Keyboard.GetState().IsKeyDown (Keys)

Keyboard.GetState().IsKeyUp(Keys)

Keyboard.GetState().GetPressedKeys()

さまざまなメソッドで、キーボードの入力状態を容易に取得できる。

マウス、ゲームパッド(Xbox360コントローラー)、タッチや加速度センサー(WindowsPhone)なども、プラットフォームや環境に応じて容易に取得できす。

Demo

ちいさなサンプル

Microsoft社製のマルチパラダイム言語。

OCaml, Haskellなどの関数型言語に強い影響を受け、さま

ざまなアイディアの元に作られた最先端の言語。

.NET Framework上で動作し、.NET の最新テクノロジを利用できる関数型言語。

オープンソース

C#er, VBerが関数型をはじめるならコレできまり!

F#

関数プログラミング

オブジェクト指向

手続型

プログラミング メタプログラミング

言語指向

プログラミング

マルチパラダイム

関数プログラミング

オブジェクト指向 メタプログラミン

言語指向

プログラミング

手続型

プログラミング

マルチパラダイム

エドワード・ガーソン(Edward Garson)

02関数プログラミングを学ぶことの重要性

関数(機能/式)の集まりとして表現する

関数は第一級(ファーストクラスオブジェクト)として扱う

副作用がない(あるいは、少ない)

操作は入力を出力にマップ(写像)するのが基本

関数プログラミング

関数型言語の利点

並列/並行プログラムの容易性

関数型言語の利点

並列/並行プログラムの容易性

簡単だというと嘘になりますが、

「泣きたいくらい難しい。」ということはないので、

まずは書き始めてください、F#を。

それが第一歩です。

関数型言語って難しいんでしょう?

WindowsForm

WPF

Silverlight

XNA

Azure

ASP.NET

.NETのテクノロジ

WindowsForm

WPF

Silverlight

XNA

Azure

ASP.NET

.NETのテクノロジ

F# + XNA

なぜF# + XNAなのか

なぜF# + XNAなのか

なぜF# + XNAなのか

FRIENDLY F# with game development and XNA

著者:Giuseppe Maggiore

ジュゼッペ ・マジョーレ

販売:Smashwords.com

販売形式:EBookのみ

内容:シミュレーション(物理、AI等)ゲーム開発に焦点を当て、関数型言語F#2.0とXNA4.0を使って解説。

http://goo.gl/i9pRa

F# + XNA

関数型言語でゲームが手軽に作れちゃう時代が来てた。

(意識していなかったけど、とっくに来てた!)

→ せっかくだから、俺はこのF#でゲームを書くぜ!

関数型言語で 手軽にゲームが作れる時代

ゲームプログラミング(ゲームループ)は状態が変化し続けることに着目したパラダイムです。

「状態をなるべく持たない。」、「破壊的代入を好まない」

関数型言語との相性はわるいのでは?

F#ってゆーか、関数型言語で ゲームプログラミングとか無理ゲーじゃね?

ゲームプログラミング(ゲームループ)は状態が変化し続けることに着目したパラダイムです。

「状態をなるべく持たない。」、「破壊的代入を好まない」

関数型言語との相性はわるいのでは?

F#ってゆーか、関数型言語で ゲームプログラミングとか無理ゲー?

Visual Studio 2010 Shell (Integrated) 再配布可能パッケージhttp://goo.gl/6MsC2

Visual C# 2010 Express

http://www.microsoft.com/japan/msdn/vstudio/express/

F# CTP http://goo.gl/NMmCU

XNA Game Studio 4.0

APP HUB – [関連情報] – [ダウンロード]より http://create.msdn.com/ja-JP

開発準備

新しいアイディア、新しいゲームデザインは難しい。

真似って大事。

どこまで真似をできるか。

もっとよくできそうな部分はないか探す。

自分のアイディアを盛り込む。

絵や音楽づくりはプログラミングとはまた違ったおしごと。

模倣で学ぶゲームプログラミング

ぷよぷよのざっくりとしたルール

・2つでひと組で、ぷよの組が落下してくる。

・落下してくるぷよの組の色の組合せはランダム。

・落下中のぷよの組は、プレイヤーにより移動・回転の操作ができる。

・同じ色のぷよが4つ以上つながると消える(ぷよの消去)。

・ぷよの下に障害物がない場合、障害物に接地するまで落下する。

・ぷよの消去と落下の繰り返しにより連鎖が発生する場合がある。

・ぷよを配置できない状態になったら窒息(ゲームオーバー)。

・フィールド上のすべてのぷよを消去すると「全消し」となり得点を得る。

ぷよぷよのゲームの状態

ゲームオーバーかどうか

ポーズ状態かどうか

現在の得点

消したぷよの数

落下中のぷよの組の状態

NEXTぷよの状態

フィールドの状態

...

代表的なゲームの状態

ぷよぷよのゲーム状態を表すレコード型

ぷよぷよのゲームの状態

フィールド上のぷよ配置状態を表現するには?

フィールドの表現

横6×縦13の〼に区切る

フィールドの表現

X座標とY座標で表せる

2次元配列で表現できそう

フィールドの表現

フィールドの表現

色ごとに異なる値を設定することで、 フィールドのぷよの配置を表現する

フィールドの表現

ぷよが配置されていない場所も埋める。

色の表現

色の表現

enum (列挙型)で色違いのぷよを表現する。フィールドにぷよが配置されていない状態 n も定義する。

色の表現

フィールドの表現

落下中のぷよは、確定していないので、 フィールド上は、配置がないものする。

実は、1次元配列で表現できる

※数字は配列のindexを表す

数字が黄色のindexは壁を表す

フィールドの表現(余談)

落下中のぷよの表現

3×3〼で考えて、配列の配列として表現

右回転(360度回転の流れ)

落下中のぷよの回転

左回転(360度回転の流れ)

※ 赤ぷよを回転軸とした場合。

右回転(90度回転)

落下中のぷよの回転

3×3〼のそれぞれの位置に数字で名前を付けたとします。

回転に関係のある数字のみに注目して考えます。

※ 赤ぷよ(4の位置)を回転軸としています。

右回転(90度回転)

落下中のぷよの回転

1と5を入替える。

5と3を入替える。

7と5を入替える。

90度回転

右回転(90度回転)

落下中のぷよの回転

右回転(90度回転)

落下中のぷよの回転

落下中のぷよの回転

図はイメージ

右回転(90度回転)

落下中のぷよの回転

ぷよの回転方向に壁やぷよなど障害物が存在する場合、 そのまま回転させることが出来ないので回避する処理が必要。

右回転(90度回転)

右回転(90度回転)後の位置に障害物がある場合

ぷよ回転時の障害物の回避

赤ぷを回転軸とした場合、障害物があるので右回転ができない。

障害物を回避して回転させる必要がある。

右回転(90度回転)後の位置に障害物がある場合

ぷよ回転時の障害物の回避

90度右回転すると障害物に衝突するので、左に移動する。

左に移動した後、90度右回転する。

ぷよの移動

ぷよが移動可能な方向は、左右と下。

ぷよの落下もmove関数を利用。 判別供用体(Discriminated Union、DU)

パターンマッチ match direction with | Right -> {ps.current with position = x + 1, y } | Left -> {ps.current with position = x - 1, y } | Down -> {ps.current with position = x , y + 1 } というように、書くのが一般的。

そもそも回転ができない場合

※ 赤ぷよを回転軸とした場合。

回避丌可能な障害物があり、右回転も左回転もできない。

回避および回転可能かどうかを判断する必要がある。

実装例ではavoidance関数で行っている。

ぷよの生成とNEXTぷよ

現在落下中のぷよ

次に落ちてくるぷよ

次の次に落ちてくるぷよ

ぷよの組はNEXTぷよの表示のめにも3組生成しておく必要がある。

ぷよの生成とNEXTぷよ

ぷよの組はNEXTぷよの表示のめにも3組生成しておく必要がある。

ぷよの組の色の組合せはランダムとする。

レベルに応じて生成するぷよ組の色数を変える。

レベル001~レベル002 ・・・ レベル003~レベル004 ・・・ レベル005~レベル999 ・・・

ぷよの生成とNEXTぷよ

消したぷよの数

消去したぷよに応じたレベルを返す関数を定義

40個ぷよを消すごとに「+1」したレベルを返す。

ただし、最大レベルは999とする。

ぷよの生成とNEXTぷよ

雑なランダム生成

ぷよの組(puyoObj)の生成

ぷよの組

ぷよの組を表すレコード型

ぷよの接地と確定

ぷよの接地と確定

ぷよの接地と確定

床に接地(Yは12まで)するか、ぷよに接地(Y+1の位置が「0」ではない場合)するまでは落下し続ける。

GameクラスのUpdateメソッドでゲームの状態を更新するときに、落下中のぷよ組の座標Yに1を加算する。つまり、move関数にDirection.Downを指定して呼び出すことで表現できる。

床あるいはぷよに接地した場合、はじめてフィールド上に確定させる。

フィールド上の「0」は省略しています。落下中ぷよはフィールド上では「0」。

重力によるぷよの落下

重力によるぷよの落下

重力によるぷよの落下

重力によるぷよの落下

ぷよの落下

フィールドの座標において、Y+1の位置が「0」であるものすべてについて、ぷよをY+1へひとつずつずらす。

ぷよの連結と消去判定

ぷよの連結と消去判定

ぷよがいくつ連結しているかを調べる関数が必要。

最も簡単な連結ぷよの個数チェックと消去アルゴリズムは、フィールド上の座標ごとに同色ぷよの連結を再帰的に探索し保持しておきます、連結ぷよが4つ以上隣接していれば消すというものです。その際、1度調べた座標についてはチェック済みとして、再度チェックしないようにするという方法です。

左の絵の赤ぷよがいくつつながっているかを例に考えてみます。

ぷよの連結と消去判定

ぷよの連結と消去判定

探索部分に着目してみます。

ぷよの連結と消去判定

再帰関数を表す。

ぷよ連結の描画

上下左右の4方向で繋がる可能性があるので、4桁の2進数で表せる。

つまり単独の時も含めて、 16パターンの絵が必要なことがわかる。

Union型は、C#やVBで言うところの FlagsAttributeでマークした列挙型。

b はビットを表す。

ぷよ連結の描画

ぷよがいくつ連結しているかを調べる関数が必要。

ぷよ連結の描画

連鎖

ぷよの消去処理と、重力による落下処理の繰り返しで表現できます。

得点計算

A = 基本得点(消したぷよの数 × 10 )

B = 連鎖ボーナス

C = 連結ボーナス

D = 複色ボーナス

得点 = Σ { A × (B + C + D ) } n

k=1 n n n n

n は連鎖数を表す(連鎖ごとに計算が行われる)

連鎖ボーナス

連鎖数が多いほど得点が多く得られる。

最大連鎖の理論値は19連鎖。

連鎖ボーナス B は、連鎖数に応じたボーナス。

連鎖数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

ボーナス 0 8 16 32 64 96 128 160 192 224 256 288 320 352 384 416 448 480 512

連鎖ボーナス表

連結ボーナス

同時消し数が多いほど得点が多く得られる。

11以上連結した場合のボーナスは10。

連結ボーナス Cは、同時に消したぷよの数に応じたボーナス。

連結ボーナス表

同時消しぷよ数 4 5 6 7 8 9 10 11 ボーナス 0 2 3 4 5 6 7 10

複色ボーナス

同時に消した色の数が多いほど得点が多く得られる。

複色ボーナス Dは、同時に消したぷよの色数に応じたボーナス。

複色ボーナス表

消した色数 1 2 3 4 5 ボーナス 0 3 6 12 24

落下ボーナスと全消しボーナス

ぷよの組が落下中にDownキーを押しっぱなしにすることで得られる得点のことで、 フィールド1マスごとに1点得ることができる。

全消しボーナスとは、ぷよを消去する処理が行われた後、フィールド上にぷよが1つも存在しない場合に得られるボーナス。

3600点 + (現在のLEVEL × 5)点

落下ボーナス

全消しボーナス

全消し判定

フィールドのすべてがPuyoColors.n (「0」)であるかどうかをチェックするだけです。

全消しの場合、得点にボーナス点を加算する。

LEVEL判定

計算量のことを考えると、ぷよぷよのゲーム状態としてLEVELを持たせて、都度状態を変更した方

がよさそうな気もしなくもないですが、レベルに応じて生成するぷよの色数を変えたりする都合上、このように、現在のLEVELを毎回計算するように、

ステートレスな仕組みにしておいた方がしっくりくる場合も。

計算量も少ないので今回は許容する。

40個消すごとにLEVELが1上がる

スコアの保存

XNAではストレージで保存/読込する機能があらかじめ用意されていま

Windowsゲームでは、マイドキュメントのSavedGamesフォルダにデータが格納されます。

XBOX360の場合はストレージの選択処理なども絡んできます。

スコアの保存

スコアの読込

保存とは逆に、デシリアライズして読み込むだけです(ぇ

XNAでストレージを扱う仕組みを用意してくれているのは嬉しいのですが、どうしても記述が冗長になってしまうんですよねえ…。

nullとか書きたくないですし…。

ゲームの状態を描画する

とことんF#よぷよ!で

使用した画像は7枚

Gameクラスの派生クラスのUpdateメソッドで更新したPuyoState(ゲームの状態)に応じて、Drawメソッドで描画するだけ!

アニメーション

C#で書く場合は、アニメーション専用のクラスを記述するのが一般的なようですが、F#ではいちいちクラスつくのメンドイよねってことで、ちょっとした副作用の表現であれば、クロージャーで書いたほうがシンプルです。

えろい人はStateMonadとか言い出すので注意しましょう。

おすすめの書籍

おすすめの書籍

おすすめの書籍

ご清聴ありがとうございました。