1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Pyxel】Pyxel × Pymunkでピンボール

Last updated at Posted at 2024-12-28

pyxelのアドベントカレンダーで紹介された記事Pyxel × Pymunkで物理シミュレーションを始めよう!に感化されてpymunkを調べていました。

pymunkのexampleにflipper.pyというピンボールのサンプルがあったのでこれをpyxelに移植しつつpymunkをいろいろいじってみました。

pyxelに移植したソースコードは以下

import pyxel

SCREEN_WIDTH = 600
SCREEN_HEIGHT = 600
INIT_POS = SCREEN_WIDTH//2+20, SCREEN_HEIGHT//4
BALL_RADIUS = 20

class App:
	def __init__( self, pymunk, fps=60 ):
		self.pymunk = pymunk
		self.fps = fps

		pyxel.init( SCREEN_WIDTH, SCREEN_HEIGHT, fps=fps, title="pyxel flipper" )
		self.create_world()
		pyxel.run(self.update, self.draw)

	def create_world(self):
		self.space = self.pymunk.Space()
		self.space.gravity = ( 0.0, 900.0 )
		self.space.sleep_time_threshold = 0.3

		#外壁
		static_lines = [
			self.pymunk.Segment(self.space.static_body, (150, 500), (50, 50), 1.0),
			self.pymunk.Segment(self.space.static_body, (450, 500), (550, 50), 1.0),
			self.pymunk.Segment(self.space.static_body, (50, 50), (300, 0), 1.0),
			self.pymunk.Segment(self.space.static_body, (300, 0), (550, 50), 1.0),
			self.pymunk.Segment(self.space.static_body, (300, 180), (400, 200), 1.0),
		]
		for line in static_lines:
			line.elasticity = 0.7
			line.group = 1
		self.space.add(*static_lines)

		#フリッパー
		fp = [(20, -20), (-120, 0), (20, 20)]
		mass = 100
		moment = self.pymunk.moment_for_poly(mass, fp)

		# right flipper
		self.r_flipper_body = self.pymunk.Body(mass, moment)
		self.r_flipper_body.position = 450, 500
		self.r_flipper_shape = self.pymunk.Poly(self.r_flipper_body, fp)
		self.space.add(self.r_flipper_body, self.r_flipper_shape)

		self.r_flipper_joint_body = self.pymunk.Body(body_type=self.pymunk.Body.KINEMATIC)
		self.r_flipper_joint_body.position = self.r_flipper_body.position
		j = self.pymunk.PinJoint(self.r_flipper_body, self.r_flipper_joint_body, (0, 0), (0, 0))
		s = self.pymunk.DampedRotarySpring(
			self.r_flipper_body, self.r_flipper_joint_body, 0.15, 20000000, 900000
		)
		self.space.add(j, s)

		# left flipper
		self.l_flipper_body = self.pymunk.Body(mass, moment)
		self.l_flipper_body.position = 150, 500
		self.l_flipper_shape = self.pymunk.Poly(self.l_flipper_body, [(-x, y) for x, y in fp])
		self.space.add(self.l_flipper_body, self.l_flipper_shape)

		self.l_flipper_joint_body = self.pymunk.Body(body_type=self.pymunk.Body.KINEMATIC)
		self.l_flipper_joint_body.position = self.l_flipper_body.position
		j = self.pymunk.PinJoint(self.l_flipper_body, self.l_flipper_joint_body, (0, 0), (0, 0))
		s = self.pymunk.DampedRotarySpring(
			self.l_flipper_body, self.l_flipper_joint_body, -0.15, 20000000, 900000
		)
		self.space.add(j, s)

		self.r_flipper_shape.group = self.l_flipper_shape.group = 1
		self.r_flipper_shape.elasticity = self.l_flipper_shape.elasticity = 0.4

		#バンパー
		self.bumper1_body = self.pymunk.Body(body_type=self.pymunk.Body.KINEMATIC)
		self.bumper1_body.position = (240, 100)
		self.bumper1_shape = self.pymunk.Circle( self.bumper1_body, 10 )
		self.bumper1_shape.elasticity = 1.5
		self.space.add(self.bumper1_body, self.bumper1_shape)

		self.bumper2_body = self.pymunk.Body(body_type=self.pymunk.Body.KINEMATIC)
		self.bumper2_body.position = (360, 100)
		self.bumper2_shape = self.pymunk.Circle( self.bumper2_body, 10 )
		self.bumper2_shape.elasticity = 1.5
		self.space.add( self.bumper2_body, self.bumper2_shape )

		#ボール
		self.ball_body = self.pymunk.Body( 1, float('inf') )
		self.ball_body.position = ( INIT_POS )
		self.ball_shape = self.pymunk.Circle( self.ball_body, BALL_RADIUS )
		self.ball_shape.elasticity = 0.9
		self.space.add( self.ball_body, self.ball_shape )

	def update(self):
		from pymunk import Vec2d

		step = 5
		step_dt = 1 / self.fps / step
		for _ in range(step):
			self.space.step(step_dt)

		#操作
		if self.getInputB():
			pyxel.quit()

		# 左フリッパーの制御
		if self.getInputLEFT():  # キーを押している間、フリッパーを上げる
			self.l_flipper_body.apply_impulse_at_local_point(
				Vec2d.unit() * 8000, (-100, 0)
			)
		# 右フリッパーの制御
		if self.getInputRIGHT():  # キーを押している間、フリッパーを上げる
			self.r_flipper_body.apply_impulse_at_local_point(
				Vec2d.unit() * -8000, (-100, 0)
			)

		#ボールが画面外になったかどうかの判定
		if self.ball_body.position.get_distance((300, 300)) > 1000:
			self.ball_body.position = INIT_POS
			self.ball_body.velocity = (0, 0)


	def draw(self):
		pyxel.cls(0)

		#外壁
		pyxel.line( 150, 500, 50, 50, 7 )
		pyxel.line( 450, 500, 550, 50, 7 )
		pyxel.line( 50, 50, 300, 0, 7 )
		pyxel.line( 300, 0, 550, 50, 7 )
		pyxel.line( 300, 180, 400, 200, 7 )

		#フリッパー
		self.r_flipper_body.position = 450, 500
		self.l_flipper_body.position = 150, 500
		self.r_flipper_body.velocity = self.l_flipper_body.velocity = 0, 0

		_x = [0,0,0]
		_y = [0,0,0]

		_cnt = 0
		for v in self.r_flipper_shape.get_vertices():
			_x[_cnt],_y[_cnt] = v.rotated(self.r_flipper_shape.body.angle) + self.r_flipper_shape.body.position
			_cnt += 1

		pyxel.tri(_x[0],_y[0],_x[1],_y[1],_x[2],_y[2],6)

		_cnt = 0
		for v in self.l_flipper_shape.get_vertices():
			_x[_cnt],_y[_cnt] = v.rotated(self.l_flipper_shape.body.angle) + self.l_flipper_shape.body.position
			_cnt += 1

		pyxel.tri(_x[0],_y[0],_x[1],_y[1],_x[2],_y[2],6)


		#バンパー
		x, y = self.bumper1_body.position
		pyxel.circ( x, y, 10, 11 )
		x, y = self.bumper2_body.position
		pyxel.circ( x, y, 10, 11 )

		#ボール
		x, y = self.ball_body.position
		pyxel.circ( x, y, BALL_RADIUS, 14 )


	#-----------------------------------------------------------------
	#入力(キーボード&ジョイパッド)
	#-----------------------------------------------------------------
	#左
	def getInputLEFT(self):
		if pyxel.btn(pyxel.KEY_LEFT) or pyxel.btn(pyxel.GAMEPAD1_BUTTON_DPAD_LEFT):
			return 1
		else:
			return 0
	#右
	def getInputRIGHT(self):
		if pyxel.btn(pyxel.KEY_RIGHT) or pyxel.btn(pyxel.GAMEPAD1_BUTTON_DPAD_RIGHT):
			return 1
		else:
			return 0
	#button-A
	def getInputA(self):
		if pyxel.btnp(pyxel.KEY_Z, hold=10, repeat=10) or pyxel.btnp(pyxel.GAMEPAD1_BUTTON_A, hold=10, repeat=10):
			return 1
		else:
			return 0
	#button-B
	def getInputB(self):
		if pyxel.btnp(pyxel.KEY_X, hold=10, repeat=10) or pyxel.btnp(pyxel.GAMEPAD1_BUTTON_B, hold=10, repeat=10):
			return 1
		else:
			return 0


if __name__ == "__main__":
	import pymunk

	FPS = 60
	App(pymunk, FPS )

スクリーンショットは以下
pyxel_flipper.png

「Pyxel × Pymunkで物理シミュレーションを始めよう!」を Webで動かしたいを参考に
Web化も成功しました。
リンク先:
github-HTML

線を設定することで玉と線の当たり判定してくれて尚且つ物理シミュレートできるのは楽でいいかな。

上記を応用して作ってみたのが以下。
(線が多いのでソースコードは省略、動作は基本的に上記同様です。)
pinball_sample.gif

フリッパーと玉との挙動が怪しいけれど雰囲気はピンボール、発展させればそれっぽくできるかもしれません。画面スクロールさせたりした時にどうなるかは今後の課題です。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?