LoginSignup
3
0

More than 3 years have passed since last update.

Wio Terminal に Vacuum Invaders を移植

Posted at

Wio Terminal の CircuitPython に Stage ライブラリを追加する」にて Wio Terminal に Stage ライブラリを組み込みましたが、今回はそれを使って PyBadge 用のゲーム Vacuum Invaders を Wio Terminal に移植してみます。

※ オリジナルの PyBadge でのプレイの様子はここで見れます。

PyBadge の画面が 160x128 であるのに対して、Wio Terminal は 320x240 なので、そのままだと画面の左上 1/4 ぐらいのスペースで動くゲームになってしまうので、ゲーム画面を広げて、エイリアン数も増やしてみました。

PyBadge にはまがりなりにもスピーカーがついていて、Stageライブラリでは wav ファイルからの効果音を鳴らすことができるのですが、Wio Terminal にはブザーしかないので、今回はsimpleio を使って音を出しています。

※ simpleio は CircuitPython Librariesに含まれています。

結果としてはこんなゲームになりました。

IMG_5473.gif

元のゲームソースにある tiles.bmpと以下の code.py、加えて simpleio ライブラリを Wio Terminal に転送しておけば遊べるはずです。

  • code.py
import time
import random
from micropython import const
from simpleio import tone

import board
import stage
import ugame


GAME_FPS = const(34)  # フレームレート

STAGE_WIDTH = const(15)  # ゲームステージの幅(タイル単位)
STAGE_HEIGHT = const(15)  # ゲームステージの高さ(タイル単位)

ALIENS_WIDTH = const(12)  # エイリアングリッドの幅(タイル単位)
ALIENS_HEIGHT = const(5)  # エイリアングリッドの高さ(タイル単位)

TILE_WIDTH_PIXELS = const(16)  # 1タイルの幅(ピクセル単位)
TILE_HEIGHT_PIXELS = const(16)  # 1タイルの硬さ(ピクセル単位)

STAGE_WIDTH_PIXELS = const(240)  # STAGE_WIDTH * TILE_WIDTH_PIXELS
STAGE_HEIGHT_PIXELS = const(240)  # STAGE_HEIGHT * TILE_HEIGHT_PIXELS
ALIENS_WIDTH_PIXELS = const(192)  # ALIENS_WIDTH * TILE_WIDTH_PIXELS
ALIENS_HEIGHT_PIXELS = const(80)  # ALIENS_HEIGHT * TILE_HIGHT_PIXELS
DEAD_LINE = const(160)  # (STAGE_HEIGHT - ALIENS_HEIGHT) * TILE_HEIGHT_PIXELS

TILE_SPACE = const(1)  # Bank内の宇宙画像のインデックス
TILE_STAR1 = const(2)  # Bank内の星1画像のインデックス
TILE_STAR2 = const(3)  # Bank内の星2画像のインデックス
TILE_SHIP = const(4)  # Bank内の宇宙船画像のインデックス
TILE_BOMB = const(6)  # Bank内の爆破画像のインデックス
TILE_ALIEN = const(8)  # Bank内のエイリアン画像のインデックス
TILE_SAUCER = const(9)  # Bank内の円盤画像のインデックス
TILE_MISSILE = const(12)  # Bank内のミサイル画像のインデックス

tiles = stage.Bank.from_bmp16('tiles.bmp')  # タイルやスプライトのための画像読み込み

text_area = stage.Text(9, 1)  # テキスト表示エリアの用意(横文字数、縦文字数)
text_area.move(80, 120)  # テキストエリアの表示位置


class Ship(stage.Sprite):
    '''宇宙船スプライト。
    '''

    def __init__(self):
        '''初期化。
        '''
        super(Ship, self).__init__(tiles, TILE_SHIP, 110, 214)
        self.dx = 0  # 移動増分
        self.x = 110  # 初期 x 位置
        self.tick = 0  # 
        self.dead = False  # やられたか

    def update(self):
        '''表示の更新。
        '''
        super(Ship, self).update()
        self.tick = not self.tick

        keys = ugame.buttons.get_pressed()
        self.set_frame(4, 0 if self.tick else 4)
        if keys & ugame.K_RIGHT:
            self.dx = min(self.dx + 1, 4)
            self.set_frame(5, 0)
        elif keys & ugame.K_LEFT:
            self.dx = max(self.dx - 1, -4)
            self.set_frame(5, 4)
        else:
            self.dx = self.dx // 2
        if keys & ugame.K_SELECT:
            if missile.y <= -TILE_HEIGHT_PIXELS:
                missile.move(self.x, self.y)
                tone(board.BUZZER, 523.25, 0.005)
                tone(board.BUZZER, 783.99, 0.005)
            elif missile1.y <= TILE_HEIGHT_PIXELS:
                missile1.move(self.x, self.y)
                tone(board.BUZZER, 523.25, 0.005)
                tone(board.BUZZER, 783.99, 0.005)
            elif missile2.y <= TILE_HEIGHT_PIXELS:
                missile2.move(self.x, self.y)
                tone(board.BUZZER, 523.25, 0.005)
                tone(board.BUZZER, 783.99, 0.005)
        if keys & ugame.K_O:
            pause(' Pause...')
        self.x = max(min(self.x + self.dx, 224), 0)
        self.move(self.x, self.y)


class Saucer(stage.Sprite):
    '''円盤スプライト。
    '''

    def __init__(self):
        '''初期化。
        '''
        super(Saucer, self).__init__(tiles, 9, 0, 0)
        self.tick = 0
        self.dx = 4

    def update(self):
        '''表示の更新。
        '''
        super(Saucer, self).update()
        self.tick = (self.tick + 1) % 6
        self.layer.frame(TILE_SAUCER, 0 if self.tick >= 3 else 4)
        if self.x >= 240 or self.x <= -TILE_WIDTH_PIXELS:
            self.dx = -self.dx
        self.move(self.x + self.dx, self.y)
        if abs(self.x - ship.x) < 4 and bomb.y >= 240:
            bomb.move(self.x, self.y)


class Bomb(stage.Sprite):
    '''爆破スプライト。
    '''

    def __init__(self):
        '''初期化。
        '''
        super(Bomb, self).__init__(tiles, TILE_BOMB, 0, 240)
        self.boom = 0

    def update(self):
        '''表示の更新。
        '''
        super(Bomb, self).update()
        if self.y >= STAGE_HEIGHT_PIXELS:
            return
        if self.boom:
            if self.boom == 1:
                tone(board.BUZZER, 493.88, 0.005)
                tone(board.BUZZER, 349.23, 0.005)
            self.set_frame(12 + self.boom, 0)
            self.boom += 1
            if self.boom > 4:
                self.boom = 0
                ship.dead = True
                self.move(self.x, 240)
            return
        self.move(self.x, self.y + 8)
        self.set_frame(6, 0 if self.y % TILE_HEIGHT_PIXELS else 4)
        if stage.collide(self.x + 4, self.y + 4, self.x + 12, self.y + 12,
                         ship.x + 4, ship.y + 4, ship.x + 12, ship.y + 12):
            self.boom = 1


class Missile(stage.Sprite):
    '''ミサイルスプライト。
    '''

    def __init__(self, power):
        '''初期化。
        '''
        super(Missile, self).__init__(tiles, TILE_MISSILE, 0, -32)
        self.boom = 0
        self.power = power

    def update(self):
        '''表示の更新。
        '''
        super(Missile, self).update()
        if self.boom:
            if self.boom == 1:
                tone(board.BUZZER, 493.88, 0.005)
                tone(board.BUZZER, 349.23, 0.005)
            self.set_frame(TILE_MISSILE + self.boom)
            self.boom += 1
            if self.boom > 4:
                self.boom = 0
                self.kill()
                aliens.tile(self.ax, self.ay, 0)
                aliens.dirty = True
            return

        if self.y <= -32:
            return
        self.move(self.x, self.y - 8)
        self.set_frame(TILE_MISSILE - self.power,
                       0 if self.y % TILE_HEIGHT_PIXELS == 6 else 4)
        self.ax = (self.x + 8 - aliens.x) // TILE_WIDTH_PIXELS
        self.ay = (self.y + 4 - aliens.y) // TILE_HEIGHT_PIXELS
        if (aliens.tile(self.ax, self.ay)
            and (self.x + 10 - aliens.x) % TILE_WIDTH_PIXELS > 4):
            aliens.tile(self.ax, self.ay, 7)
            self.move(self.x, self.y - 4)
            self.boom = 1

    def kill(self):
        '''
        '''
        self.move(self.x, -32)
        self.set_frame(12 - self.power)


class Aliens(stage.Grid):
    '''エイリアングリッド。
    '''

    def __init__(self):
        '''初期化。
        '''
        super(Aliens, self).__init__(tiles, ALIENS_WIDTH, ALIENS_HEIGHT)
        for y in range(ALIENS_HEIGHT):
            for x in range(ALIENS_WIDTH):
                self.tile(x, y, TILE_ALIEN)
        self.tick = 0
        self.left = 0  # 左側エイリアン全滅数列ピクセル数
        self.right = 0  # 右側エイリアン全滅列ピクセル数
        self.descend = 0
        self.dx = 2  # 水平方法移動単位ピクセル数
        self.dirty = False

    def update(self):
        '''表示の更新。
        '''
        self.tick = (self.tick + 1) % 4
        self.layer.frame(0, 0 if self.tick >= 2 else 4)
        if self.tick in (0, 2):
            if (self.x >= (TILE_WIDTH_PIXELS * (STAGE_WIDTH - ALIENS_WIDTH)
                           - self.dx + self.right)
                or self.x <= 2 - self.left):
                self.y += 1
                self.descend += 1
                if self.descend >= 4:
                    self.descend = 0
                    self.dx = -self.dx
                    self.x += self.dx
            else:
                self.x += self.dx
            self.move(self.x, self.y)

    def reform(self):
        '''エイリアングリッドの左右の端に関する情報を更新する。
        '''
        self.left = TILE_WIDTH_PIXELS * (ALIENS_WIDTH - 1)
        self.right = TILE_HEIGHT_PIXELS * (ALIENS_WIDTH - 1)
        for x in range(ALIENS_WIDTH):
            for y in range(ALIENS_HEIGHT):
                if self.tile(x, y):  # 指定座標のエイリアンが生きている
                    self.left = min(TILE_WIDTH_PIXELS * x, self.left)
                    self.right = min((ALIENS_WIDTH - 1) * TILE_WIDTH_PIXELS
                                     - TILE_WIDTH_PIXELS * x, self.right)
        self.dirty = False


def pause(info):
    '''ポーズ。

    :param info: ポーズ中に表示するテキスト。
    '''
    while ugame.buttons.get_pressed() & ugame.K_O:
        time.sleep(0.25)
    text_area.cursor(0, 0)
    text_area.text(info)
    game.render_block()
    while not ugame.buttons.get_pressed() & ugame.K_O:
        time.sleep(0.25)
    text_area.clear()
    game.render_block()
    while ugame.buttons.get_pressed() & ugame.K_O:
        time.sleep(0.25)


while True:
    space = stage.Grid(tiles, STAGE_WIDTH, STAGE_HEIGHT)
    game = stage.Stage(ugame.display, GAME_FPS)

    for y in range(STAGE_HEIGHT):
        for x in range(STAGE_WIDTH):
            space.tile(x, y, TILE_SPACE)
    for i in range(15):
        space.tile(random.randint(0, STAGE_WIDTH - 1),
                   random.randint(0, STAGE_HEIGHT - 1),
                   random.randint(TILE_STAR1, TILE_STAR2))

    aliens = Aliens()
    aliens.move(8, 17)

    saucer = Saucer()
    bomb = Bomb()
    ship = Ship()
    missile = Missile(0)
    missile1 = Missile(1)
    missile2 = Missile(2)
    sprites = [saucer, bomb, ship, missile, missile1, missile2]
    game.layers = [text_area] + sprites + [aliens, space]
    game.render_block()

    while (aliens.left + aliens.right < STAGE_WIDTH_PIXELS - TILE_WIDTH_PIXELS
           and aliens.y < DEAD_LINE
           and not ship.dead):
        for sprite in sprites:
            sprite.update()
        aliens.update()
        game.render_sprites(sprites)
        game.render_block(aliens.x + aliens.left - 1,
                          aliens.y - 1,
                          aliens.x + ALIENS_WIDTH_PIXELS + 1 - aliens.right,
                          aliens.y + ALIENS_HEIGHT_PIXELS)
        if aliens.dirty:
            aliens.reform()
        game.tick()

    if ship.dead or aliens.y >= DEAD_LINE:
        pause('Game Over')
    else:
        pause('You won!')

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0