見出し画像

pyxelゲーム制作TIPS_RPGの村風

はじめに

このnoteはpythonのレトロゲームエンジン「pyxel」でのゲーム制作に役立つかもしれない小さなサンプルプログラムのTIPSです。
以下の環境で作成・動作させています。
・OS:Windows11
・開発環境:Visual Studio Code(Ver.1.88.1)
・pyxelのバージョン:2.0.12

記事内のコードはご自由にお使いください。イメージ等もファイルは添付していませんが、模写(?)して使っていただいて構いません。
また、pyxelの動作環境や各関数の詳細な説明はpyxel公式GitHubをご参照ください。

RPGの村といえば

シームレスに移動出来たり、ワールドマップからアイコンに入ったりといろいろですが、まあおおむね「店があり」「宿屋があり」「情報を集めて」「なんかイベントも起こったり」といった具合でしょうか。基本的には戦闘が起きない区域なのですが、ここをあえて侵すことでイベントの衝撃度を高めている作品もありますね。
今回は以下のnoteで作った見下ろし形RPGの基本構造を流用して、「マップへのイベントの配置」「建物に入る(場面転換をする)」の2点を作ってみたいと思います。

作っていきましょう

それでは始めましょう。

まずは画面の表示、キャラクターの移動までのコードをペタッと。

import pyxel


class App:
    def __init__(self):
        #ここで起動時の処理をします                                
        pyxel.init(128, 128)        
        pyxel.load('./sample02.pyxres')    
        self.player_pos = [16, 16]         
        pyxel.run(self.update, self.draw)

    def update(self):
        #ここで毎フレームの更新作業をします
        #コントロール部分###################
        if pyxel.btnp(pyxel.KEY_RIGHT):
            if self.move_check(self.player_pos[0]+8, self.player_pos[1]):
                self.player_pos[0] += 8
            
        elif pyxel.btnp(pyxel.KEY_LEFT):
            if self.move_check(self.player_pos[0]-8, self.player_pos[1]):
                self.player_pos[0] -= 8

        elif pyxel.btnp(pyxel.KEY_UP):
            if self.move_check(self.player_pos[0], self.player_pos[1]-8):
                self.player_pos[1] -= 8

        elif pyxel.btnp(pyxel.KEY_DOWN):
            if self.move_check(self.player_pos[0], self.player_pos[1]+8):
                self.player_pos[1] += 8

    def move_check(self, x, y):
        if pyxel.tilemaps[0].pget(x//8, y//8) == (0, 1):
            return False
        else:
            return True

    def draw(self):
        #ここで毎フレームの描画作業をします
        pyxel.cls(0)        
        pyxel.bltm(0, 0, 0, 0, 0, 128, 128)       
        pyxel.blt(self.player_pos[0], self.player_pos[1], 0, 0, 0, 8, 8, 14)


App()
▲実行すると画面が表示されます。ここまではOK。

ではとりあえずお絵描きをします。ちょうど真ん中に部屋のようなものがあるので、これを基に建物を作りましょう。

▲いろいろとタイルを作成。
▲建物っぽくして、周りを柵で囲いました。
右下のレバーを倒したら入れるようにしましょう。

さて、タイルマップを増やしたせいで移動時のチェックを見直す必要が出てきました。ここでは「1行目のタイルは移動可能」というルールで作ってみます。move_check関数を修正しましょう。

    def move_check(self, x, y):        
        if pyxel.tilemaps[0].pget(x//8, y//8)[1] > 0:
            return False
        else:
            return True

pyxel.tilemaps[0].pget(x//8, y//8)[1]とすることでpgetで取得したタプルの2つ目の要素を選択できます。(a, b)という感じで返ってくるのでbのみになる形です。ここが>0なら移動できないこととすれば、上記の「1行目のタイルは移動可能」というルールに合致するはず。
※pythonでは(例外もありますが)数字は0からスタートするので>0としています。

▲移動判定がきちんと行われ、柵の中には入れなくなりました。

さて、では右下にポツンとあるレバーに役割を与えてあげましょう。やりたいこととしては、「レバーを引くと扉が開く」「作動後のレバーはグラフィックを変える」です。という事はタイルマップを書き換える必要があるということ。やってみましょう。

    def event_check(self, x, y):
        t = pyxel.tilemaps[0].pget(x//8, y//8)
        if t == (0, 4):
            pyxel.tilemaps[0].pset(11, 12, (1, 4))
            pyxel.tilemaps[0].pset(7, 9, (2, 0))
            pyxel.tilemaps[0].pset(8, 9, (3, 0))
        else:
            pass

新たにevent_check関数を作成しました。move_check関数と同じように移動先のタイルを取得して条件を分岐させています。レバーのタイルは(0, 4)なので、if t == (0, 4):の分岐にイベント処理を書きます。
タイルマップを書き換えるにはpyxel.tilemaps[t].psetの命令を使用します。引数は(書き換えたいマスx、書き換えたいマスy、書き換えるタイルの種類)です。今回書いたコードでは上から「レバーの見た目を起動後のものに」「左のドアを通路に」「右のドアを通路に」変更しています。

このevent_check関数をコントロール部分から呼び出しましょう。

    def update(self):
        #ここで毎フレームの更新作業をします
        #コントロール部分###################
        if pyxel.btnp(pyxel.KEY_RIGHT):
            if self.move_check(self.player_pos[0]+8, self.player_pos[1]):
                self.player_pos[0] += 8
            else:
                self.event_check(self.player_pos[0]+8, self.player_pos[1])
            
        elif pyxel.btnp(pyxel.KEY_LEFT):
            if self.move_check(self.player_pos[0]-8, self.player_pos[1]):
                self.player_pos[0] -= 8
            else:
                self.event_check(self.player_pos[0]-8, self.player_pos[1])                

        elif pyxel.btnp(pyxel.KEY_UP):
            if self.move_check(self.player_pos[0], self.player_pos[1]-8):
                self.player_pos[1] -= 8
            else:
                self.event_check(self.player_pos[0], self.player_pos[1]-8)                

        elif pyxel.btnp(pyxel.KEY_DOWN):
            if self.move_check(self.player_pos[0], self.player_pos[1]+8):
                self.player_pos[1] += 8
            else:
                self.event_check(self.player_pos[0], self.player_pos[1]+8)      

今までmove_check関数でFalseが返った場合は何もしていませんでしたが、この場合にevent_check関数を呼ぶように変更。障害物が特定のマスならイベントを実行します。

▲レバーに触ると扉が開きました。

これで柵の中に入ることができるようになりました。同じように次は建物に入るイベントを作ってみましょう。今回はマップの転換も行い、室内マップを表示させます。

▲室内マップを作りました。

ではevent_check関数に分岐を追加して、建物の入り口マスに触れたら建物内に移動するようにしましょう。入口のマスは(2, 3)と(3, 3)です。

    def event_check(self, x, y):
        t = pyxel.tilemaps[0].pget(x//8, y//8)
        if t == (0, 4):
            pyxel.tilemaps[0].pset(11, 12, (1, 4))
            pyxel.tilemaps[0].pset(7, 9, (2, 0))
            pyxel.tilemaps[0].pset(8, 9, (3, 0))
        elif t == (2, 3) or t == (3, 3):
            self.player_pos = [7*8, 22*8]
        else:
            pass

t == (2, 3) or t == (3, 3):の場合にキャラクターを(7, 22)のマスに移動させています。タイルマップは8ドットで1マスなので、実際はそれぞれの座標に8を掛けた値を設定します。

▲建物の入り口に触れるとキャラクターが移動しました。

これでキャラクター座標はきちんと移動しているのですが、画面外に行ってしまっているので表示されません。pyxel.cameraを使って画面の描画位置も移動させましょう。

    def event_check(self, x, y):
        t = pyxel.tilemaps[0].pget(x//8, y//8)
        if t == (0, 4):
            pyxel.tilemaps[0].pset(11, 12, (1, 4))
            pyxel.tilemaps[0].pset(7, 9, (2, 0))
            pyxel.tilemaps[0].pset(8, 9, (3, 0))
        elif t == (2, 3) or t == (3, 3):
            self.player_pos = [7*8, 22*8]
            pyxel.camera(0, 16*8)
        else:
            pass

pyxel.camera(0, 16*8)で画面の描画位置を16マス下にずらします。

    def draw(self):
        #ここで毎フレームの描画作業をします
        pyxel.cls(0)        
        pyxel.bltm(0, 0, 0, 0, 0, 128, 256)       
        pyxel.blt(self.player_pos[0], self.player_pos[1], 0, 0, 0, 8, 8, 14)

これに合わせてタイルマップも16マス下まで読み込んでおくように変更。
pyxel.bltm(0, 0, 0, 0, 0, 128, 256)の256部分が変更されています。

▲室内マップに入ることができました。

さあ、最後はここから元の室外マップに戻る処理を付ければ完成です。
キャラクターの移動は同じようにして、カメラは初期位置なので(0, 0)に合わせます。

    def event_check(self, x, y):
        t = pyxel.tilemaps[0].pget(x//8, y//8)
        if t == (0, 4):
            pyxel.tilemaps[0].pset(11, 12, (1, 4))
            pyxel.tilemaps[0].pset(7, 9, (2, 0))
            pyxel.tilemaps[0].pset(8, 9, (3, 0))
        elif t == (2, 3) or t == (3, 3):
            self.player_pos = [7*8, 22*8]
            pyxel.camera(0, 16*8)
        elif t == (4, 3) or t == (5, 3):
            self.player_pos = [7*8, 7*8]
            pyxel.camera(0, 0)
        else:
            pass

室外へ出る際の処理、elif t == (4, 3) or t == (5, 3):以下を作成しました。先ほどと同様に、「キャラクター位置を設定」して「カメラ位置を調整」します。

▲戻ってこれました。
これでマップの行き来が可能です。

これにて完成です。現状ではただ建物内に入ることができるだけなので、ショップのような機能を持たせるとぐっとゲームらしくなりそうです。建物が1つというのは寂しいので、もっと増やすのも良いと思います。

最後にすべてつなげたソースを載せて終わりにしようと思います。

import pyxel


class App:
    def __init__(self):
        #ここで起動時の処理をします                                
        pyxel.init(128, 128)        
        pyxel.load('./sample02.pyxres')    
        self.player_pos = [16, 16]         
        pyxel.run(self.update, self.draw)

    def update(self):
        #ここで毎フレームの更新作業をします
        #コントロール部分###################
        if pyxel.btnp(pyxel.KEY_RIGHT):
            if self.move_check(self.player_pos[0]+8, self.player_pos[1]):
                self.player_pos[0] += 8
            else:
                self.event_check(self.player_pos[0]+8, self.player_pos[1])
            
        elif pyxel.btnp(pyxel.KEY_LEFT):
            if self.move_check(self.player_pos[0]-8, self.player_pos[1]):
                self.player_pos[0] -= 8
            else:
                self.event_check(self.player_pos[0]-8, self.player_pos[1])                

        elif pyxel.btnp(pyxel.KEY_UP):
            if self.move_check(self.player_pos[0], self.player_pos[1]-8):
                self.player_pos[1] -= 8
            else:
                self.event_check(self.player_pos[0], self.player_pos[1]-8)                

        elif pyxel.btnp(pyxel.KEY_DOWN):
            if self.move_check(self.player_pos[0], self.player_pos[1]+8):
                self.player_pos[1] += 8
            else:
                self.event_check(self.player_pos[0], self.player_pos[1]+8)                

    def move_check(self, x, y):        
        if pyxel.tilemaps[0].pget(x//8, y//8)[1] > 0:
            return False
        else:
            return True
        
    def event_check(self, x, y):
        t = pyxel.tilemaps[0].pget(x//8, y//8)
        if t == (0, 4):
            pyxel.tilemaps[0].pset(11, 12, (1, 4))
            pyxel.tilemaps[0].pset(7, 9, (2, 0))
            pyxel.tilemaps[0].pset(8, 9, (3, 0))
        elif t == (2, 3) or t == (3, 3):
            self.player_pos = [7*8, 22*8]
            pyxel.camera(0, 16*8)
        elif t == (4, 3) or t == (5, 3):
            self.player_pos = [7*8, 7*8]
            pyxel.camera(0, 0)
        else:
            pass

    def draw(self):
        #ここで毎フレームの描画作業をします
        pyxel.cls(0)        
        pyxel.bltm(0, 0, 0, 0, 0, 128, 256)       
        pyxel.blt(self.player_pos[0], self.player_pos[1], 0, 0, 0, 8, 8, 14)


App()

ここまで読んでいただきありがとうございました。

※有料エリアですが、特に何もありません。設定しているだけですので、お気に召しましたら購入いただけると嬉しいです。

ここから先は

28字

¥ 100

この記事が参加している募集

ここまで読んでいただきありがとうございます!