Pygameで弾幕(超入門編)

弾幕

タスクシステムをPythonで実験しようとしていたら、
知らない間に弾幕を作っていました…。
 
そこで気がついたことをメモ。
 
util.py

class Vec2D:
	def __init__(self, x, y):
		self.x = x
		self.y = y
	def __add__(self, pos):
		return Vec2D(self.x + pos.x, self.y + pos.y)
	def __iadd__(self, pos):
		return Vec2D(self.x + pos.x, self.y + pos.y)
	def __sub__(self, pos):
		return Vec2D(self.x - pos.x, self.y - pos.y)
	def __mul__(self, vec):
		return self.x*vec.x + self.y*vec.y
	def __eq__(self, vec):
		return (self.x==vec.x and self.y==vec.y)
	def nomalize(self):
		d = math.sqrt(self.x ** 2 + self.y ** 2) * 1.0
		if(d == 0): d = 1
		return Vec2D(self.x / d, self.y / d)
	def cross(self, scalar):
		return Vec2D(self.x*scalar, self.y*scalar)
	def distance(self, vec):
		return math.sqrt(pow(self.x - vec.x, 2) + pow(self.y - vec.y, 2))
	def distance2(self, vec):
		return pow(self.x - vec.x, 2) + pow(self.y - vec.y, 2)
	def getOrthogonalVector(self):
		return Vec2D(self.y, -self.x)
	def reverse(self):
		return Vec2D(self.x*-1, self.y*-1)
	def getTuble(self):
		return (self.x, self.y)
	def debug(self):
		print "(x,y)=(%f,%f)" % (self.x, self.y)

 
main.py

import pygame
import math
import random

from util import *

# 文字列の描画
def drawMessage(pos, message):
	font = pygame.font.SysFont("", 25)
	ren = font.render(message, 0, (255, 255, 255, 255))
	pygame.display.get_surface().blit(font.render(message, 0, (255, 255, 255, 255)), pos.getTuble())
# 線分描画
def drawLine(pos1, pos2, color=(0, 0, 255, 255)):
	pygame.draw.line(pygame.display.get_surface(), color, pos1.getTuble(), pos2.getTuble())
# 円描画
def drawCircle(pos, radius, color=(0, 0, 255, 255)):
	pygame.draw.circle(pygame.display.get_surface(), color, pos.getTuble(), radius)
# 矩形描画
# rect = (pos.x, pos.y, size.x, size.y)
def drawRect(rect, color=(0, 0, 255, 255)):
	pygame.draw.rect(pygame.display.get_surface(), color, rect)

# タスク基底クラス
class Task:
	def __init__(self, pos=Vec2D(0,0), mov=Vec2D(0,0)):
		self.pos = pos
		self.mov = mov
		self.alive = True
	def update(self):
		self.pos += self.mov
		if self.isOut():
			self.alive = False
	def isOut(self):
		return (self.pos.x < 0 or 640 < self.pos.x or
				self.pos.y < 0 or 640 < self.pos.y)
	def debug(self):
		print "p(x,y)=(%d,%d) m(x,y)=(%d,%d) a=%d" % (self.pos.x, self.pos.y, self.mov.x, self.mov.y, self.alive)

# 敵クラス
class Enemy(Task):
	def __init__(self, pos=Vec2D(0,0), mov=Vec2D(0,0), bullet=None):
		self.pos    = pos
		self.mov    = mov
		self.alive  = True
		self.bullet = bullet
		self.rot    = 0 # 度
	def update(self):
		self.pos += self.mov
		if self.isOut():
			self.alive = False
		self.bullet(self)

g_tamaList  = []
g_enemyList = []

def bulletRoll(enemy):
	mov = Vec2D(
		5*math.cos(math.radians(enemy.rot)),
		5*math.sin(math.radians(enemy.rot)))
	tama = Task(pos=Vec2D(enemy.pos.x+8,enemy.pos.y+8), mov=mov)
	g_tamaList.append(tama)
	enemy.rot += 12.34
	if(enemy.rot > 360):
		enemy.rot -= 360
def bulletCircle(enemy):
	for i in range(5):
		mov = Vec2D(
			5*math.cos(math.radians(enemy.rot)),
			5*math.sin(math.radians(enemy.rot)))
		tama = Task(pos=Vec2D(enemy.pos.x+8,enemy.pos.y+8), mov=mov)
		g_tamaList.append(tama)
		enemy.rot += 12.34
		if(enemy.rot > 360):
			enemy.rot -= 360
def bulletWave(enemy):
	mov = Vec2D(
		10*math.cos(math.radians(enemy.rot)),
		5)
	tama = Task(pos=Vec2D(enemy.pos.x+8,enemy.pos.y+8), mov=mov)
	g_tamaList.append(tama)
	enemy.rot += 12.34
	if(enemy.rot > 360):
		enemy.rot -= 360
def bulletWide(enemy):
	if(enemy.rot % 33 == 0):
		for j in range(5):
			for i in range(5):
				mov = Vec2D(
					(i**2*0.3 + 3)*math.cos(math.radians(enemy.rot)),
					 i**2*0.3 + 3)
				tama = Task(pos=Vec2D(enemy.pos.x+8,enemy.pos.y+8), mov=mov)
				g_tamaList.append(tama)
			enemy.rot += 20
			if(enemy.rot > 360):
				enemy.rot -= 360
	enemy.rot += 1
	if(enemy.rot > 360):
		enemy.rot = 0

SCREEN  = Vec2D(480, 640)

def main():
	pygame.init()
	pygame.display.set_mode(SCREEN.getTuble(), 0, 32)
	screen = pygame.display.get_surface()

	global g_tamaList
	global g_enemyList

	flgInit = True
	while True:
		for e in pygame.event.get():
			if(e.type == pygame.QUIT): return
		pygame.display.update()
		pygame.time.wait(10)
		screen.fill((0, 0, 0))
		
		mouseL, mouseR, mouseM = pygame.mouse.get_pressed()
		
		# ここから
		# 初期化
		if(flgInit):
			g_enemyList.append(Enemy(pos=Vec2D(240, 240), bullet=bulletCircle))
			flgInit = False
		# 弾の更新・描画
		for enemy in g_enemyList:
			enemy.update()
			drawRect((enemy.pos.x, enemy.pos.y, 16, 16), (0, 255, 0, 255))
			if(not enemy.alive):
				g_enemyList.remove(enemy)
		
		# 弾の更新・描画
		for tama in g_tamaList:
			tama.update()
			drawCircle(tama.pos, 3, (255, 0, 0, 255))
			if(not tama.alive):
				g_tamaList.remove(tama)
		# Debug
		drawMessage(Vec2D(0, 0), "Fire:%d" % (len(g_tamaList)))
	
if(__name__ == "__main__"):
	main()

まず、基本は回転ですね。

def bulletRoll(enemy):
	mov = Vec2D(
		5*math.cos(math.radians(enemy.rot)),
		5*math.sin(math.radians(enemy.rot)))

5の半径で、三角関数を使った回転運動をしています。

	enemy.rot += 12.34

で、角度を適当に増やしていけば、回転撃ちの完成です。
 
 
ただ、これだと、1方向に撃つのを回転しているだけなので、
ちょっと味気ないです。
 
そこで、1フレーム内に複数の方法に撃つ方法を採用します。

def bulletCircle(enemy):
	for i in range(5):
		mov = Vec2D(
			5*math.cos(math.radians(enemy.rot)),
			5*math.sin(math.radians(enemy.rot)))
		tama = Task(pos=Vec2D(enemy.pos.x+8,enemy.pos.y+8), mov=mov)
		g_tamaList.append(tama)
		enemy.rot += 12.34
		if(enemy.rot > 360):
			enemy.rot -= 360

…まあ、、、1フレーム内に5回ループを回しているだけですが…。