人工知能に関する断創録

このブログでは人工知能のさまざまな分野について調査したことをまとめています(更新停止: 2019年12月31日)

ファイアボール

ここでは始点から終点へ向けて弾をまっすぐ飛ばす方法を説明します。1945では敵機が自機に向けてまっすぐ弾を飛ばす際に同じアルゴリズムを使っています。サンプルスクリプトでは、画面の中央(始点)からマウスでクリックした方向(終点)にファイアボールを飛ばします。マウスを押しっぱなしにして動かすとスクリーンショットのように乱れ撃ちできます。

fireball.zip
f:id:aidiary:20100605232453p:plain

サンプルスクリプト

#!/usr/bin/env python
#coding:utf-8
import pygame
from pygame.locals import *
import math
import sys

SCR_RECT = Rect(0, 0, 640, 480)
START = (320, 240)  # ファイアボールの始点

def main():
    pygame.init()
    screen = pygame.display.set_mode(SCR_RECT.size)
    pygame.display.set_caption(u"ファイアボール")
    
    # スプライトグループ
    all = pygame.sprite.RenderUpdates()
    Fireball.containers = all
    # スプライトの画像を登録
    Fireball.image = load_image("fireball.png")
    
    clock = pygame.time.Clock()
    while True:
        clock.tick(60)
        screen.fill((0,0,0))
        mouse_handler()
        all.update()
        all.draw(screen)
        pygame.display.update()
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == KEYDOWN and event.key == K_ESCAPE:
                pygame.quit()
                sys.exit()

def mouse_handler():
    """マウスイベントを検知してファイアボールを発射"""
    mouse_pressed = pygame.mouse.get_pressed()
    if mouse_pressed[0]:
        x, y = pygame.mouse.get_pos()
        Fireball(START, (x,y))

class Fireball(pygame.sprite.Sprite):
    """ファイアボール"""
    speed = 10
    def __init__(self, start, target):
        pygame.sprite.Sprite.__init__(self, self.containers)
        self.rect = self.image.get_rect()
        # 始点と終点をセット
        self.start = start
        self.target = target
        self.rect.center = self.start
        # 終点の角度を計算
        self.direction = math.atan2(target[1]-start[1], target[0]-start[0])
        # 速度を計算
        self.vx = math.cos(self.direction) * self.speed
        self.vy = math.sin(self.direction) * self.speed
    def update(self):
        self.rect.move_ip(self.vx, self.vy)
        # 画面外に出たらオブジェクトを破棄
        if not SCR_RECT.contains(self.rect):
            self.kill()

def load_image(filename, colorkey=None):
    try:
        image = pygame.image.load(filename)
    except pygame.error, message:
        print "Cannot load image:", filename
        raise SystemExit, message
    image = image.convert()
    if colorkey is not None:
        if colorkey is -1:
            colorkey = image.get_at((0,0))
        image.set_colorkey(colorkey, RLEACCEL)
    return image

if __name__ == "__main__":
    main()

始点と終点から角度を求める

f:id:aidiary:20100605232454p:plain

画面中央の点からマウスでクリックした方向にファイボールを飛ばしたいので画面中央の点から見たマウスの位置を求める必要があります。上の図を見てください。始点(start)から終点(target)へ直線を引いたときの角度directionがファイアボールを飛ばす方向となります。始点の座標(画面中央の点)と終点の座標(マウス位置)からその間の角度を求めるにはmath.atan2()を使います。数学では逆正接 (arctangent) と呼ばれます。難しいことはともかく使い方は簡単です。

  # 終点の角度を計算
  self.direction = math.atan2(target[1]-start[1], target[0]-start[0])
  print math.degrees(self.direction)  # 角度を度で表示

第一引数には始点から終点のY座標の変化量、第二引数にはX座標の変化量を与えます。これで角度directionが求まります。 directionは-180度〜180度をラジアンという単位で表示した値です。Xの正の方向から半時計周りは負の角度、時計回りは正の角度となります。ラジアン単位をよく使われる度単位に変換するにはmath.degrees()が使えます。ソースコード中のdirectionの値を表示してみると角度がいくつか直感的につかめるので確かめてみてください。

角度から速度を求める

f:id:aidiary:20100605232455p:plain

始点から終点への角度が求まりました。次は角度から速度を求めて見ます。ファイアボールの速度はspeed=10と定義されています。これをX方向の速度vxとY方向の速度vyに分解してやる必要があります。この分解はsin()とcos()を使います。

  # 速度を計算
  self.vx = math.cos(self.direction) * self.speed
  self.vy = math.sin(self.direction) * self.speed

やっとX方向の速度vxとY方向の速度vyが求まりました。後は画像の移動と跳ね返り処理(2008/5/9)と同じです。ループが回るたびに距離に速度を足して移動させます。

  self.rect.move_ip(self.vx, self.vy)

atan2()、degrees()、sin()、cos()といった関数はmathモジュールに含まれるのでmathをimportしておきます。mathは数学関係の関数がいろいろそろってます。