おねーちゃん!ちょっと気になってたんだけど、Pyxelでキャラクターの絵とかを細かく作ろうと思ったら、図形を組み合わせて作るしかないの?
良い疑問だね!実は、今回ゲーム作りにPyxelを紹介した理由の1つが「それ」なんだ!
え?そうなの??
Pyxelにはドット絵や音楽を作るためのGUIエディタがあって、これがすごく簡単で便利なんだよ!
ほうほう
まぁこれに関しては、エディタを触ってみると凄さがよく分かると思うから、まずは起動してみようか!
ターミナル上で、「hockey.py」が置かれているフォルダをカレントディレクトリにして、下のコマンドを実行してみてね
pyxel edit assets.pyxres
!?!?なんか急に画面が現れた!?
これがエディタ画面だよ!左の大きい枠は16x16のマスになっていて、右側の四角い白枠の範囲に描画されるんだ
右側の部分が1枚の紙になっているイメージで、プログラムで扱う時はその紙のどの箇所(座標)の絵を使うかって命令の出し方をするんだ
なるほどね!じゃあ、右下の「Image」って書かれているところの数字は何なの??
その数字は「何枚目の紙か」を表しているんだ
今回はホッケーの絵くらいだからそのままで良いけど、例えば「キャラクターの画像」と「背景に使う木や雲などの画像」みたいなのは管理を分けたくなるでしょ
確かに、そういう時に1枚の紙じゃなくて複数で分けられるのは便利だね!
あと、色は全部で16色なんだけど、このうちの1色を「透過色」に指定が出来るから、基本的には15色でデザインすると思っておくと良いよ
「透過色」?
これについては画像をプログラムに反映するときに説明するよ
ということで、とりあえずこんな感じでホッケーの絵を作ってみてね!
16x16の範囲内ならどんなデザインでもOKだよ!
オッケー!完成したよ!!
完成したら、Windowsなら「Ctrl + S」、Macなら「control + S」で保存してエディタを終了してね
次は作った画像をプログラムに反映させていくよ!
まずは関数「__init__」で「assets.pyxres」を読み込む処理を追加しよう
def __init__(self):
pyxel.init(160, 120, title="ホッケーゲーム")
pyxel.load("assets.pyxres") # これを追加
self.mode = "TITLE"
self.reset_game()
pyxel.run(self.update, self.draw)
これが無いと作った素材が読み込めないんだね
逆に言えば、これを読み込むだけで全部の素材の読み込みが完了するんだよ
それは読み込み忘れが起きなくなるから良いね!
次はホッケー画像を表示するために関数「draw」の「pyxel.circ()」を「pyxel.blt()」に変更するよ
# 関数「draw」内のホッケーの描画を変更
if self.mode == "PLAY": # プレイ画面
# pyxel.circ(self.x, self.y, self.r, 8)
pyxel.blt(self.x - self.r, self.y - self.r, 0, 0, 0, 16, 16, 0) # 作成したホッケー画像の表示
画像の表示は「pyxel.blt(x, y, img, u, v, w, h, colkey)」となっていて、
x, y : 表示するx,y座標(左上が基準)
img : 何枚目の紙か
u, v : 読み込みたい画像の左上x,yの座標
w, h : 画像の幅と高さ
colkey : 透過したい色の番号
と引数を設定するよ
なるほど、この「colkey」に指定した色が透明になるから、さっき「透過色」って言ってたんだね
あと、x,y座標の指定が「pyxel.circ()」の時は円の中心だったのが「pyxel.blt()」だと左上になってるから、半径分だけズラしてるんだね!
そういうこと
ちなみに、ホッケーの大きさが変わってるから、関数「reset_game」の「self.r」を変更しておこうね
# ホッケーの初期化
def reset_hockey(self):
self.x = pyxel.width // 2
self.y = pyxel.height // 2
self.vx = random.choice([-1, 1])
self.vy = random.choice([-1, 1])
self.r = 8 # ここを変更
そういえばそうだったね!ちゃんと修正しておくよ
おおー、ちゃんとホッケーが作った画像に代わってる!
さて、じゃあ次は「音」を追加してみようか
そうか!なんか寂しいと思ったら音が無かったからだ!
ということで、ホッケー画像を作った時と同じくエディタを起動しようか
おっけー!起動したよ!
じゃあ、画面上の左から3つ目のアイコンをクリックしてみてね
なんか左にピアノの鍵盤が出てきた!
これが「SOUND」のエディタ画面になるよ
そしたら、なんなく鍵盤の横の白い範囲をクリックして音楽を作ってみてね!クリックしたらピンク色のマーカーが付いて、白い範囲の一番下をクリックすると「休符」を意味する青色のマーカーが出てくるよ
こんな感じ...かな??
下の黄色い場所は何なの??
そこは、上から順に「トーン」「ボリューム」「エフェクト」を変更できる場所なんだ
| 項目 | 効果 | 備考 |
|---|---|---|
| TON | 音の種類 |
|
| VOL | 音量 |
|
| EFX | 音の効果 |
|
今回は触らずに続けるけど、もし気になるなら色々試してみてね!
了解!
それと、エディタの上部分の左から4つ目の音符アイコンの「MUSIC」を使えば、単音だけじゃなくて作った音を最大4つまで同時に鳴らすように組み合わせられるようになるよ
単音だけじゃないんだ!じゃあ結構本格的なBGMも作れそうだね!
音楽が作れたら保存してエディタを閉じよう
次は作った音楽をプログラムに反映させるよ!
# 関数「update」内の最初
if self.mode == "PLAY": # プレイ画面
# BGM再生(チャンネル0でSOUND 0をループ)
if not pyxel.play_pos(0): # チャンネル0が再生中でなければ
pyxel.play(0, 0, loop=True)
# 以下省略
お、ちゃんとプレイ画面に入ったら作った音楽が流れ始めた!
単音の場合は「pyxel.play(チャンネル番号, SOUNDで作った音の番号, ループするかどうか)」って感じで設定できるよ
チャンネル番号??MUSICのエディタ画面で最大4つまで音が鳴らせるって言ってたから、その番号のことかな
おー察しが良いね!つまり、チャンネル番号に設定できるのは「0~3」になるから覚えておいてね
おっけー!ちなみに、「MUSIC」で作った音を鳴らす場合はどうしたら良いの??
その場合は、「pyxel.playm(MUSICで作った曲の番号, ループするかどうか)」で良いよ!
あと、音楽を止める場合は「pyxel.stop()」を使えばOK!
今回はゲームが終了した時に音楽を止めるようにしよう
# 関数「update」内の最初
elif self.mode == "GAMEOVER": # 終了画面
pyxel.stop() # これを追加
if pyxel.btnp(pyxel.KEY_SPACE):
self.mode = "TITLE"
ゲームが終わったらちゃんとBGMが止まるようになった!
バッチリだね!他にも、ホッケーとバーが衝突した時や得点が入った時にちょっとした音が鳴るようにしてみると良いかもね
ちょっと自分なりに色々改良してみる!
ひとまずホッケーゲームはこれで完成だよ!最後に、今回のホッケーをちょっと改良してみたデータを置いておくから、もし気になったら見てみてね
完成したプログラム
import pyxel
import random
class App:
def __init__(self):
pyxel.init(160, 120, title="ホッケーゲーム")
pyxel.load("assets.pyxres")
self.mode = "TITLE"
self.reset_game()
pyxel.run(self.update, self.draw)
def reset_game(self):
# ホッケーの初期化
self.reset_hockey()
# プレイヤー(左側)のバー
self.bar_height = 20 # バーの高さ
self.bar_width = 5 # バーの幅
self.bar_x = 10 # プレイヤーのx座標
self.bar_y = (pyxel.height - self.bar_height) // 2 # プレイヤーのy座標
# CPU(右側)のバー
self.cpu_x = pyxel.width - 10 - self.bar_width # CPUのx座標
self.cpu_y = (pyxel.height - self.bar_height) // 2 # CPUのy座標
# スコア用
self.score_player = 0
self.score_cpu = 0
# ホッケーの初期化
def reset_hockey(self):
self.x = pyxel.width // 2
self.y = pyxel.height // 2
self.vx = random.choice([-1, 1])
self.vy = random.choice([-1, 1])
self.r = 8
def update(self):
if self.mode == "PLAY": # プレイ画面
# BGM再生(チャンネル0でSOUND 0をループ)
if not pyxel.play_pos(0): # チャンネル0が再生中でなければ
pyxel.play(0, 0, loop=True)
self.x += self.vx
self.y += self.vy
# プレイヤーバーの操作(上下キー)
if pyxel.btn(pyxel.KEY_UP):
self.bar_y -= 2
if pyxel.btn(pyxel.KEY_DOWN):
self.bar_y += 2
self.bar_y = max(0, min(pyxel.height - self.bar_height, self.bar_y))
# CPUバーの自動追尾
if random.random() > 0.2: # 80%の確率で追尾
if self.y > self.cpu_y + self.bar_height // 2:
self.cpu_y += 1.2
elif self.y < self.cpu_y + self.bar_height // 2:
self.cpu_y -= 1.2
self.cpu_y = max(0, min(pyxel.height - self.bar_height, self.cpu_y))
# ウィンドウの端に当たったらリセット
if self.x <= self.r: # 左側が当たった場合はCPUの得点
self.score_cpu += 1
self.reset_hockey()
elif self.x >= pyxel.width - self.r:
self.score_player += 1 # 右側が当たった場合はプレイヤーの得点
self.reset_hockey()
if self.y <= self.r or self.y >= pyxel.height - self.r:
self.vy *= -1
# 勝利条件チェック
if self.score_player >= 5 or self.score_cpu >= 5:
self.mode = "GAMEOVER"
# プレイヤーバーの衝突判定
if (
self.x + self.r >= self.bar_x and
self.x - self.r <= self.bar_x + self.bar_width and
self.y + self.r >= self.bar_y and
self.y - self.r <= self.bar_y + self.bar_height
):
self.vx = abs(self.vx) + 1
# CPUの衝突判定
if (
self.x - self.r <= self.cpu_x + self.bar_width and
self.x + self.r >= self.cpu_x and
self.y + self.r >= self.cpu_y and
self.y - self.r <= self.cpu_y + self.bar_height
):
self.vx = -(abs(self.vx) + 1)
elif self.mode == "TITLE": # タイトル画面
# スペースで開始
if pyxel.btnp(pyxel.KEY_SPACE):
self.reset_game()
self.mode = "PLAY"
elif self.mode == "GAMEOVER": # 終了画面
pyxel.stop()
if pyxel.btnp(pyxel.KEY_SPACE):
self.mode = "TITLE"
def draw(self):
pyxel.cls(0)
if self.mode == "PLAY": # プレイ画面
# pyxel.circ(self.x, self.y, self.r, 8)
pyxel.blt(self.x - self.r, self.y - self.r, 0, 0, 0, 16, 16, 0)
pyxel.rect(self.bar_x, self.bar_y, self.bar_width, self.bar_height, 11)
pyxel.rect(self.cpu_x, self.cpu_y, self.bar_width, self.bar_height, 14)
pyxel.text(20, 4, f"YOU: {self.score_player:04}", 11)
pyxel.text(100, 4, f"CPU: {self.score_cpu:04}", 14)
elif self.mode == "TITLE": # タイトル画面
pyxel.text(40, 40, "--- HOCKEY GAME ---", 7)
pyxel.text(40, 80, "PRESS SPACE TO START", 6)
elif self.mode == "GAMEOVER": # 終了画面
if self.score_player > self.score_cpu:
winner = "YOU"
else:
winner = "CPU"
pyxel.text(60, 40, f"{winner} WIN!!", 10)
pyxel.text(20, 70, "PRESS SPACE TO RETURN TO TITLE", 7)
App()
