pygletで敵管理・敵移動

プレイヤーが動くようになったので、今度は敵を作ります。

  • シンプルな敵(上から出てきて画面下方向に移動するだけ)のクラスを作る
  • 下方向に行って、画面外に出たら消える
  • 一定時間ごとに敵を生成する

このくらいを目標に作業しましょう。

まずはシンプルな敵は簡単ですね。この前作った GameObject を使って作れば何も問題ありません。

  • pygletは画面の下が y=0 なので、下に移動させるときは self.y を減らす

ことに注意です(忘れてて逆に移動してしまった…)。

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
        super().on_update(dt)

さて、画面外に出たら消える、を書かないとですね。pyglet のサンプルを真似して書いてしまうのが良さそうです。つまり…

  • GameObject クラスに「死んだかどうか(is_dead)」を用意する
  • GameObject の on_update の後で、is_dead になったオブジェクトを消す

とすればシンプルに書けそうです。

敵が画面外(敵の大きさは中心から縦横16ピクセルで作っているから、32ピクセルです。余裕を持って32で画面外)に出たら is_dead を Trueに。

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

GameObject の更新ループの後、is_dead を見て削除。リストの内包表記ありましたねー。久しぶりに書きました。

    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 クラスというのを作って、on_update で更新してもらいましょう。

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)

dt に今回のデルタタイムが「秒」単位で入ってくるので、加算していきます。経過時間が1秒を超えたら、画面上部の見えないところに敵を生成するように書きました。

下に移動する敵なので、いい感じに画面の上から出てくるようになります。

まとめるとこんな感じです! だいぶ長くなりましたけれど、まだ 150 行程度だからなんとかなるでしょう。

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

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 = []

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)

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

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)

    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
        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をコピーしました