HPバーを作成する

RPGやシューティングなど、どんなタイプのゲームにも使用することができるHPバーを作成します。

基本的なHPバー

複数のRectを重ねて、バーを作成します。

白い四角形に、一回り小さい黒い四角形を重ねることで、枠を表現します。

そこに青色のバーを重ね、HPバーの見た目を表すことができますね。

ソースコードは、以下のようになります。

import pygame
from pygame.locals import *

class HealthBar():
    def __init__(self, x, y, width, max):
        self.x = x
        self.y = y
        self.width = width
        self.max = max # 最大HP
        self.hp = max # HP
        self.mark = int((self.width - 4) / self.max) # HPバーの1目盛り

        self.font = pygame.font.SysFont(None, 28)
        self.label = self.font.render("HP", True, (255, 255, 255))
        self.frame = Rect(self.x + 2 + self.label.get_width(), self.y, self.width, self.label.get_height())
        self.bar = Rect(self.x + 4 + self.label.get_width(), self.y + 2, self.width - 4, self.label.get_height() - 4)
        self.value = Rect(self.x + 4 + self.label.get_width(), self.y + 2, self.width - 4, self.label.get_height() - 4)

    def update(self):
        self.value.width = self.hp * self.mark

    def draw(self, screen):
        pygame.draw.rect(screen, (255, 255, 255), self.frame)
        pygame.draw.rect(screen, (0, 0, 0), self.bar)
        pygame.draw.rect(screen, (0, 0, 255), self.value)
        screen.blit(self.label, (self.x, self.y))

コンストラクタで、それぞれのRectを作成します。

ついでに「HP」というラベルも作成し、そのレベルの高さと同じ高さのフレームを作成します。

バーの長さは、コンストラクタの引数として渡すwidthと同じ長さになるようにしています(「HP」ラベルの長さは含まず)。

maxは、最大HPの値です。

動きのあるHPバー

このままでも最低限の機能はありますが、もう少し動きがあるとだいぶそれっぽくなります。

HPバーの残り具合によって、バーの色も変えるようにします。

import pygame
from pygame.locals import *

class HealthBar():
    def __init__(self, x, y, width, max):
        self.x = x
        self.y = y
        self.width = width
        self.max = max # 最大HP
        self.hp = max # HP
        self.mark = int((self.width - 4) / self.max) # HPバーの1目盛り

        self.font = pygame.font.SysFont(None, 28)
        self.label = self.font.render("HP", True, (255, 255, 255))
        self.frame = Rect(self.x + 2 + self.label.get_width(), self.y, self.width, self.label.get_height())
        self.bar = Rect(self.x + 4 + self.label.get_width(), self.y + 2, self.width - 4, self.label.get_height() - 4)
        self.value = Rect(self.x + 4 + self.label.get_width(), self.y + 2, self.width - 4, self.label.get_height() - 4)

        # effect_barを追加
        self.effect_bar = Rect(self.x + 4 + self.label.get_width(), self.y + 2, self.width - 4, self.label.get_height() - 4)
        self.effect_color = (0, 255, 255)

    def update(self):
        if self.hp >= self.max:
            self.hp = self.max
            
        if self.effect_bar.width > self.mark * self.hp:
            self.value.width = self.mark * self.hp
            if self.effect_bar.width >= self.value.width:
                self.effect_bar.inflate_ip(-1, 0)
        elif self.value.width < self.mark * self.hp:
            self.effect_bar.width = self.mark * self.hp
            self.value.inflate_ip(1, 0)

        # effect_barの色を変える
        if self.effect_bar.width <= self.bar.width / 6:
            self.effect_color = (255, 255, 0)
        elif self.effect_bar.width <= self.bar.width / 2:
            self.effect_color = (255, 255, 0)
        else:
            self.effect_color = (0, 255, 0)

    def draw(self, screen):
        pygame.draw.rect(screen, (255, 255, 255), self.frame)
        pygame.draw.rect(screen, (0, 0, 0), self.bar)
        pygame.draw.rect(screen, self.effect_color, self.effect_bar)
        pygame.draw.rect(screen, (0, 0, 255), self.value)
        screen.blit(self.label, (self.x, self.y))

update()内でinflate_ip()を使うことで、徐々に減ったり増えたりさせることができます。

さらに、HPが半分以下になるとバーは黄色になり、6分の1以下になると赤になります。

これで、HPバーとしては十分なものになりました。

HPバーの使い方

HPバーはMainクラス内でインスタンス化して使用します。

collision_detection()内でプレイヤーと敵の当たり判定を行い、衝突があればhpを-1します。

def __init__(self);
    # HPバーのインスタンス化
    self.hp = bar.HealthBar(32, 32, 100, 12) # maxの値はwidth-4を割り切れる数にする

(省略)

def collision_detection(self):
    enemy_collided = pygame.sprite.spritecollide(self.player, self.enemies, False)
    if enemy_collided:
        # プレイヤーが無敵でないなら、HPを-1する
        if self.player.invincible == False:
            self.hp.hp -= 1
            self.player.invincible = True

self.player.invincibleという見慣れない属性がありますが、これはプレイヤークラスに新しく追加した属性になります。

これを追加しないと、当たり判定はupdate()のたびに呼び出されるので、あっという間にhpがゼロになってしまいます。

当たりを検出してダメージを受けたら、一定時間無敵時間を設けるようにします。

class Player(pygame.sprite.Sprite):
    speed = 5
    frame = 0
    animecycle = 24
    # 無敵時間の設定
    invincible_time = 64

    def __init__(self, pos):
        (省略)

        # 無敵かどうか
        self.invincible = False

    def update(self):
        (省略)

        # 無敵状態なら無敵時間を減らす
        if self.invincible == True:
            if self.invincible_time == 0:
                self.invincible = False
                self.invincible_time = Player.invincible_time
            else:
                self.invincible_time -= 1

プレイヤークラスに無敵状態かどうかを判定するself.invincibleを定義し、Trueであれば無敵時間を減らすという処理を書いています。

これで、連続で当たり判定が呼び出されてしまうことを防ぎます。

HPが減る一方だと味気ないので、Mainクラスのkey_handler()でスペースキーを押すとHPが回復するようにします。

def key_handler(self):
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
        elif event.type == KEYDOWN and event.key == K_SPACE:
            self.hp.hp += 1

もはやこれだけでもシューティングゲームといっていいくらいのクオリティになってきましたね!