通勤中にPyxelでミニゲーム作る

昔から通勤時にミニゲーム作ってます
今までイロイロと開発環境を模索していましたが、最近はPyxelを使用しています
どんな感じで通勤時に開発しているのか、書いてみようと思います

# 自分のプログラミングレベル
昔に、HSP触っていた
Unityは、C#を一応は使用していた
Pythonは、ウェブスクレイピングできる程度

# 開発環境
#### ｢GPD-Pocket(初代)｣
通勤時に立ちながら操作できる!!幸せ!!
Windows10なので…後継機をどうしたものか…
以前は、Villiv-N5やMiix 2 8なんか使っていました
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70289/cb4a38e6-c0c1-79bf-015d-1d12e6fd5326.jpeg">

#### Pyxel
今回の主人公
低スペックUMPCでもちゃんと動作するのが良い
これぐらい機能が制限しているぐらいがミニゲーム作るのにはちょうどいい
ドットでもなんとなくplayerとenemyが分かればいいので標準EditでOK
めざせABAゲーム!!なのです

#### Visual Studio Code
一応、見やすくコードを書きたかったので導入
低スペックUMPCでも動作するし、インストール容量少なくて助かる
導入方法は検索してみてください

#### GitHub
コードとリソースだけなのでココに保存すれば安心だしログにもなる
コミットぐらいしかつかわないなのでGitHub Desktopで十分

#### ChatGPT
簡単なサンプルを提示していくれので、時短になります

# なんでPyxelなの
#### ゲームデータの容量が超小さい
まあ、コードとちょっとしたリソースぐらいなので…UMPCに優しい
別の環境にも移動しやすい

#### 動作が軽い
低解像度のドットゲームなので、そんなに処理を気にしなくてよい(と思っている)
高速化とか考えないでw、ベタベタでグチャグチャなコードでも普通に動くのでありがたい

#### 今までのゲーム制作ノウハウが使える
コードで全てを動かすので、HSPやUnityで勉強していたプログラミングのテクニックが利用できる。昔からある方法なので、言語は違えどアプローチの方法はいくらでもネットに転がっている
最近ではChatGPTも手伝ってくれるしで、良い時代になったものです

#### 基本的に１つのコードで完結する
ミニゲームなので、それほど長大なコードにならない。シンプルで良い
ある程度慣れてきたら、自分用のテンプレートを作って置けば、初期設定なしですぐに作り始められる
たまに、enemyとか汎用的に別コードにしたくなるけど…イマイチ別ファイルにして呼び出す方法がわからないので保留してしまう…

#### プログラム能力が大して無くてもなんとかなる
個人的にはSTGのサンプルを解読(webで解説してくれているところもあったり)して、自分で理解しやすいように泥臭いコードに変換して今に至る
当たり判定、タイルマップとの判定とかは…試行錯誤が必要だったけど、一度できればコードコピペで動くので楽に感じる

# テンプレート例
200行ぐらいのコード
playerは、←→キーで左右移動、Aボタンで攻撃
見た目は図形描画なのでリソース不要
enemyは、HP=0になると消える
```py
#左右移動アクションmaster
import pyxel
#画面遷移用の変数
SCENE_TITLE = 0	    #タイトル画面
SCENE_PLAY = 1	    #ゲーム画面
SCENE_GAMEOVER = 2  #ゲームオーバー画面
#定数
WINDOW_H = 128
WINDOW_W = 128
PLAYER_HP = 1
PLAYER_SPEED = 1
PLAYER_BULLET_SPEED = 4
#list用意
bullets = []
enemies = []
#関数(List実行)
def update_list(list):
    for elem in list:
        elem.update()   #listの各要素に対してupdate()実行
#関数(List描画)
def draw_list(list):
    for elem in list:
        elem.draw()     #listの各要素に対してdraw()実行
#関数(List更新)
def cleanup_list(list):
    i = 0
    while i < len(list):        #listに要素が入っている間loops
        elem = list[i]          #要素取得
        if not elem.is_alive:   #死亡の場合
            list.pop(i)         #要素削除
        else:                   #生存の場合
            i += 1              #スルー

#■Player
class Player:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.dx = 0
        self.dy = 0
        self.hp = PLAYER_HP
        self.direction = 1
        self.is_alive = True
    def update(self):
        #移動入力
        if (pyxel.btn(pyxel.KEY_RIGHT) or pyxel.btn(pyxel.GAMEPAD1_BUTTON_DPAD_RIGHT)):
            self.dx = 1
            self.direction = 1  #右向き
        if (pyxel.btn(pyxel.KEY_LEFT) or pyxel.btn(pyxel.GAMEPAD1_BUTTON_DPAD_LEFT)):
            self.dx = -1
            self.direction = -1 #左向き
        #攻撃入力
        if pyxel.btnp(pyxel.KEY_A):
            if self.direction == 1:
                Bullet(self.x + 5, self.y + 4, PLAYER_BULLET_SPEED, self.direction)
            elif self.direction == -1:
                Bullet(self.x + 2, self.y + 4, PLAYER_BULLET_SPEED, self.direction)
        #Playerの位置を更新
        self.x = self.x + self.dx
        #移動停止
        self.dx = 0
    def draw(self):
        #editorデータ描画(player)
        pyxel.rect(self.x, self.y, 8, 8, 9)

class Bullet:
    def __init__(self, x, y, speed, dir):
        self.x = x
        self.y = y
        self.speed = speed
        self.direction = dir
        self.size = 1
        self.color = 10 #colorは0～15
        self.count = 0
        self.is_alive = True
        bullets.append(self)
    def update(self):
        self.x += self.speed * self.direction        #弾移動
        self.count += 1
        #一定時間で消去
        if self.count > 30:            
            self.is_alive = False   #消去
    def draw(self):
        pyxel.circ(self.x, self.y, self.size, self.color)

#■Enemy
class Enemy:
    def __init__(self, x, y, speed, dir, hp):
        self.x = x
        self.y = y
        self.hp = hp
        self.direction = dir    #移動方向flag(右:1 左:-1)
        self.is_alive = True
        enemies.append(self)
    def update(self):
        #移動
        pass
    def draw(self):
        pyxel.rect(self.x, self.y, 8, 8, 7)

class App:
    def __init__(self):
        #画面サイズの設定　titleはwindow枠にtext出せる
        pyxel.init(WINDOW_W, WINDOW_H, title="Pyxel Base")
        self.score = 0
        #画面遷移の初期化
        self.scene = SCENE_TITLE
        #Playerインスタンス生成
        self.player = Player(pyxel.width / 2, pyxel.height / 2)
        #Enemy仮配置
        Enemy(32, pyxel.height / 2, 0, 0,3)
        #実行開始 更新関数 描画関数
        pyxel.run(self.update, self.draw)

	#更新関数
    def update(self):
        #処理の画面分岐
        if self.scene == SCENE_TITLE:
            self.update_title_scene()
        elif self.scene == SCENE_PLAY:
            self.update_play_scene()
        elif self.scene == SCENE_GAMEOVER:
            self.update_gameover_scene()

    #タイトル画面処理用update
    def update_title_scene(self):
        #ENTERでゲーム画面に遷移
        if pyxel.btnr(pyxel.KEY_RETURN):
            self.scene = SCENE_PLAY

    #ゲーム画面処理用update
    def update_play_scene(self):
        #Player制御
        self.player.update()
        #EnemyとBulletの当たり判定
        for enemy in enemies:
            for bullet in bullets:
                if (enemy.x + 8    > bullet.x and
                    enemy.x         < bullet.x + 2 and
                    enemy.y + 8    > bullet.y and
                    enemy.y         < bullet.y + 2):
                    #Hit時の処理
                    enemy.hp -= 1
                    bullet.is_alive = False
                    #残りHP判定
                    if enemy.hp <= 0:
                        enemy.is_alive = False
                        self.score += 10
        #list実行
        update_list(bullets)
        update_list(enemies)
        #list更新
        cleanup_list(bullets)
        cleanup_list(enemies)

    #ゲームオーバー画面処理用update
    def update_gameover_scene(self):
        #ENTERでタイトル画面に遷移
        if pyxel.btnr(pyxel.KEY_RETURN):
            self.score = 0
            self.scene = SCENE_TITLE
            #list全要素削除
            bullets.clear()                     #list全要素削除
            enemies.clear()                     #list全要素削除

	#描画関数
    def draw(self):
        #画面クリア 0は黒
        pyxel.cls(0)
        #描画の画面分岐
        if self.scene == SCENE_TITLE:
            self.draw_title_scene()
        elif self.scene == SCENE_PLAY:
            self.draw_play_scene()
        elif self.scene == SCENE_GAMEOVER:
            self.draw_gameover_scene()
        #score表示(f文字列的な)
        pyxel.text(4, 4, f"SCORE {self.score:5}", 7)

    #タイトル画面描画用update
    def draw_title_scene(self):
        pyxel.text(48, 28, "________", 7)
        pyxel.text(32, 58, "- PRESS  ENTER -", 7)

    #ゲーム画面描画用update
    def draw_play_scene(self):
        self.player.draw()
        draw_list(bullets)
        draw_list(enemies)

    #ゲームオーバー画面描画用update
    def draw_gameover_scene(self):
        pyxel.text(44, 40, "GAME OVER", 7)
        pyxel.text(32, 80, "- PRESS ENTER -", 7)
App()
```
ミニゲーム的なゲーム開発の参考になれば幸いです
ゲーム作ろうぜ!!