プレイヤーの弾を追加

プレイヤーから弾を撃つ処理を追加してみましょう。

画面上のオブジェクトは GameObject クラスで表現することにしたので、弾も同様に GameObject を継承して作ってみます。

追加が必要なポイントは

  • プレイヤーが弾を撃ったら新しい GameObject (弾) を生成したい
  • 「弾を撃つ」ために、スペースキーが「押された」判定を取得したい

の2点ですね。

まず、「弾」ですが、Shot クラスを作ります。

on_update で、初期状態で決めておいた self.speed に従って上方向に進むようにして、画面外に出たら削除します。Shot はこれだけ。簡単ですね。

class Shot(GameObject):
    def __init__(self, x, y):
        super().__init__()
        self.x = x
        self.y = y
        lines = (
            ((0, 0), (0, 16)), # 縦に1本、16ピクセルで弾を表示
        )
        color = (0, 230, 0)
        self.create_lines(lines, color)
        self.speed = 160

    def on_update(self, dt):
        d = dt * self.speed
        self.y += d
        
        # 画面外判定
        if self.y >= WINDOW_HEIGHT + 16:
            self.is_dead = True
        super().on_update(dt)

次は、プレイヤーの on_update で

  • スペースキーが押された判定を取得する
  • スペースキーが押されたら Shot クラスを作る

を書きます。肝になる部分は以下のところです。

        space_key_status = key_status[key.SPACE]
        if space_key_status and not self.prev_space_key_status:
            new_game_objects.append(Shot(self.x, self.y + 16))
        self.prev_space_key_status = space_key_status

キーが「押されている」判定は key_status[] に入っています。「前回の on_update では押されていなくて、今回の on_update で押されている」ことがわかれば入力判定が取れますね。本当は何か別のところで Press 判定取るようにした方が良いのでしょう…。とりあえずプレイヤーの中でやってしまいました。

次に、新しく作った GameObject の処理についてです。いったん new_game_objects に積んで、次のフレームから on_update が回るようにしてみました。

久々に画面写真を載せてみますが、こんな感じです。菱形がプレイヤー、棒が弾、三角が敵…となっています。デザインがいまいちなのが盛り上がりませんが、なんとなくゲームっぽい?

import pyglet
from pyglet import shapes
from pyglet.window import key
from collections import defaultdict
import random
import copy

title = "Pyglet Shooting Game"

WINDOW_WIDTH = 640
WINDOW_HEIGHT = 480
window = pyglet.window.Window(WINDOW_WIDTH, WINDOW_HEIGHT, title)
batch = pyglet.graphics.Batch()

LINE_WIDTH = 2
PLAYER_SPEED = 120

@window.event
def on_draw():
    window.clear()
    batch.draw()

key_status = defaultdict(bool)

@window.event
def on_key_press(symbol, modifiers):
    key_status[symbol] = True

@window.event
def on_key_release(symbol, modifiers):
    key_status[symbol] = False

game = None
game_objects = []
new_game_objects = []

class GameObject():
    def __init__(self):
        self.is_dead = False
        self.line_shapes = []
        self.lines = None
        self.x = 0
        self.y = 0

    def create_lines(self, lines, color):
        self.lines = lines
        for line in lines:
            x1 = self.x + line[0][0]
            y1 = self.y + line[0][1]
            x2 = self.x + line[1][0]
            y2 = self.y + line[1][1]
            self.line_shapes.append(shapes.Line(x1, y1, x2, y2, width=LINE_WIDTH, color=color, batch=batch))

    def on_update(self, dt):
        for (line, line_shape) in zip(self.lines, self.line_shapes):
            line_shape.x = self.x + line[0][0]
            line_shape.y = self.y + line[0][1]
            line_shape.x2 = self.x + line[1][0]
            line_shape.y2 = self.y + line[1][1]

def on_update(dt):
    game.on_update(dt)
    for game_object in game_objects:
        game_object.on_update(dt)
    
    for dead_object in [obj for obj in game_objects if obj.is_dead]:
        game_objects.remove(dead_object)
    
    game_objects.extend(new_game_objects) 
    new_game_objects.clear()

pyglet.clock.schedule_interval(on_update, 1 / 120.0)

class Shot(GameObject):
    def __init__(self, x, y):
        super().__init__()
        self.x = x
        self.y = y
        lines = (
            ((0, 0), (0, 16)), # 縦に1本、16ピクセルで弾を表示
        )
        color = (0, 230, 0)
        self.create_lines(lines, color)
        self.speed = 160

    def on_update(self, dt):
        d = dt * self.speed
        self.y += d
        
        # 画面外判定
        if self.y >= WINDOW_HEIGHT + 16:
            self.is_dead = True
        super().on_update(dt)

class Player(GameObject):
    def __init__(self, x, y):
        super().__init__()
        self.x = x
        self.y = y

        # 菱形
        lines = (
            ((0, 16), (8, 0)), ((8, 0), (0, -16)), 
            ((0, -16), (-8, 0)), ((-8, 0), (0, 16))
        )
        color = (0, 230, 0)
        self.create_lines(lines, color)
        self.prev_space_key_status = False

    def on_update(self, dt):
        d = dt * PLAYER_SPEED
        dx = 0
        dy = 0
        if key_status[key.W]: # up
            dy = d
        elif key_status[key.S]: # down
            dy = -d
      
        if key_status[key.A]: # left
            dx = -d
        elif key_status[key.D]: # right
            dx = d

        space_key_status = key_status[key.SPACE]
        if space_key_status and not self.prev_space_key_status:
            new_game_objects.append(Shot(self.x, self.y + 16))
        self.prev_space_key_status = space_key_status

        self.x += dx
        self.y += dy
        super().on_update(dt)

class Enemy0(GameObject):
    def __init__(self, x, y):
        super().__init__()
        self.x = x
        self.y = y
        self.speed = 80

        # 三角
        lines = (
            ((-16, 16), (16, 16)), ((16, 16), (0, -16)), ((0, -16), (-16, 16)), 
        )
        color = (230, 230, 0)
        self.create_lines(lines, color)

    def on_update(self, dt):
        d = dt * self.speed
        self.y -= d
        if self.y < -32:
            self.is_dead = True
        super().on_update(dt)


class Game():
    def __init__(self):
        self.enemy_timer = 0

    def on_update(self, dt):
        self.enemy_timer += dt

        if self.enemy_timer > 1.0:
            self.enemy_timer = 0
            enemy_size = 32
            enemy_x = random.randint(enemy_size, WINDOW_WIDTH - enemy_size)
            enemy_y = WINDOW_HEIGHT + enemy_size
            enemy = Enemy0(enemy_x, enemy_y)
            game_objects.append(enemy)

def create_player():
    player = Player(WINDOW_WIDTH // 2, WINDOW_HEIGHT // 3)
    game_objects.append(player)

def create_game():
    global game
    game = Game()

create_game()
create_player()

pyglet.app.run()

タイトルとURLをコピーしました