Pythonでレトロ2Dゲームを作ってみた|Pyxel|ゲームプログラミング
PythonのPyxelというライブラリを使って、レトロゲームを作ってみました。
youtu.be
おにぎりくんが爆弾を避けながらビールを獲得していくというシンプルなゲームです。※おにぎりくんは、親戚の子供向けに作成した架空のキャラクターです。
Pyxel紹介
Pythonのライブラリの1つです。ゲームを作るために必要な基本的な機能が用意されているので、ゲームプログラミング初心者の方でも、短時間でゲームを実装できます。ゲーム内で使用するアイコンや音楽も自分で作ることができます。
Pyxelのサイト:
https://github.com/kitao/pyxel/blob/master/README.ja.md
ソースコード
サイトに公開されているサンプルコードを基に、見よう見まねで実装しました。オブジェクト指向プログラミングの練習にもなりました。
処理が冗長だったり、わかりづらかったりする部分はあるかと思いますが、一旦、作りたい仕様は実現できたので良しとします。
# -*- coding: utf-8 -*- from collections import deque, namedtuple from random import randint import pyxel import time Point = namedtuple("Point", ["w", "h"]) UP = Point(-16, 16) DOWN = Point(16, 16) RIGHT = Point(-16, 16) LEFT = Point(16, 16) class App: def __init__(self): pyxel.init(160, 120, caption="Onigiri-kun Loves Beer") pyxel.load("my_resource.pyxres") self.direction = RIGHT #START FLAG self.START = False #GAMEOVER FLAG self.GAMEOVER = False self.end_bgm_flg = 1 # Score self.score = 0 self.items_got = 0 self.bombs_got = 0 self.total_items = 0 # Starting Point self.player_x = 37 self.player_y = 38 self.player_vy = 0 self.item = [((i+4) * 60, randint(6, 104), True) for i in range(4)] self.sp_item = [((i+1) * 950, randint(6, 104), True) for i in range(2)] self.bomb = [((i+3) * 77, randint(6, 104), True) for i in range(3)] self.timebar = 99 pyxel.playm(0, loop=True) pyxel.run(self.update, self.draw) def update(self): if pyxel.btnp(pyxel.KEY_Q): pyxel.quit() # enter key to start if pyxel.btn(pyxel.KEY_ENTER) or pyxel.btn(pyxel.GAMEPAD_1_START): self.START = True if self.timebar < 0: self.GAMEOVER = True if self.GAMEOVER is True: pyxel.stop() if self.GAMEOVER and (pyxel.btn(pyxel.KEY_ENTER or pyxel.btn(pyxel.GAMEPAD_1_START))) : self.reset() if not self.START or self.GAMEOVER: return self.update_player() for i, v in enumerate(self.item): self.item[i] = self.update_item(*v) for i, v in enumerate(self.sp_item): self.sp_item[i] = self.update_sp_item(*v) for i, v in enumerate(self.bomb): self.bomb[i] = self.update_bomb(*v) self.timebar -= 0.2415 def update_player(self): if pyxel.btn(pyxel.KEY_LEFT) or pyxel.btn(pyxel.GAMEPAD_1_LEFT): self.player_x = max(self.player_x - 4, 0) self.direction = LEFT if pyxel.btn(pyxel.KEY_RIGHT) or pyxel.btn(pyxel.GAMEPAD_1_RIGHT): self.player_x = min(self.player_x + 4, pyxel.width - 16) self.direction = RIGHT if pyxel.btn(pyxel.KEY_UP) or pyxel.btn(pyxel.GAMEPAD_1_UP): self.player_y = max(self.player_y - 4, 0) self.direction = UP if pyxel.btn(pyxel.KEY_DOWN) or pyxel.btn(pyxel.GAMEPAD_1_DOWN): self.player_y = min(self.player_y + 4, pyxel.height - 16) self.direction = DOWN def draw(self): if self.GAMEOVER: if self.end_bgm_flg == 1: if (self.bomb[0][2] is False and abs(self.bomb[0][0] - self.player_x) < 48 and abs(self.bomb[0][1] - self.player_y) < 48) \ or (self.bomb[1][2] is False and abs(self.bomb[1][0] - self.player_x) < 48 and abs(self.bomb[1][1] - self.player_y) < 48) \ or (self.bomb[2][2] is False and abs(self.bomb[2][0] - self.player_x) < 48 and abs(self.bomb[2][1] - self.player_y) < 48): pyxel.play(3, 6) else: pyxel.play(3, 7) time.sleep(1) self.end_bgm_flg = 0 MESSAGE =\ """ FINISH PUSH ENTER RESTART """ pyxel.text(51, 40, MESSAGE, 1) pyxel.text(50, 40, MESSAGE, 7) return # bg color pyxel.cls(12) # time bar pyxel.rect( 50, 2, 90 if self.timebar > 90 else self.timebar, 4, 11 if self.timebar > 60 else 10 if self.timebar > 40 else 9 if self.timebar > 20 else 8 ) # draw item for x, y, is_active in self.item: if is_active: pyxel.blt(x, y, 0, 16, 0, 16, 16, 12) # draw special item for x, y, is_active in self.sp_item: if is_active: pyxel.blt(x, y, 0, 16, 16, 16, 16, 12) # draw bomb for x, y, is_active in self.bomb: if is_active: pyxel.blt(x, y, 0, 32, 0, 16, 16, 12) # draw player pyxel.blt( self.player_x, self.player_y, 0, 0, 16 if (self.item[0][2] is False and abs(self.item[0][0] - self.player_x) < 12 and abs(self.item[0][1] - self.player_y) < 12) or (self.item[1][2] is False and abs(self.item[1][0] - self.player_x) < 12 and abs(self.item[1][1] - self.player_y) < 12) or (self.item[2][2] is False and abs(self.item[2][0] - self.player_x) < 12 and abs(self.item[2][1] - self.player_y) < 12) or (self.item[3][2] is False and abs(self.item[3][0] - self.player_x) < 12 and abs(self.item[3][1] - self.player_y) < 12) else 32 if (self.bomb[0][2] is False and abs(self.bomb[0][0] - self.player_x) < 24 and abs(self.bomb[0][1] - self.player_y) < 24) or (self.bomb[1][2] is False and abs(self.bomb[1][0] - self.player_x) < 24 and abs(self.bomb[1][1] - self.player_y) < 24) or (self.bomb[2][2] is False and abs(self.bomb[2][0] - self.player_x) < 24 and abs(self.bomb[2][1] - self.player_y) < 24) else 48 if (self.sp_item[0][2] is False and abs(self.sp_item[0][0] - self.player_x) < 48 and abs(self.sp_item[0][1] - self.player_y) < 48) or (self.sp_item[1][2] is False and abs(self.sp_item[1][0] - self.player_x) < 48 and abs(self.sp_item[1][1] - self.player_y) < 48) else 0, self.direction[0], self.direction[1], 12, ) # print score s = "Score: {:>3}".format(self.score) pyxel.text(5, 4, s, 1) pyxel.text(4, 4, s, 7) s = "Beer: {:>1}".format(self.items_got) + " Bomb: {:>1}".format(self.bombs_got) pyxel.text(5, 110, s, 1) pyxel.text(4, 110, s, 7) if not self.START: START_TEXT1 ="PUSH ENTER KEY" START_TEXT2 =": PLAYER" START_TEXT3 =": 100 pt / time +++" START_TEXT4 =": 10 pt / time +" START_TEXT5 =": -50 pt / time ---" pyxel.text(50, 23, START_TEXT1, 1) pyxel.text(49, 23, START_TEXT1, 7) pyxel.text(56, 43, START_TEXT2, 1) pyxel.text(55, 43, START_TEXT2, 7) pyxel.blt(37, 55, 0, 16, 16, 16, 16, 5) pyxel.text(56, 61, START_TEXT3, 1) pyxel.text(55, 61, START_TEXT3, 7) pyxel.blt(37, 73, 0, 16, 0, 16, 16, 5) pyxel.text(56, 79, START_TEXT4, 1) pyxel.text(55, 79, START_TEXT4, 7) pyxel.blt(37, 91, 0, 32, 0, 16, 16, 5) pyxel.text(56, 97, START_TEXT5, 1) pyxel.text(55, 97, START_TEXT5, 7) return def update_item(self, x, y, is_active): if is_active and abs(x - self.player_x) < 12 and abs(y - self.player_y) < 12: is_active = False self.score += 10 self.items_got += 1 self.timebar += 3 self.player_vy = min(self.player_vy, -8) pyxel.play(3, 4) self.total_items += 1 if self.GAMEOVER is False: x -= 3 if x < -10: if is_active is True: self.total_items += 1 if self.timebar < 0: x = 999999999999999 else: x += 240 y = randint(6, 104) is_active = True return (x, y, is_active) def update_sp_item(self, x, y, is_active): if is_active and abs(x - self.player_x) < 12 and abs(y - self.player_y) < 12: is_active = False self.score += 100 self.timebar += 10 self.items_got += 1 self.player_vy = min(self.player_vy, -8) pyxel.play(3, 5) self.total_items += 1 if self.GAMEOVER is False: x -= 5 if x < -10: if is_active is True: self.total_items += 1 if self.timebar < 0: x = 999999999999999 else: x += 1900 y = randint(6, 104) is_active = True return (x, y, is_active) def update_bomb(self, x, y, is_active): if is_active and abs(x - self.player_x) < 12 and abs(y - self.player_y) < 12: is_active = False self.score -= 50 self.timebar -= 50 self.bombs_got += 1 self.player_vy = min(self.player_vy, -8) pyxel.play(3, 6) if self.GAMEOVER is False: x -= 2 if x < -10: if self.timebar < 0: x = 999999999999999 else: x += 231 y = randint(6, 104) is_active = True return (x, y, is_active) def reset(self): #GAMEOVER FLAG self.GAMEOVER = False # Score self.score = 0 self.items_got = 0 self.bombs_got = 0 self.total_items = 0 # Starting Point self.player_x = 42 self.player_y = 60 self.player_vy = 0 self.item = [((i+5) * 60, randint(6, 104), True) for i in range(4)] self.sp_item = [((i+1) * 950, randint(6, 104), True) for i in range(2)] self.bomb = [((i+4) * 77, randint(6, 104), True) for i in range(3)] self.timebar = 99 self.end_bgm_flg = 1 pyxel.playm(0, loop=True) App()
プログラム実行ファイル
実行ファイルは、Google ドライブ上にzipファイル形式で公開してます。以下のリンクから、zipファイルをダウンロードできます。
origiri-kun_loves_beer_v1.0.zip - Google ドライブ
ダウンロードの完了後、解凍ソフト等を使用して、zipファイルを展開します。
※Google ドライブなのでファイル改ざんの可能性は無いと思いますが、念のため、ウイルスソフトでファイルをスキャンすることをお勧めします。
exeファイルをダブルクリックすると、ゲームが起動されます。是非遊んでみてください。
まとめ
投稿が遅くなりましたが、夏休みの成果物として、プログラムのコードおよびプログラム実行ファイルを公開しました。
また、実際に作業している様子を動画にしていますので、Pyxelを使って実際にゲームを作ってみたいという方の参考になれば幸いです。
youtu.be
最後まで読んでいただき、ありがとうございました。