#!python # -*- mode: python; Encoding: utf-8; coding: utf-8 -*- # Last updated: <2017/04/24 21:28:13 +0900> u""" PySDL2 のテスト. ポンゲーム。 敵の動きを作成 動作確認環境: Windows10 x64 + Python 2.7.13 32bit + PySDL2 0.9.5 The Pong Game — PySDL2 0.9.5 documentation http://pysdl2.readthedocs.io/en/latest/tutorial/pong.html """ # sdl2 の全機能にアクセスしたいので、sdl2 もインポートする import sys import sdl2 import sdl2.ext # 白色を定義 WHITE = sdl2.ext.Color(255, 255, 255) class SoftwareRenderer(sdl2.ext.SoftwareSpriteRenderSystem): u"""ソフトウェアレンダラークラス.""" def __init__(self, window): u"""コンストラクタ.""" super(SoftwareRenderer, self).__init__(window) def render(self, components): u"""描画処理.""" # 画面全体を黒く塗りつぶしてから、本来の描画処理を行うようにしている sdl2.ext.fill(self.surface, sdl2.ext.Color(0, 0, 0)) super(SoftwareRenderer, self).render(components) class MovementSystem(sdl2.ext.Applicator): u"""各スプライトの移動処理を担当するクラス.""" def __init__(self, minx, miny, maxx, maxy): u"""コンストラクタ.""" super(MovementSystem, self).__init__() self.componenttypes = Velocity, sdl2.ext.Sprite self.minx = minx self.miny = miny self.maxx = maxx self.maxy = maxy def process(self, world, componentsets): u"""スプライトの移動処理.""" for velocity, sprite in componentsets: # 速度を持っているスプライトに対して、 # 画面内から飛び出さないように位置を変更する swidth, sheight = sprite.size sprite.x += velocity.vx sprite.y += velocity.vy sprite.x = max(self.minx, sprite.x) sprite.y = max(self.miny, sprite.y) pmaxx = sprite.x + swidth pmaxy = sprite.y + sheight if pmaxx > self.maxx: sprite.x = self.maxx - swidth if pmaxy > self.maxy: sprite.y = self.maxy - sheight class CollisionSystem(sdl2.ext.Applicator): u"""アタリ処理クラス.""" def __init__(self, minx, miny, maxx, maxy): u"""コンストラクタ.""" super(CollisionSystem, self).__init__() self.componenttypes = Velocity, sdl2.ext.Sprite self.ball = None self.minx = minx self.miny = miny self.maxx = maxx self.maxy = maxy def _overlap(self, item): u"""ボールがプレイヤー(パドル)と当たってるか調べて返す.""" pos, sprite = item if sprite == self.ball.sprite: # 見ている相手がボールなのでアタリ判定はしない return False # 相手の範囲とボールの範囲を取得 left, top, right, bottom = sprite.area bleft, btop, bright, bbottom = self.ball.sprite.area return (bleft < right and bright > left and btop < bottom and bbottom > top) def process(self, world, componentsets): u"""アタリ判定処理.""" # ボールと当たってるスプライトを返す collitems = [comp for comp in componentsets if self._overlap(comp)] if collitems: # 何かに(プレイヤーに)当たったらボールのx速度を反転する self.ball.velocity.vx = -self.ball.velocity.vx # 当たった位置でy速度を変更 sprite = collitems[0][1] ballcentery = self.ball.sprite.y + self.ball.sprite.size[1] // 2 halfheight = sprite.size[1] // 2 stepsize = halfheight // 10 degrees = 0.7 paddlecentery = sprite.y + halfheight if ballcentery < paddlecentery: factor = (paddlecentery - ballcentery) // stepsize self.ball.velocity.vy = -int(round(factor * degrees)) elif ballcentery > paddlecentery: factor = (ballcentery - paddlecentery) // stepsize self.ball.velocity.vy = int(round(factor * degrees)) else: self.ball.velocity.vy = - self.ball.velocity.vy # ボールが上下の壁に当たったらボールのy速度を反転 if (self.ball.sprite.y <= self.miny or self.ball.sprite.y + self.ball.sprite.size[1] >= self.maxy): self.ball.velocity.vy = - self.ball.velocity.vy if (self.ball.sprite.x <= self.minx or self.ball.sprite.x + self.ball.sprite.size[0] >= self.maxx): self.ball.velocity.vx = - self.ball.velocity.vx class Velocity(object): u"""速度保持クラス. コレを持っているクラスは速度があるので、位置が変化するものとして扱われる。 """ def __init__(self): u"""コンストラクタ.""" super(Velocity, self).__init__() self.vx = 0 self.vy = 0 class TrackingAIController(sdl2.ext.Applicator): u"""敵の思考ルーチン.""" def __init__(self, miny, maxy): u"""コンストラクタ.""" super(TrackingAIController, self).__init__() self.componenttypes = PlayerData, Velocity, sdl2.ext.Sprite self.miny = miny self.maxy = maxy self.ball = None def process(self, world, componentsets): u"""思考処理.""" for pdata, vel, sprite in componentsets: if not pdata.ai: continue centery = sprite.y + sprite.size[1] // 2 if self.ball.velocity.vx < 0: # ball is moving away from the AI if centery < self.maxy // 2: vel.vy = 3 elif centery > self.maxy // 2: vel.vy = -3 else: vel.vy = 0 else: bcentery = self.ball.sprite.y + self.ball.sprite.size[1] // 2 if bcentery < centery: vel.vy = -3 elif bcentery > centery: vel.vy = 3 else: vel.vy = 0 class PlayerData(object): u"""パドルががCPUかプレイヤーかを保持するクラス.""" def __init__(self): u"""コンストラクタ.""" super(PlayerData, self).__init__() self.ai = False class Player(sdl2.ext.Entity): u"""プレイヤー(パドル)クラス.""" def __init__(self, world, sprite, posx=0, posy=0, ai=False): u"""コンストラクタ.""" self.sprite = sprite self.sprite.position = posx, posy # スプライトの位置を設定 self.velocity = Velocity() # 速度を持たせる self.playerdata = PlayerData() self.playerdata.ai = ai # プレイヤーにするか、CPUにするかを指定 class Ball(sdl2.ext.Entity): u"""ボールクラス.""" def __init__(self, world, sprite, posx=0, posy=0): u"""コンストラクタ.""" self.sprite = sprite self.sprite.position = posx, posy self.velocity = Velocity() def run(): u"""メイン処理.""" # PySDL2 の初期化 sdl2.ext.init() # ウインドウ作成と表示 window = sdl2.ext.Window("The Pong Game", size=(800, 600)) window.show() # ワールド作成 world = sdl2.ext.World() # 移動処理担当クラスを生成。左上と右下の座標を渡している movement = MovementSystem(0, 0, 800, 600) # アタリ判定処理クラスを生成。左上と右下の座標を渡している collision = CollisionSystem(0, 0, 800, 600) # ソフトウェアレンダラーを生成 spriterenderer = SoftwareRenderer(window) # 敵AI制御クラスを生成 aicontroller = TrackingAIController(0, 600) # 敵AI制御クラス、移動処理担当クラス、 # アタリ判定クラス、レンダラーをワールドに追加 world.add_system(aicontroller) world.add_system(movement) world.add_system(collision) world.add_system(spriterenderer) # 白色のスプライトを3つ作成 factory = sdl2.ext.SpriteFactory(sdl2.ext.SOFTWARE) sp_paddle1 = factory.from_color(WHITE, size=(20, 100)) # プレイヤー1 sp_paddle2 = factory.from_color(WHITE, size=(20, 100)) # プレイヤー2 sp_ball = factory.from_color(WHITE, size=(20, 20)) # ボール # プレイヤーを2つ生成 player1 = Player(world, sp_paddle1, 0, 250) player2 = Player(world, sp_paddle2, 780, 250, True) # ボールを生成 ball = Ball(world, sp_ball, 390, 290) ball.velocity.vx = -3 collision.ball = ball aicontroller.ball = ball running = True # ループ管理用フラグ # メインループ while running: events = sdl2.ext.get_events() # イベント取得 for event in events: if event.type == sdl2.SDL_QUIT: # 閉じるボタンが押されたらメインループを終了させる running = False break if event.type == sdl2.SDL_KEYDOWN: # キーが押された if event.key.keysym.sym == sdl2.SDLK_UP: # 上キーだった player1.velocity.vy = -3 elif event.key.keysym.sym == sdl2.SDLK_DOWN: # 下キーだった player1.velocity.vy = 3 elif event.type == sdl2.SDL_KEYUP: # キーが離された if event.key.keysym.sym in (sdl2.SDLK_UP, sdl2.SDLK_DOWN): # 上キーか下キーだった player1.velocity.vy = 0 # 時間待ち sdl2.SDL_Delay(10) # ワールドの処理 world.process() # ウインドウのグラフィックバッファを更新 # window.refresh() sdl2.ext.quit() # SDL2関係の終了処理 return 0 # 終了コードを返す if __name__ == "__main__": # このスクリプトが単体で呼ばれた時に、ここが処理される ret = run() # メイン処理呼び出し sys.exit(ret) # pythonスクリプトを終了