2D横視点アクションゲームを作った話(Pygame製)

これは何?

これはKMCアドベントカレンダー*1の1日目の記事です。トップバッターじゃん。明日の記事はKMCID: primeさんの"PEZY-SC2使った話でも書く"の予定です。primeさんはオセロが強い人です。

adventar.org

KMC(京大マイコンクラブ)ではこれとは別にお絵描きアドベントカレンダーもやっています。KMCの名だたる絵師がこの日のために腕によりをかけて描いた逸品が拝めます。よろしくお願いします。

adventar.org

これらのカレンダーの内容はinonoa (id:inonoa) さんの尽力によって下の記事に逐一まとめられるので、良ければ参照してください。inonoaさん、頑張ってくださいね。 上記Adventarがまとまっているので尽力しないようです。決してサボったわけではないはずです。

あいさつ

こんにちは。KMCID: inonoa(id:inonoa)といいます。KMC(京大マイコンクラブ)の2回生で、広報をしていたはずですが、今がちょうど次期広報への引き継ぎ期間です。次の広報には是非精力的に広報活動に励み、ひいてはKMCのみならずこの世界をも牛耳ってもらいたいと思っています。

本題

KMCの主要な活動の一つにゲーム制作があります。KMCにはプログラミングやDTM、お絵描きなど様々な部門*2があり、それぞれの部門の仲間と協力してクオリティの高いゲームを製作できる点でゲーム制作に最適な環境と言えます。まあ今作は(音楽以外は)個人製作ですが…。

また、京都大学には11月祭(学園祭)があり、KMCのブースではゲームを展示・頒布します。完全な個人制作では目標と期日を決め、黙々と開発し、それでも手に取ってもらえるかも定かでないという厳しい戦いになるかと思いますが、KMCでは11月祭を目標とし、そこでは多くの人間(しかもかなり多様)に遊んでもらうことが出来ます。もう作るしかないですね。

gyazo.com

ということで作りました。その名も「敵を避ける遊戯(ゲーム)」といいます。誰も死ななくていいやさしいアクションゲームです。スクロールはしません(手抜き)。製作期間はおおむね2か月半です。

ここからはこのゲームを作った上で頑張ったこととか知見とか、というか個人的に詰まったところとかを、プログラム・絵/グラフィック・ゲームデザイン*3(・音楽)に分けて書いていきます。興味ないところは適当に読み飛ばしてください。

プログラム編

プログラムです。こだわりの無い方はUnity*4を使ってください。私は色々あってPygameを使います。名前から察しが付く気はしますが、PygameとはPython*5のゲーム用ライブラリ*6です。長所とか短所は後述。

んで

Pygameですが、使えるツール(画像を読み込んで表示したり音楽を流したり)が入っているだけなので、ゲームのプログラム本体は一から作っていくことになります。まず最初にもろもろの準備をする処理を書いた後にメインループ*7というものを書きます。次に、その中に主人公動かしたりなんかしたりする処理を書けばとりあえず動くものが出来ます。

でも

これではタイトル画面も無ければ効果音もなっていないので味気ないですね。とりあえず複数の画面(シーンとかシーケンスとかって呼びます)を作れるようにします。まだどう書くのが最適か分かっていないのですが、手探りで書いたのが下のコードになります。

class Scene():
    def __init__(self,):
        self.lyrs = [[],[],[],[],[],[],[],[],[],[]] # 0~9の10レイヤー(まあ十分やろ知らんけど),この順で処理(衝突とかは別かなあ)する
        # こっちが低レイヤー
        # 
        #
        # phantom
        #
        #
        # boss
        # 
        # enemy
        #
        # UI
        # こっちが高レイヤー
        self.enms = [] # 当たり判定するやつ

        self.isEnd = False
        self.nextScnNum = 0
        self.nextScns = []
        self.havePause = False
    
    def makeNextScene(self,):
        pass

class TitleScene(Scene):
    def __init__(self,):
        super().__init__()
        titleBG = objects.GameObject(objects.titleBGImgGrp,self,0,[-10,-10])
        titleUI = objects.TitleUI(self)
    
    def makeNextScene(self,):
        gameScene = GameScene()
        self.nextScns = [gameScene]

コード貼っておくとそれっぽくなりそうなので貼りました。Sceneというクラス*8を作ります。中にはいくつか書いてありますが、重要なのはlyrs(layers)だと思います(多分)。lyrsにはいろいろなものを入れることが出来るので、ここ(実際にはこれから派生したクラス(ゲーム画面ならGameScene、タイトル画面ならTitleScene、というふうに分けていく)のlyrs)にプレイヤーとか敵とか背景とかHPゲージとかもろもろを入れておけば、勝手にまとめて表示されて、すごい*9。「HPは主人公に隠れたりしてほしくない」とかがあれば、lyrsの後ろの方に入れるようにすれば、手前に表示されてすごい*10

あとはこれをいくつか作って、それらを切り替える処理を足せば、なんかゲームっぽいゲームになります。嘘です。実際は敵とか主人公のクラスをつくったり*11それっぽくジャンプさせたり当たり判定をしたり音をつけたりしないとそれっぽくなりません。つらい。

ちなみにUnityなどだとこの辺はそんなに書かなくてもよしなにやってくれるはずです。次のゲームはUnityで作ります。

ゲームが出来たので(?)、個人的に??となったところをまとめます。

効果音の遅延

効果音がなると嬉しいですよね。効果音を鳴らすまでは簡単です。具体的には、効果音の音声データを読み込む処理と再生する処理をそれぞれ一行ずつ書けば再生されます。そう思った私はジャンプに効果音をつけようと、ボタンを押すと音が鳴るように書き、そして実際に鳴らしました。 ただ押してから鳴るまで0コンマn秒。音が遅れて聞こえてきます。 調べたところ、なんかPygameのなんかのパラメーターが初期設定ではかなりでかめに設定されているらしく、そのパラメーターをそこそこ小さめに設定しなおせば解決しました。お分かりかと思いますが細かくは何も理解していません。

ちなみにこのブログを参考にしたはずです。 pygameの音遅延について ねごと

と思ったら公式のドキュメントに普通に書いてあったので和訳のリンクを載せておきます。 http://westplain.sakuraweb.com/translate/pygame/Mixer.cgi

依存関係(?)ぐちゃぐちゃ

ゲームの規模がそこそこ大きくなってくると1つのファイルではごちゃごちゃしてきて分けたくなります。分けるとバラバラになるので、このファイルからはこのファイルを使います*12、と初めに指定しないと他のファイルのクラスなどのデータが使えません。俺は単純なので、分けたすべてのファイルの始めに「他のすべてのファイルを使う」と書けば今まで通りだな、と思いました。

実際には世界は単純ではありません。例えばファイルAがファイルBを使うと書くと、Aを実行するためにはBが必要、ということになります。しかし、Bを見るとAを使うと書かれているとしましょう。そうするとBを実行するためにもAが必要です。ということでAを見に行きます。AにはBが必要です。無限ループです。

こうならないようにそれぞれのファイルをつくりましょう。私は一旦紙に何が何を使うか書いてみてから書き直しました。「依存関係」とかでググるといいのかもしれません。

固定だと思っている数でも変えるときがあるからちゃんと名前つけてまとめよう

表題の通りです。主人公や敵の走る速さやジャンプの強さなどは最終的には一定ですが、作っている最中にはイメージに合うように修正し続けて、理想に近づけていきます。修正する際、一か所を修正するだけならいいのですが、よく使う数字であればあるほどプログラム中の他の箇所に頻出するので、あらかじめ1つの定数*13を作っておいて、その定数の値を変更すればそれが使われている箇所全てに反映されるようにしておけば楽です。私はそれを一部しなかったので面倒でした。特に、作ったときにはこの値で決定やろと思っているような数でも変わる可能性がある、と思っておくのが良いのでしょう。たぶん。

Pygameの長所とか短所

後述って書いてしまったので記述します。

長所としては、Python自体が行数少なく楽に書ける言語ということもあり、他の言語(特にC++C#、その他)で同じようなことをするより楽だとは思います。

短所は上で挙げた効果音の遅延(直りますが)、Unity*14等に比べて一から作っていく必要があるなどがあります。加えて、そこまでメジャーじゃないのが痛いかもしれません。メジャーであればあるほど、詰まったら詰まったところでGoogle検索をかければ答えが出てくる確率が上がりますし、サークルなどのコミュニティ内でメジャーであれば周りの人にも聞けますが、マイナーだと言わずもがなです。ただ全く情報がないわけではありませんし、ドキュメントの日本語訳があったりもするのでそこまで気になるわけではないです。あとPythonは処理が遅くなる傾向がありますが小さいゲームなので実感できませんでした。重いゲームには不向きだと思います。

記述したのでプログラム編を終わります。

絵・グラフィック編

絵です。一応全部ひとりで描きました。

ドット絵(風)はほぼ初挑戦だったため、昨年冬のコミケで日高重工様が頒布していたドット絵マチュアテクニックという冊子を参考にさせてもらいました。今から入手できるのかなこれ…と思ってたらできるようです。 もしくは「ドット絵 アンチエイリアス」等でググると情報が出てきそうです。

ただ、色数?の概念を失っていたり色々とドット絵警察に怒られそうな方法をとってしまったので謝っておきます。クリスタ*15で自由に描き倒した絵を解像度低いだけでドット絵風と言い張ってすみません…。

主人公や周辺のデザインは、元々「敵を避けるゲーム」ということで素早くかわしていくイメージがあったので、速い動物を参考にしようとググったところハヤブサが出てきたので、ハヤブサにジェット噴射しそうな物体をつけて、ところでこいつ何があってそんなに避けてるんだと考えると謎の組織に追われてるイメージが浮かび、謎の組織は宇宙にいるイメージがあり、宇宙を舞台にするなら宇宙人っぽいのを描こうとし、あとは適当です。連想ゲームは大事ですね。ペンギンが出てるのはペンギン・ハイウェイの影響です。

タイトルやUI(ユーザーインターフェース。選択肢とかHPバーとか。)も描いています。選択肢を光らせたかったので「星」とかでググって光り方を見ました。
「〇ボタンでタイトルへ」とかを全部描いたり、小学生でもできるゲーム*16を目指したので字は全部ひらがなとカタカナ(もしくはフリガナをつける)で書いたり、出来るだけ親切にしようと心掛けてはいました。

出来なかったこととしては、キャラに統一性をもたせる*17、HPバーや選択肢などに動きをつけたりして目立たせる、ボスのUFOの下から手かなんかを出してばいきん〇んみたいにしてうねうね動かす、などです。

いうことが思い浮かばないので絵編を終わります。

ゲームデザイン

ゲームデザインってなんだと言われると俺には説明できないです。ここではどのような意図でどのような敵を作りどのような意図でどのように置いたのか、などの話をします。

今回の主人公は走ると跳ぶ(二段ジャンプまで)の他、穴を掘る、横に自分の身長の1.5倍ほど瞬間移動するという操作が可能です。そのため普通の敵(乗っても倒せない〇リボー)の他、これらの操作を使わせる敵を用意します。幅のある敵は瞬間移動一回では当たってしまうのでジャンプするしかありませんし、縦に細長い敵は瞬間移動してすり抜けるしかありません。穴を掘れば全部避けられるといわれそうですがそんなこともあろうかとペンギンを地下に徘徊させています。しかし、地上に大量の敵が押し寄せると、同時にペンギンが退くので、穴を掘って冗談のような敵の大軍をやり過ごせます。

gyazo.com

gyazo.com

gyazo.com

また、途中で左側から敵が出てきたりします。出すほうは簡単ですが、プレイする側、特にマリオなどの横スクロールアクションの経験者は、敵は右側から出てくる*18と思い込んでいるかもしれない。そのため安全地帯だと思っていた左側に主人公を待機させているところに、敵が出てきて大幅にダメージを受けて死ぬ。となると何とも理不尽なので、なんとか右側に主人公を誘導(敵をいくつか出すと避けると同時に右に移動してくれるかなあ)してから左から敵を出している…つもりではいますがどうなんでしょうね。

ただ、このゲームにはスクロールという概念がなく、限られた(しかも平坦な)フィールドで多様なアクションを楽しめなければいけません。このために、

gyazo.com

gyazo.com

こうします。上に乗ってもダメージを受けない敵を用意し、大量に散らして疑似的な横スクロールを実現します。さも自分で考えたかのように言ってますがアンダーテ…何でもないです。飛び石に一つ一つ飛び乗っていく緊張感(緊張感は開放感(快感)を増幅させる!!)に加えて、最後には敵で山(マリオの1-1にあるようなの)を作り、緊張感を極限まで高め、反対に切り抜けた時の達成感/開放感を高めています。このゲームには最後にボスがいるのですが、ボスも同様に最後のみるからに苛烈な(理不尽な)攻撃によって開放感を高めます。もちろん後付けの理論です。

このようないくつかの敵やアイデアを後は(ほぼ)難易度順に並べたら完成です。難易度を段階的に上げていくことと、逆に途中で難易度の起伏を設けて開放感を出すことが大事です。

反省点としては、上に乗れる敵が上に乗れる敵だと気づかれないことが挙げられました。一目でそうとわかるデザインにする、誘導をきつくする(あまりにもあからさまにするとあからさまですが)、などの方法が考えられます。ボスが理不尽なのはサ〇ズのせいです。

完成したのでゲームデザイン編を終わります。

曲は書いてません

曲は自分は書いていませんが、無音を貫くわけにもいかないので頼んだり拝借したりしています。

BGMを頼む

KMCにぶくん(「ぶ」です、KMCID: bu4)という部員がいて、彼にBGMとゲームオーバー、クリア時の音楽を乞いました。ぶくんがn晩でやってくれました。

gyazo.com

これだけでイメージ伝わるんかという問題が残ります。これ以外にも部内のチャットツール*19にスクショを貼り付けまくっていたので、ゲーム画面のイメージは理解されているだろうという体ではありますが、伝えるイメージがいつも画面上にあるとは限らない*20のでもっと器用に伝えていかないとな、という気持ちです。

加えてこのゲームには一定時間後に終わりがあるため、BGMがあまりに長いと聞いてもらえないということを失念しかかっていました。大まかに長さを指定しておくべきだったかもしれません。

効果音を調達する

効果音は魔王魂様のものをお借りしています。周りに効果音サイト聞いたのに結局使ってなくてすみません。

特に効果音に造詣が深いわけでもこだわりがあるわけでもなかったのですが、いざ探してみると思ったような効果音はなかなか見つかりませんでした。無料で最適な音を見つけようというのも虫が良すぎるのですが、それを改めて実感した出来事です。

また、音量も少し問題になりました。効果音が小さすぎたり大きすぎたりして、はじめはPygameに音量調節をする関数があるだろうと勝手に思っていたのですが、BGMの方にしかない…と思って今探したら普通にありました。俺の努力はいったい……。ただ音量を上げることは出来なさそうなので、上げたい場合は各種ソフトを利用しましょう(私はAudacityというものを使いました)。できるようになると一瞬でできるはずなのですがそこまでが微妙に面倒で、音量上げたいだけやのになあという愚痴がこぼれます。音量を上げたら上げたで音割れビームだのなんだの言われましたが音割れポッター同好会との関連はありません。

音楽の知識は何もないので終わります。

完成

ということで完成です。おめでとうございます。ありがとうございます。

今後のinonoa

今後の私とかどうでもいいとは思いますがとりあえず書きます。

ステージをつけたいです。華麗に敵を避けながらステージ上を走り回るイメージを本来はしていたので、1ステージでも作れないかなと妄想中です。お楽しみに。

そして、今それとは別に「川を下るゲーム」を構想中です。来年の11月祭には完成していることでしょう*21。こっちもお楽しみに。

以上宣伝でした。

KMCM

こっちはKMCの宣伝です。

KMCは年末のコミックマーケット95に出展します(2日目(日)東タ31a)。今回のゲームのほか様々なゲーム・音楽を収録したCDや部誌を頒布しているはずなのでよろしくお願いします。

また、ゲーム作りたい!!!!という人は是非KMCに入りましょう。高校生や社会人の部員もいるので誰でも大歓迎です。川を下るゲームのプロジェクトに参加したい人はもっと大歓迎です。

www.kmc.gr.jp

以上

ありがとうございました。明日のprimeさんの記事もぜひご覧ください。

*1:アドベントカレンダーとは : クリスマスまでの期間に日数を数えるために使用されるカレンダー……から派生し、多分IT業界等で流行っている、12/1 ~ 25の25日間、一つのテーマに沿って人々が交代で記事を書くイベント。KMCアドベントカレンダーのテーマは特にないですが。

*2:完全に分かれてるわけではなく交流は活発です、多分

*3:ゲームの仕様を考えることとというかなんというか。詳しくはググってください

*4:比較的簡単かつ高機能なゲーム制作ソフト

*5:プログラミング言語。楽に書けるようになってるがその分処理が遅くなる。

*6:コード(プログラミング言語で書かれた文章)の集合で、プログラミング中に必要があるたびにライブラリのこの関数/クラス/ect.使うよ~って一部を引っ張って来る。

*7:一定時間ごとにキーボードなどの入力を受け取ってそれに応じてキャラを動かし、当たり判定をし、それを反映させた画面を現在の画面と入れ替える、ということを延々繰り返す処理。動かすだけではだめで画面を更新しないと見た目上何も動かないというのが一年前くらいの私には不思議でした。

*8:実際のモノを作る前につくられる、モノが持つ性質や出来ることを書いた雛形(もしくは設計図)。これを書いておけば実際のモノは欲しいときにいくつでも作れるね

*9:勝手にというか、Pygameにそういう機能があるわけではなく、勝手に表示されるようにメインループ内にそういう処理を書いていきます…

*10:これもそういう風に書いていきます…。

*11:はじめの方はクラスじゃなくてもいいんですが、いっぱい生成したり後から生成したり柔軟なことをしたいときにはクラスを作っておくと便利ですね…

*12:pythonでは"import ○○"と書くと使えるようになります

*13:Pythonに定数の機能はないので変数を頑張って定数と思い込みます。全部大文字の変数名を付けると定数、というのが慣例のようです。

*14:こいつUnityばっか挙げてんな

*15:かの有名なCLIP STUDIO PAINT

*16:11月祭には子供が多く来るので実際に小学生がしたりする。

*17:アンケートに実際にそのような意見があり、至極もっともでした…

*18:というより自分が右に動くので右側の視界に入る

*19:slackです

*20:ストーリー上の立ち位置とか動くテンポとかを思いつきました

*21:昨年の11月祭に完成しているはずのゲームを実質ほったらかしているので後ろめたさと申し訳なさしかありません