9
3

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とPymunk 動かす! -- 速度とベクトル入門

Last updated at Posted at 2025-12-01

はじめに

 生成AIメニューを使って「PyxelとPymunk」のサンプルプログラムを作成するプロンプトをこれまで、試してきました。動かしてみて、自然なふるまいができるかどうか、検証、確認が、大切であるし、Pymunkで物理空間を生成し、描画はPyxelで行うというデジタルな実験装置をつくるのは、ものづくりの面白さだと思うところです。
 高等学校では、物理基礎を履修する学校もあれば、ないところもあります。CGの世界では、物理的なふるまいは大切なところでもあります。とにかく動くというところから、ハマってもらえるきっかけがあれば、楽しくなると思うところです。
さて、デジタル実験装置のサンプルプログラム、コピペ道場のはじまりです。動かしてパラメータ変更していろいろ試してみてください。

3.1 位置と速度の基本概念

1つの半径20の円が初期位置から一定速度で動くシンプルな例。
速度は1次元(x方向)で固定。
画面に半径20の円を描画し、位置が毎フレーム更新される。

import pyxel
import pymunk

COLOR_WHITE = 7

class SimpleMovingCircle:
    def __init__(self):
        # 画面サイズの設定
        self.width = 640
        self.height = 480
        # Pyxelの初期化(マウスカーソルは非表示)
        pyxel.init(self.width, self.height, title="Simple Moving Circle")
        pyxel.mouse(False)
        # 物理シミュレーションの初期化
        self.reset()
        # メインループ開始
        pyxel.run(self.update, self.draw)
    
    def reset(self):
        # Pymunkの物理空間を作成
        self.space = pymunk.Space()
        # 重力なし(等速直線運動を観察するため)
        self.space.gravity = (0, 0)
        # オブジェクトがスリープ状態に入るまでの時間閾値
        self.space.sleep_time_threshold = 0.5
        
        # 円の物理ボディを作成
        radius = 20
        mass = 1
        # 円の慣性モーメントを計算
        moment = pymunk.moment_for_circle(mass, 0, radius)
        self.body = pymunk.Body(mass, moment)
        # 初期位置:左端、画面中央の高さ
        self.body.position = (radius + 10, self.height // 2)
        # 初期速度:右方向に400px/s
        self.body.velocity = (400, 0)
        
        # 円の形状(コライダー)を作成
        shape = pymunk.Circle(self.body, radius)
        # 弾性係数0.0(完全非弾性、反発なし)
        shape.elasticity = 0.0
        # 摩擦係数0.0(摩擦なし)
        shape.friction = 0.0
        
        # ボディと形状を物理空間に追加
        self.space.add(self.body, shape)
        self.shape = shape
        self.radius = radius
    
    def update(self):
        # 物理シミュレーションを1フレーム分進める(必ず最初に実行)
        self.space.step(1 / 60.0)
        
        # Rキーでリセット
        if pyxel.btnp(pyxel.KEY_R):
            self.reset()
    
    def draw(self):
        # 画面を黒でクリア
        pyxel.cls(0)
        
        # 円の現在位置を取得
        x, y = self.body.position
        # 円を描画(Pyxelの座標系はそのまま使用)
        pyxel.circ(int(x), int(y), self.radius, COLOR_WHITE)

if __name__ == "__main__":
    SimpleMovingCircle()
  • 等速直線運動:重力も摩擦もないため、円は初速度400px/sで右方向へ等速直線運動を続けます
  • Pyxelの座標系:Pyxelは左上が原点(0,0)で下方向がY正のため、Pymunkの座標をそのまま使用(座標変換なし)
  • シンプルな構成:地面や障害物がなく、円の等速運動のみを観察できる最小構成

3.2 1次元運動のプログラム(速度一定の直線運動)

画面640x480、半径20の円。
pymunkを使わずに、速度を変数化し、時間経過で位置が変化。
速度の意味、座標の更新式(位置 += 速度)を示す。
画面外に出たら左端に戻す。

import pyxel

class LinearMotion:
    def __init__(self):
        # 画面サイズの設定
        self.width = 640
        self.height = 480
        # 円の半径
        self.radius = 20
        # 初期位置:左端、画面中央の高さ
        self.x = self.radius
        self.y = self.height // 2
        # 速度:右方向に8px/frame
        self.vx = 8
        # Pyxelの初期化とメインループ開始
        pyxel.init(self.width, self.height, title="1D Linear Motion")
        pyxel.run(self.update, self.draw)
    
    def update(self):
        # 位置を更新(等速直線運動)
        self.x += self.vx
        # 円が画面右端を完全に通過したら左端にリセット
        if self.x - self.radius > self.width:
            self.x = self.radius
    
    def draw(self):
        # 画面を黒でクリア
        pyxel.cls(0)
        # 円を描画(色8=赤)
        pyxel.circ(int(self.x), int(self.y), self.radius, 8)

if __name__ == "__main__":
    LinearMotion()
  • 物理エンジン不使用:Pymunkを使わず、Pyxelのみで実装された等速直線運動
  • シンプルな運動学:x += vx による最も基本的な位置更新
  • ループ処理:画面右端を通過したら左端に戻る無限ループ
  • フレームレート依存:速度8px/frameは、Pyxelのデフォルト60FPSで約480px/s(前のPymunk版の400px/sに近い速度)

前のPymunkコードとの比較:

  • Pymunk版:物理エンジンによる厳密なシミュレーション(質量、慣性モーメント、物理空間)
  • この版:直接的な座標計算による簡易実装(学習の初期段階に適している)

3.3 2次元ベクトルでの速度表現と加算

速度を(x, y)ベクトルで表現。
位置ベクトルに速度ベクトルを足す形で更新。
斜め方向にも動かせる。
画面640x480、半径20の円。
画面端でループさせる

import pyxel

class Vector2D:
    """2次元ベクトルクラス(位置や速度を表現)"""
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):
        """ベクトルの加算演算子をオーバーロード"""
        return Vector2D(self.x + other.x, self.y + other.y)
    
    def copy(self):
        """ベクトルのコピーを作成"""
        return Vector2D(self.x, self.y)

class MovingCircle:
    """2次元空間を移動する円オブジェクト"""
    def __init__(self, pos, vel, radius, screen_width, screen_height):
        self.pos = pos          # 位置ベクトル
        self.vel = vel          # 速度ベクトル
        self.radius = radius    # 円の半径
        self.screen_width = screen_width
        self.screen_height = screen_height
    
    def update(self):
        # 位置を速度分だけ更新(ベクトル加算)
        self.pos = self.pos + self.vel
        
        # X方向の画面端処理(トーラス状のループ)
        if self.pos.x < 0:
            self.pos.x += self.screen_width
        elif self.pos.x > self.screen_width:
            self.pos.x -= self.screen_width
        
        # Y方向の画面端処理(トーラス状のループ)
        if self.pos.y < 0:
            self.pos.y += self.screen_height
        elif self.pos.y > self.screen_height:
            self.pos.y -= self.screen_height
    
    def draw(self):
        """円を描画(色8=赤)"""
        pyxel.circ(int(self.pos.x), int(self.pos.y), self.radius, 8)

class App:
    """メインアプリケーションクラス"""
    def __init__(self):
        # 画面サイズの設定
        self.width = 640
        self.height = 480
        pyxel.init(self.width, self.height, title="2D Vector Velocity")
        
        # 円の初期化:画面中央から斜め右下方向に移動
        pos = Vector2D(self.width / 2, self.height / 2)
        vel = Vector2D(4, 3)  # 速度ベクトル(vx=4, vy=3)
        self.circle = MovingCircle(pos, vel, 20, self.width, self.height)
        
        # メインループ開始
        pyxel.run(self.update, self.draw)
    
    def update(self):
        # 円の位置を更新
        self.circle.update()
    
    def draw(self):
        # 画面を黒でクリア
        pyxel.cls(0)
        # 円を描画
        self.circle.draw()

App()
  • ベクトル演算の導入:Vector2Dクラスで位置と速度をベクトルとして扱う
  • 2次元等速直線運動:速度ベクトル(4, 3)により斜め方向に移動
  • トーラス状ループ:画面の上下左右の端から出ると反対側から現れる(パックマン方式)
  • オブジェクト指向設計:MovingCircleクラスで円の振る舞いをカプセル化

前のコードからの進化:

  • 1D → 2D:X方向のみ → X, Y両方向の運動
  • スカラー → ベクトル:個別の変数 → ベクトルクラスによる統一的な扱い
  • クラス設計:機能を適切に分割(Vector2D, MovingCircle, App)

3.4 ベクトルの大きさ(速度の大きさ)計算

画面640x480、半径20の円。
ベクトルの大きさ(速度の大きさ)計算
速度ベクトルの大きさ(速さ)を計算。
速度の大きさを画面に表示したり、速度の正規化(単位ベクトル化)を紹介。

import pyxel
import pymunk
import math

COLOR_WHITE = 7
COLOR_RED = 8
COLOR_GREEN = 3

class CircleSpeedSimulation:
    def __init__(self):
        # 画面サイズの設定
        self.width = 640
        self.height = 480
        # Pyxelの初期化
        pyxel.init(self.width, self.height, title="Circle Speed Simulation")
        # 物理シミュレーションの初期化
        self.reset()
        # メインループ開始
        pyxel.run(self.update, self.draw)
    
    def reset(self):
        # Pymunkの物理空間を作成
        self.space = pymunk.Space()
        # 重力なし(等速直線運動を観察)
        self.space.gravity = (0, 0)
        # スリープ閾値の設定
        self.space.sleep_time_threshold = 0.5
        
        # 円の物理ボディを作成
        mass = 5
        radius = 20
        # 円の慣性モーメントを計算
        moment = pymunk.moment_for_circle(mass, 0, radius)
        self.body = pymunk.Body(mass, moment)
        # 初期位置:画面中央
        self.body.position = (self.width / 2, self.height / 2)
        
        # 円の形状を作成
        self.shape = pymunk.Circle(self.body, radius)
        # 弾性係数0.8(80%の反発)
        self.shape.elasticity = 0.8
        # 摩擦係数0.5(ただし空間内に他の物体がないため効果なし)
        self.shape.friction = 0.5
        
        # ボディと形状を物理空間に追加
        self.space.add(self.body, self.shape)
        
        # 初期速度:右上方向(vx=250, vy=-200)
        # ※Pymunkは上方向が正のY軸
        self.body.velocity = (250, -200)
    
    def update(self):
        # 物理シミュレーションを1フレーム分進める
        self.space.step(1 / 60.0)
        
        # Rキーでリセット
        if pyxel.btnp(pyxel.KEY_R):
            self.reset()
    
    def draw(self):
        # 画面を黒でクリア
        pyxel.cls(0)
        
        # 円の現在位置を取得
        pos_x, pos_y = self.body.position
        radius = self.shape.radius
        
        # 速度ベクトルを取得
        speed_x, speed_y = self.body.velocity
        # 速度の大きさ(スカラー)を計算
        speed = math.sqrt(speed_x ** 2 + speed_y ** 2)
        
        # 速度ベクトルを正規化(単位ベクトル化)
        if speed != 0:
            norm_x = speed_x / speed
            norm_y = speed_y / speed
        else:
            # ゼロ除算を防ぐ
            norm_x = 0
            norm_y = 0
        
        # 円を描画(緑色)
        pyxel.circ(int(pos_x), int(pos_y), int(radius), COLOR_GREEN)
        
        # 正規化された速度ベクトルを赤い線で表示(円の中心から半径分)
        pyxel.line(
            int(pos_x), int(pos_y),
            int(pos_x + norm_x * radius), int(pos_y + norm_y * radius),
            COLOR_RED
        )
        
        # 速度の大きさを表示
        pyxel.text(5, 5, f"Speed: {speed:.2f}", COLOR_WHITE)
        # 正規化された速度ベクトルを表示
        pyxel.text(5, 15, f"Normalized Velocity: ({norm_x:.2f}, {norm_y:.2f})", COLOR_WHITE)

if __name__ == "__main__":
    CircleSpeedSimulation()
  • 速度の可視化:速度ベクトルの方向を赤い線で表示
  • ベクトルの正規化:速度ベクトルを単位ベクトルに変換(方向のみを表現)
  • 速度の大きさ計算:speed = √(vx² + vy²) でスカラー速度を算出
  • Pymunkの座標系:Y軸上向きが正(velocity = (250, -200)は右上方向)

学習ポイント:

  • ベクトルの大きさ:ピタゴラスの定理による計算
  • 正規化:各成分を大きさで割ることで単位ベクトル化
  • 可視化の重要性:抽象的なベクトル概念を視覚的に理解

3.5 簡単な方向転換:ベクトルの向きを変える

画面640x480、半径20の円。
簡単な方向転換:ベクトルの向きを変える
左右矢印キー入力で速度ベクトルの向きを変える。
三角関数を使ってベクトルの向きを回転させる例。
画面端でループ処理
角度(度)を表示、速度ベクトル表示

import pyxel
import pymunk
import math

COLOR_WHITE = 7
COLOR_RED = 8

class CircleVectorRotate:
    def __init__(self):
        # 画面サイズの設定
        self.width = 640
        self.height = 480
        # Pyxelの初期化(マウスカーソル表示)
        pyxel.init(self.width, self.height, title="Circle Vector Rotate")
        pyxel.mouse(True)
        # 物理シミュレーションの初期化
        self.reset()
        # メインループ開始
        pyxel.run(self.update, self.draw)
    
    def reset(self):
        # Pymunkの物理空間を作成
        self.space = pymunk.Space()
        # 重力なし(等速直線運動)
        self.space.gravity = (0, 0)
        # スリープ閾値の設定
        self.space.sleep_time_threshold = 0.5
        
        # 円の物理ボディを作成
        mass = 1
        radius = 20
        # 円の慣性モーメントを計算
        moment = pymunk.moment_for_circle(mass, 0, radius)
        self.body = pymunk.Body(mass, moment)
        # 初期位置:画面中央
        self.body.position = (self.width / 2, self.height / 2)
        
        # 円の形状を作成
        shape = pymunk.Circle(self.body, radius)
        # 弾性係数0.9(90%反発)
        shape.elasticity = 0.9
        # 摩擦係数0.5
        shape.friction = 0.5
        
        # ボディと形状を物理空間に追加
        self.space.add(self.body, shape)
        self.radius = radius
        
        # 初期角度:0度(右方向)
        self.angle_deg = 0
        # 初期速度:右方向に200px/s
        speed = 200
        self.vx = speed
        self.vy = 0
        self.body.velocity = (self.vx, self.vy)
    
    def update(self):
        # 物理シミュレーションを1フレーム分進める
        self.space.step(1 / 60.0)
        
        # 現在の位置と速度を取得
        pos_x = self.body.position.x
        pos_y = self.body.position.y
        vel_x = self.body.velocity.x
        vel_y = self.body.velocity.y
        
        # Rキーでリセット
        if pyxel.btnp(pyxel.KEY_R):
            self.reset()
            return
        
        # 左矢印キー:反時計回りに10度回転
        if pyxel.btnp(pyxel.KEY_LEFT):
            self.angle_deg = (self.angle_deg + 10) % 360
        # 右矢印キー:時計回りに10度回転
        if pyxel.btnp(pyxel.KEY_RIGHT):
            self.angle_deg = (self.angle_deg - 10) % 360
        
        # 現在の速度の大きさを計算(math.hypot = √(x²+y²))
        speed = math.hypot(vel_x, vel_y)
        # 角度をラジアンに変換
        rad = math.radians(self.angle_deg)
        # 極座標から直交座標への変換(速度ベクトルの回転)
        self.vx = speed * math.cos(rad)
        self.vy = speed * math.sin(rad)
        # 新しい速度を適用
        self.body.velocity = (self.vx, self.vy)
        
        # 画面端のループ処理(トーラス状)
        x = self.body.position.x
        y = self.body.position.y
        
        # X方向の画面端処理
        if x < -self.radius:
            self.body.position = (self.width + self.radius, y)
        elif x > self.width + self.radius:
            self.body.position = (-self.radius, y)
        
        # Y方向の画面端処理
        if y < -self.radius:
            self.body.position = (x, self.height + self.radius)
        elif y > self.height + self.radius:
            self.body.position = (x, -self.radius)
    
    def draw(self):
        # 画面を黒でクリア
        pyxel.cls(0)
        
        # Pymunk座標系(Y上向き)からPyxel座標系(Y下向き)への変換
        x = int(self.body.position.x)
        y = int(self.height - self.body.position.y)
        
        # 円を描画(赤色)
        pyxel.circ(x, y, self.radius, COLOR_RED)
        
        # 速度ベクトルを取得
        vel_x = self.body.velocity.x
        vel_y = self.body.velocity.y
        
        # 速度ベクトルを白い線で表示(0.2倍にスケーリング)
        vx_end_x = int(x + self.vx * 0.2)
        vx_end_y = int(y - self.vy * 0.2)  # Y軸反転
        pyxel.line(x, y, vx_end_x, vx_end_y, COLOR_WHITE)
        
        # 角度と速度の情報を表示
        text_angle = f"Angle: {self.angle_deg} deg"
        text_vel = f"Velocity: ({vel_x:.1f}, {vel_y:.1f})"
        pyxel.text(5, 5, text_angle, COLOR_WHITE)
        pyxel.text(5, 15, text_vel, COLOR_WHITE)

if __name__ == "__main__":
    CircleVectorRotate()
  • 速度ベクトルの回転:矢印キーで速度の方向を10度ずつ回転
  • 極座標変換:角度と速度の大きさから直交座標(vx, vy)を計算
  • 速度の大きさ保持:回転しても速度の大きさ(200px/s)は一定
  • 座標系の変換:Pymunk(Y上向き) ↔ Pyxel(Y下向き)の変換処理

学習ポイント:

  • 極座標と直交座標:vx = speed * cos(θ), vy = speed * sin(θ)
  • math.hypot():√(x²+y²)を計算する関数(速度の大きさ)
  • 剰余演算:(angle + 10) % 360 で角度を0-359度に正規化
  • 座標系の注意:描画時のY軸反転(self.height - y, y - vy * 0.2)

3.6 複数物体の速度管理と更新

画面640x480、半径20の円。
複数のカラフルな円がそれぞれ異なる速度ベクトルで動く。
画面端でループさせる
リストや配列で物体を管理し、各物体の位置・速度を更新。

3-6ai_reset.py
import pyxel

# Pyxelで使用可能な色のリスト(色番号1-15)
COLOR_LIST = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

class MultipleCircles:
    def __init__(self):
        # 画面サイズの設定
        self.width = 640
        self.height = 480
        # 円の半径
        self.radius = 20
        # Pyxelの初期化
        pyxel.init(self.width, self.height, title="Multiple Moving Circles")
        # 円の初期配置
        self.reset()
        # メインループ開始
        pyxel.run(self.update, self.draw)
    
    def reset(self):
        # 円のリストを初期化
        self.circles = []
        # 40個の円を生成
        for i in range(40):
            # ランダムな初期位置(画面内に収まるように)
            x = pyxel.rndi(self.radius, self.width - self.radius)
            y = pyxel.rndi(self.radius, self.height - self.radius)
            # ランダムな速度(-10 ~ 10 px/frame)
            vx = pyxel.rndf(-10, 10)
            vy = pyxel.rndf(-10, 10)
            # 色をリストから順番に割り当て(15色を循環)
            color = COLOR_LIST[i % len(COLOR_LIST)]
            # 円の情報を辞書で保存
            self.circles.append({"x": x, "y": y, "vx": vx, "vy": vy, "color": color})
    
    def update(self):
        # Rキーでリセット
        if pyxel.btnp(pyxel.KEY_R):
            self.reset()
        
        # 全ての円の位置を更新
        for c in self.circles:
            # 位置を速度分だけ更新
            c["x"] += c["vx"]
            c["y"] += c["vy"]
            
            # X方向の画面端処理(トーラス状ループ)
            if c["x"] < -self.radius:
                c["x"] = self.width + self.radius
            elif c["x"] > self.width + self.radius:
                c["x"] = -self.radius
            
            # Y方向の画面端処理(トーラス状ループ)
            if c["y"] < -self.radius:
                c["y"] = self.height + self.radius
            elif c["y"] > self.height + self.radius:
                c["y"] = -self.radius
    
    def draw(self):
        # 画面を黒でクリア
        pyxel.cls(0)
        # 全ての円を描画
        for c in self.circles:
            pyxel.circ(int(c["x"]), int(c["y"]), self.radius, c["color"])

if __name__ == "__main__":
    MultipleCircles()
  • 複数オブジェクトの管理:リストで40個の円を一括管理
  • ランダム生成:pyxel.rndi()(整数)とpyxel.rndf()(浮動小数点)でランダムな初期値
  • 辞書によるデータ構造:各円の情報(x, y, vx, vy, color)を辞書で保存
  • 色の循環割り当て:i % len(COLOR_LIST) で15色を繰り返し使用

前のコードからの進化:

  • 単一オブジェクト → 複数オブジェクト(40個)
  • 固定値 → ランダム生成(位置、速度)
  • 単色 → 多色表示(15色の循環)
  • リストとループによる効率的な管理

学習ポイント:

  • リスト内包表記の代替:forループで辞書をリストに追加
  • 剰余演算の活用:i % len(COLOR_LIST) で循環参照
  • 辞書のキーアクセス:c["x"], c["vx"] などでデータを取得・更新

pyxel-20251128-231211.gif

3.6.2 複数の円の衝突

import pyxel
import pymunk

# Pyxelで使用可能な色のリスト(色番号1-15)
COLOR_LIST = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

class MultipleCirclesCollision:
    def __init__(self):
        # 画面サイズの設定
        self.width = 640
        self.height = 480
        # 円の半径
        self.radius = 20
        # Pyxelの初期化
        pyxel.init(self.width, self.height, title="Multiple Circles with Collision")
        # 物理シミュレーションの初期化
        self.reset()
        # メインループ開始
        pyxel.run(self.update, self.draw)
    
    def reset(self):
        # Pymunkの物理空間を作成
        self.space = pymunk.Space()
        # 重力なし(等速運動を観察)
        self.space.gravity = (0, 0)
        # スリープ閾値の設定
        self.space.sleep_time_threshold = 0.5
        
        # 円のリストを初期化
        self.circles = []
        
        # 40個の円を生成
        for i in range(40):
            # ランダムな初期位置(画面内に収まるように)
            x = pyxel.rndi(self.radius + 10, self.width - self.radius - 10)
            y = pyxel.rndi(self.radius + 10, self.height - self.radius - 10)
            
            # ランダムな速度(-200 ~ 200 px/s)
            vx = pyxel.rndf(-200, 200)
            vy = pyxel.rndf(-200, 200)
            
            # 色をリストから順番に割り当て(15色を循環)
            color = COLOR_LIST[i % len(COLOR_LIST)]
            
            # 円の物理ボディを作成
            mass = 1
            # 円の慣性モーメントを計算
            moment = pymunk.moment_for_circle(mass, 0, self.radius)
            body = pymunk.Body(mass, moment)
            body.position = (x, y)
            body.velocity = (vx, vy)
            
            # 円の形状を作成
            shape = pymunk.Circle(body, self.radius)
            # 弾性係数1.0(完全弾性衝突)
            shape.elasticity = 1.0
            # 摩擦係数0.0(摩擦なし)
            shape.friction = 0.0
            
            # ボディと形状を物理空間に追加
            self.space.add(body, shape)
            
            # 円の情報を保存
            self.circles.append({
                "body": body,
                "shape": shape,
                "color": color
            })
    
    def update(self):
        # 物理シミュレーションを1フレーム分進める
        self.space.step(1 / 60.0)
        
        # Rキーでリセット
        if pyxel.btnp(pyxel.KEY_R):
            self.reset()
            return
        
        # 全ての円に対して画面端処理(トーラス状ループ)
        for c in self.circles:
            x = c["body"].position.x
            y = c["body"].position.y
            vx, vy = c["body"].velocity
            
            # X方向の画面端処理
            if x < -self.radius:
                c["body"].position = (self.width + self.radius, y)
            elif x > self.width + self.radius:
                c["body"].position = (-self.radius, y)
            
            # Y方向の画面端処理
            if y < -self.radius:
                c["body"].position = (x, self.height + self.radius)
            elif y > self.height + self.radius:
                c["body"].position = (x, -self.radius)
    
    def draw(self):
        # 画面を黒でクリア
        pyxel.cls(0)
        
        # 全ての円を描画
        for c in self.circles:
            # Pymunk座標系(Y上向き)からPyxel座標系(Y下向き)への変換
            x = int(c["body"].position.x)
            y = int(self.height - c["body"].position.y)
            pyxel.circ(x, y, self.radius, c["color"])
        
        # 情報表示
        pyxel.text(5, 5, f"Circles: {len(self.circles)}", 7)
        pyxel.text(5, 15, "Press R to reset", 7)

if __name__ == "__main__":
    MultipleCirclesCollision()

1 Pymunkの導入:

  • pymunk.Space() で物理空間を作成
  • 各円に Body と Circle 形状を設定

2 衝突の実装:

  • shape.elasticity = 1.0:完全弾性衝突(エネルギー保存)
  • Pymunkが自動的に円同士の衝突を検出・処理

3 座標系の変換:

  • 描画時に y = int(self.height - c["body"].position.y) でY軸を反転
  • Pymunkは上向きが正、Pyxelは下向きが正

4 データ構造の変更:

  • 辞書に body, shape, color を保存
  • 位置・速度は body オブジェクトが管理

このコードの特徴:

  • 物理エンジンによる衝突:Pymunkが運動量保存則に従って衝突を計算
  • 完全弾性衝突:エネルギー損失なし(円は永遠に動き続ける)
  • 40個の同時衝突処理:複数オブジェクト間の衝突を自動的に処理

pyxel-20251128-231449.gif

■「AIに書かせる」のではなく、「AIに下書きさせて、人間が仕上げる」というスタンスが大切です。
■「わかったつもり」になる(The Illusion of Competence)「自分はすごいものを作れる」と錯覚します。「なぜそのコードで動くのか」を必ず検証しながら、探求していきましょう。
いろいろ、試したくなります。ありがとうございます。

9
3
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
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?