読者です 読者をやめる 読者になる 読者になる

はんぎょねこの憂鬱

耳から変な汁が出てきた

Pythonゲームプログラミング #9 スクロール

#0 環境構築
#1 メインループ
#2 画像表示
#3 アニメーション
#4 キー入力
#5 サウンド
#6 衝突判定
#7 ステートマシン
#8 マップチップ
#9 スクロール

スクロール

pygameを使ったゲームプログラミングについて説明していきます。

  • Windows10
  • Python3.6.1
  • pygame1.9.3

今回はスクロールについて紹介します。

スクロールとは?

画面に収まりきらない空間をカメラを移動させることで表示する技法です。 実際は描画位置を「カメラからの相対位置」に変換することで実現しています。

f:id:dungeonneko:20170519211651p:plain

サンプルコード

前回のマップを拡げて画面に収まらないようにします。 ついでに上下左右キーで移動できるカメラを追加します。

実行結果

f:id:dungeonneko:20170519210628g:plain

Pythonゲームプログラミング #8 マップチップ

#0 環境構築
#1 メインループ
#2 画像表示
#3 アニメーション
#4 キー入力
#5 サウンド
#6 衝突判定
#7 ステートマシン
#8 マップチップ
#9 スクロール

マップチップ

pygameを使ったゲームプログラミングについて説明していきます。

  • Windows10
  • Python3.6.1
  • pygame1.9.3

今回はマップチップについて説明します。

マップチップの概念

マップチップは背景のパターンをデータ化したものです。マップチップのデータはキャラクターの通行可否の判定などにも使用できます。 画像を再利用して大きなマップを作成することで、作業の手間を省くことも出来ますし、少ないデータで広大なマップを表現することができます。

f:id:dungeonneko:20170518230548p:plain

また、チップ単位でデータを持つという考えは、マップデータだけでなくゲームオブジェクトの配置などにも応用が可能です。 座標系やオブジェクトのサイズを固定することで、衝突判定やAIの処理負荷を下げることも出来ます。

サンプルコード

画像データ(chip.png)

f:id:dungeonneko:20170518225949p:plain

実行結果

f:id:dungeonneko:20170518230001p:plain

Pythonゲームプログラミング #7 ステートマシン

#0 環境構築
#1 メインループ
#2 画像表示
#3 アニメーション
#4 キー入力
#5 サウンド
#6 衝突判定
#7 ステートマシン
#8 マップチップ
#9 スクロール

ステートマシン

pygameを使ったゲームプログラミングについて説明していきます。

  • Windows10
  • Python3.6.1
  • pygame1.9.3

今回はステートマシンです。

ステートマシンって何?

簡単にいうと「状態(ステート)によって振る舞いを変化させたり、状態を遷移させる仕組み」のことです。 この仕組みをゲームプログラミングにうまく適用することで、複雑なプログラムも簡潔に書けるようになり、バグも減ります。

例えば、ゲームに時限爆弾を登場させたいときには「爆発まで待機する状態」「爆発している状態」「爆発が終わった状態」などが考えられます。 それぞれの状態では再生されるアニメーションもバラバラでしょうし「爆発の待機中はキャラクターが重ならないようにあたり判定を付けたい」 「爆発したときは攻撃のあたり判定を発生させたい」「爆発が終わったらゲームから消去したい」などの、状態の遷移と紐づいた様々な処理を実装する必要が出てきます。

f:id:dungeonneko:20170517215503p:plain

これを真面目にif文などで実装してしまうととても複雑なプログラムになってしまうので 「状態Aに入ったときの処理」「状態Aのあいだ呼ばれる処理」「状態Aから出ていくときの処理」などを関数で定義できるようにして、どの処理がいつ呼ばれるかはステートマシンクラスに管理させることにします。

サンプルコード

実行結果

待機ステート開始
爆発まであと90フレーム
爆発まであと89フレーム
爆発まであと88フレーム
.
.
.
爆発まであと3フレーム
爆発まであと2フレーム
爆発まであと1フレーム
待機ステート終了
爆発ステート開始
爆発アニメーションの終了待ち
爆発アニメーションの終了待ち
爆発アニメーションの終了待ち
爆発ステート終了
全ての処理が終了

ゲームオブジェクトだけでなくAIや

f:id:dungeonneko:20170517222616p:plain

シーン遷移などにもステートマシンを適用させることが可能です

f:id:dungeonneko:20170517222629p:plain

Pythonゲームプログラミング #6 衝突判定

#0 環境構築
#1 メインループ
#2 画像表示
#3 アニメーション
#4 キー入力
#5 サウンド
#6 衝突判定
#7 ステートマシン
#8 マップチップ
#9 スクロール

衝突判定

pygameを使ったゲームプログラミングについて説明していきます。

  • Windows10
  • Python3.6.1
  • pygame1.9.3

今回は衝突判定です。

バウンディングボリューム

f:id:dungeonneko:20170516141252p:plain:w480

ゲームプログラミングではフレーム処理をなるべく時間をかけずに終わらせる必要があるため、 ゲーム内のオブジェクトの衝突判定はバウンディングボリュームとよばれる幾何学形状に簡略化しておこなわれることがほとんどです。 今回は最も簡単な衝突判定の一つである円と円の判定を実装してみます。

サンプルコード

実行結果

f:id:dungeonneko:20170516161654g:plain:w320

解説

f:id:dungeonneko:20170516141317p:plain

画像を見てもわかるとおり、円と円が衝突しているかは「2つの円の中心距離」が「2つの円の半径の和」以下かどうかで判定することができます。 2つの円の半径と位置はパラメータで管理しますが、距離は計算で求める必要があります。 2点の座標間の距離は高校生で習う「ベクトル」というものを使うと簡単に求められるのですが、pygameは2Dなのでピタゴラスの定理で説明します。

f:id:dungeonneko:20170516141337p:plain

2Dの直交座標系上では、2点間の距離は直角三角形の斜辺の長さと同じものとして考えることができます。 ピタゴラスの定理より「斜辺の長さの2乗(c^2)」は「底辺の長さの2乗(a^2)と高さの2乗(b^2)の和」と等しい、つまり

c^2 = a^2 + b^2

という式が成り立ちます。平方根を求めるのは計算コストがかかるので、比較する半径の和も2乗して考えると、 円と円の衝突判定の式は下記のように「半径の和の2乗」と「斜辺の2乗」を比較したもので実現することができます。

r = r0 + r1  # 二つの半径の和
a = x0 - x1  # 底辺(2乗して判定するのでマイナスでも大丈夫)
b = y0 - y1  # 高さ(2乗して判定するのでマイナスでも大丈夫)

# 斜辺の長さが半径以下なら?
if (a * a + b * b) <= (r * r):
    print('当たってる')
else:
    print('当たってない')

Pythonゲームプログラミング #5 サウンド

#0 環境構築
#1 メインループ
#2 画像表示
#3 アニメーション
#4 キー入力
#5 サウンド
#6 衝突判定
#7 ステートマシン
#8 マップチップ
#9 スクロール

サウンド

pygameを使ったゲームプログラミングについて説明していきます。

  • Windows10
  • Python3.6.1
  • pygame1.9.3

今回はサウンド処理です。

ゲームに必要な音について

ゲームに使うキー入力のパターンは大きく分けて2つあります。「効果音」と「BGM」です。

f:id:dungeonneko:20170515113724p:plain

pygameではどちらも簡単に実装できます。

サンプルコード

解説

初期化

5行目

pygame.mixer.pre_init(44100, -16, 1, 512)

第一引数:再生周波数
第二引数:ビット数(マイナスの場合はsigned, プラスの場合はunsigned)
第三引数:チャンネル数(1=モノラル、2=ステレオ)
第四引数:バッファサイズ(2の倍数)

pygame.initでmixerも初期化されるため、pre_init関数を使ってあらかじめ初期化の設定をしておきます。 バッファサイズは小さすぎるとサウンドが途切れがちになり、大きすぎると再生までのタイムラグが長くなります。 詳しくはドキュメントを読んでください。

効果音のロードと再生

11行目と26行目

se = pygame.mixer.Sound('se.wav')
se.play()

Soundクラスのドキュメントはこちら

BGMのロードと再生

14~16行目

pygame.mixer.music.load('bgm.wav')
pygame.mixer.music.set_volume(0.5)
pygame.mixer.music.play(-1)

playに渡してる引数はループ回数で-1は無限ループになります(Soundクラスでも同様にループ指定できます)。 musicのドキュメントはこちら

補足:メモリとストリーミング

ところで効果音とBGMのちがいは何でしょう?音の長さや使用する目的もちがいますが、プログラム上ではBGMを効果音として鳴らすこともできるわけです。 それなのに、なぜプログラムの書き方が違うかというと、現代のコンピュータには、メモリ容量という制限があるためです。

f:id:dungeonneko:20170515113758p:plain

コンピュータが色々な処理をするには、メモリ上に絵や音やプログラムを載せておかなければなりません。 ところがメモリの大きさは決まっていて、一度に載せることができる限度というものがあります。 ここで問題になるのがファイルサイズの大きい音楽や、映像などのデータです。

f:id:dungeonneko:20170515113816p:plain

大きなデータはそのままではメモリに載せることができなかったり、他のプログラムなどが載せられる場所が少なくなってしまうという問題があります。 そこで人類が考えたのがストリーミングという技術です。

f:id:dungeonneko:20170515113832p:plain

音楽全体はメモリに載せることができなくても「今聴いているところ」のデータだけをファイルから切り出してくれば、場所をとらずにメモリに載せることができるというわけです。

Pythonゲームプログラミング #4 キー入力

#0 環境構築
#1 メインループ
#2 画像表示
#3 アニメーション
#4 キー入力
#5 サウンド
#6 衝突判定
#7 ステートマシン
#8 マップチップ
#9 スクロール

キー入力

pygameを使ったゲームプログラミングについて説明していきます。

  • Windows10
  • Python3.6.1
  • pygame1.9.3

今回はキー入力です。

ゲームに必要なキー入力のパターン

ゲームに使うキー入力のパターンは大きく分けて3つあります。「押した瞬間」「押しているか」「はなした瞬間」です。

f:id:dungeonneko:20170515111610p:plain

pygameにはキー入力用の関数が用意されていますが、ここには「押しているか」やリピート情報などのテキスト入力向けのものしかありません。押した瞬間などの検出はメインループの回で解説したイベント処理で取得できますが、プログラム内でそのまま利用するには手間がかかります。そこで、今回はゲームで使いやすいキー入力クラスを自作することにします。

キー入力クラスの実装

「押した瞬間」と「はなした瞬間」は前フレームのキーが「押されていたか」を保存しておけば、簡単に判定することができます。pygame.key.get_pressedを使えばキーが押されているかが判定できるので、これを毎フレーム保存しておくようにします。

  • 押した瞬間 = 前フレームはキーが押されていなかった && 今は押されている
  • はなした瞬間 = 前フレームはキーが押されていた && 今は押されていない

キー入力クラスの実装例

Z, X, Cキーでそれぞれ「押した瞬間」「押しているか」「はなした瞬間」が確認できます。注意すべき点として、キー情報の更新をおこなうとフレームの情報が上書きされてしまうので、KeyState.update関数は毎フレーム一度だけ呼び出すようにしておく必要があります。こういったクラスを作成しておけば、ジョイスティックやキーコンフィグなどを適用したい場合も、拡張が簡単になります。

Pythonゲームプログラミング #3 アニメーション

#0 環境構築
#1 メインループ
#2 画像表示
#3 アニメーション
#4 キー入力
#5 サウンド
#6 衝突判定
#7 ステートマシン
#8 マップチップ
#9 スクロール

アニメーション

pygameを使ったゲームプログラミングについて説明していきます。

  • Windows10
  • Python3.6.1
  • pygame1.9.3

今回は画像をアニメーションさせてみます。

アニメーションの仕組み

アニメーションは、パラパラ漫画のように異なる画像を少しずつ表示することによって、絵が動いているように見せています。

f:id:dungeonneko:20170512112422p:plain

ゲームでは、歩きアニメーションなどの繰り返しがあるものと、爆発のように繰り返しのないものがあります。

f:id:dungeonneko:20170512112442p:plain

画像の大きさや並びに法則性を持たせておけば、複雑なクラスを作成しなくても、簡単にアニメーションをおこなうことができます。

サンプルコード

今回はこのような画像を用意してみました。

f:id:dungeonneko:20170512101826p:plain

実行結果

f:id:dungeonneko:20170512104306g:plain

解説

表示する画像の座標指定

今回はキャラクターの画像を32ピクセルごとに横に並べているので、変数が1つあればパターンを切り替えることができます。

f:id:dungeonneko:20170512105222p:plain

例えば左から3番目の画像を表示したい場合は下記のように書きます。

i = 2  # n番目 - 1
screen.blit(img_char, (0, 0), (32 * i, 0, 32, 32))

フレームカウント

10行目と35行目

frame = 0  # フレーム
frame += 1

アニメーションをおこなうには今何フレーム目なのかをカウントしておく必要があります。メインループの最後でフレーム数をインクリメントします。

ループあり

24行目

loop_anim_index = int(frame / 3) % 5

フレームごとに変数を 0, 1, 2, 0, 1, 2 … と変化させたい場合は「フレーム÷変化の長さ」の余剰を求めます。

x = frame % 3  # 0, 1, 2 を繰り返す

アニメーションの速度が速すぎる場合は、フレーム数を割って調整します。

x = int(frame / 2) % 3  # 2フレームごとに切り替わる

ループなし

28行目

anim_index = min(int(frame / 15), 5)

アニメーションをループさせたくない場合はmin関数などを使って、指定した数より大きくならないようにしておきます。