mieki256's diary



2026/07/07(火) [n年前の日記]

#1 [prog] スプライトのzソートについて調べてる。その4

スプライトの描画順をz値に基づいて決定したい。

AI (Google Gemini) に、Python + pygame でオーダリングテーブル(ot, Ordering table)の処理をするサンプルを作ってもらった。

サンプル1 :

矩形で塗り潰すだけの複数のオブジェクトを生成して、z値に応じてオーダリングテーブルで描画順を決めるサンプルスクリプト。

_01_ot_sample1_main.py
"""
Ordering Table のサンプル(pygbag対応 + マウスクリック + Z値連動色変化版)

Windows11 x64 25H2 + Python 3.10.10 64bit + pygame-ce 2.5.7
"""

import asyncio
import random
import sys
import pygame

# 画面サイズと設定
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
FPS = 60

# オーダリングテーブルの最大サイズ(Z値の範囲: 0 〜 99)
MAX_Z_LAYERS = 100


class CustomSprite:
    """独自のZ値を持つゲームオブジェクト"""

    def __init__(self, x, y, width, height, z):
        self.rect = pygame.Rect(x, y, width, height)
        self.z = z  # 浮動小数点数などの任意の数値

        # --- Z値(0.0?99.9)に応じて色を計算 ---
        # 0.0で128、99.9で約32になるように補間
        ratio = z / 100.0
        color_val = int(192 - (160 * ratio))
        self.color = (color_val, color_val, color_val)

    def draw(self, surface, font):
        # 描画処理(わかりやすいようにZ値もテキストで表示)
        pygame.draw.rect(surface, self.color, self.rect, 0)

        # 枠線描画
        pygame.draw.rect(surface, pygame.Color(255, 255, 255, 255), self.rect, 2)

        text = font.render(f"Z: {self.z:.1f}", True, (255, 255, 255))
        surface.blit(text, (self.rect.x + 5, self.rect.y + 5))


async def main():
    pygame.init()
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    pygame.display.set_caption("Ordering Table Draw System")
    clock = pygame.time.Clock()

    font = pygame.font.Font(None, 24)

    initfg = True

    # メインループ
    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    running = False
                elif event.key == pygame.K_q:
                    running = False
                elif event.key == pygame.K_r:
                    initfg = True
            elif event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 1:
                    initfg = True

        if initfg:
            # ----------------------------------------
            # 1. オブジェクトの生成
            # ----------------------------------------
            sprites = []
            for _ in range(15):
                x = random.randint(100, 600)
                y = random.randint(100, 400)
                z = random.uniform(0.0, 99.9)
                # colorの指定を無くし、内部でZ値から自動計算するように変更
                sprites.append(CustomSprite(x, y, 100, 100, z))

            initfg = False

        # 画面クリア
        screen.fill((0, 0, 0))

        # -------------------------------------------------------------
        # 2. オーダリングテーブルの初期化
        # -------------------------------------------------------------
        ordering_table = [[] for _ in range(MAX_Z_LAYERS)]

        # -------------------------------------------------------------
        # 3. オブジェクトを整数値に変換したZ値のレイヤーに登録
        # -------------------------------------------------------------
        for sprite in sprites:
            z_index = int(sprite.z)
            z_index = max(0, min(z_index, MAX_Z_LAYERS - 1))
            ordering_table[z_index].append(sprite)

        # -------------------------------------------------------------
        # 4. オーダリングテーブルに基づいて奥から一気に描画
        # -------------------------------------------------------------
        for z_layer in reversed(ordering_table):
            for sprite in z_layer:
                sprite.draw(screen, font)

        pygame.display.flip()
        clock.tick(FPS)

        await asyncio.sleep(0)

    pygame.quit()
    sys.exit()


if __name__ == "__main__":
    asyncio.run(main())

pygbag を使って、Webブラウザ上でも実行できるようにした。マウスクリック、もしくはRキーで、オブジェクトを生成し直す。

_01_ot_sample1

z値の大小で描画順が決定されてることが分かる。

サンプル2 :

先日作成した、 _スプライトが球状に描画されるスクリプト を、オーダリングテーブルで処理するようにしてみた。

_image.png
_02_ot_sample2_main.py
"""
スプライトで球を表示するサンプル(オーダリングテーブル版・半径可変モデル)。
Google Gemini に作ってもらった。

Windows11 x64 25H2 + Python 3.10.10 64bit
"""

import pygame
import sys
import math
import asyncio

# 初期設定
pygame.init()
WIDTH, HEIGHT = 1280, 720
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("3D Point Cloud Sphere with Sprites (Ordering Table)")
clock = pygame.time.Clock()

# --- 球体上の点群(3D座標)を生成 ---
NUM_POINTS = 256
BASE_RADIUS = 300  # 基準となる半径
PULSE_AMPLITUDE = 200  # 半径の振り幅

# 最初は半径 1.0 の単位球として座標を保持しておくと、後から半径を掛け算しやすくなります
base_points = []
phi = math.pi * (math.sqrt(5.0) - 1.0)

for i in range(NUM_POINTS):
    y = 1.0 - (i / float(NUM_POINTS - 1)) * 2.0
    radius_at_y = math.sqrt(1.0 - y * y)
    theta = phi * i
    x = math.cos(theta) * radius_at_y
    z = math.sin(theta) * radius_at_y
    # 単位球の座標として格納
    base_points.append([x, y, z])

# --- オーダリングテーブルの設定 ---
TABLE_SIZE = 200  # 解像度(バケツの数)


async def main():
    # 画像の読み込み
    try:
        sprite_image = pygame.image.load("./image.png").convert_alpha()
    except Exception:
        print("image.png が見つからないため、仮のスプライトを作成します。")
        sprite_image = pygame.Surface((32, 32), pygame.SRCALPHA)
        pygame.draw.circle(sprite_image, (255, 0, 0), (16, 16), 16)
        pygame.draw.circle(sprite_image, (255, 255, 255), (12, 12), 4)

    SPRITE_W, SPRITE_H = sprite_image.get_size()
    angle_y = 0
    angle_x = 0
    angle_radius = 0  # 半径変化用のラジアン値

    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    running = False
                elif event.key == pygame.K_q:
                    running = False

        # 画面のクリア
        screen.fill((15, 15, 15))

        # アニメーション速度
        angle_y += 0.015
        angle_x += 0.001
        angle_radius += 0.01  # 半径が変化するスピード(好みに応じて調整)

        # --- 現在のフレームの半径を計算 ---
        # BASE_RADIUS を中心に、±PULSE_AMPLITUDE の範囲で波打つ
        current_radius = BASE_RADIUS + math.sin(angle_radius) * PULSE_AMPLITUDE

        # --- オーダリングテーブルの初期化 ---
        ordering_table = [[] for _ in range(TABLE_SIZE)]

        # 3次元回転と座標変換、およびテーブルへの登録
        for pt in base_points:
            # 単位球の座標に、現在のフレームの半径を掛け合わせる
            x = pt[0] * current_radius
            y = pt[1] * current_radius
            z = pt[2] * current_radius

            # Y軸周りの回転
            cos_y, sin_y = math.cos(angle_y), math.sin(angle_y)
            x1 = x * cos_y - z * sin_y
            z1 = x * sin_y + z * cos_y

            # X軸周りの回転
            cos_x, sin_x = math.cos(angle_x), math.sin(angle_x)
            y2 = y * cos_x - z1 * sin_x
            z2 = y * sin_x + z1 * cos_x

            # --- Z値をテーブルのインデックスに変換 ---
            # z2 はおおよそ -current_radius から +current_radius の範囲。
            norm_z = (z2 + current_radius) / (2 * current_radius)

            # 念のため範囲外をクリップ
            table_idx = int(norm_z * (TABLE_SIZE - 1))
            table_idx = max(0, min(TABLE_SIZE - 1, table_idx))

            # 画面座標の計算
            screen_x = int(x1 + (WIDTH // 2) - (SPRITE_W // 2))
            screen_y = int(y2 + (HEIGHT // 2) - (SPRITE_H // 2))

            # テーブルの該当する深度のバケツに追加
            ordering_table[table_idx].append((screen_x, screen_y))

        # --- 描画処理 (奥から手前へ) ---
        for bucket in ordering_table:
            for screen_x, screen_y in bucket:
                screen.blit(sprite_image, (screen_x, screen_y))

        # 画面更新
        pygame.display.flip()

        clock.tick(60)
        await asyncio.sleep(0)

    pygame.quit()
    sys.exit()


if __name__ == "__main__":
    asyncio.run(main())

これも pygbag でWebブラウザ上でも動かせるようにしてみた。

_02_ot_sample2

只のソート処理でも良さそう :

ここまで試しておいてアレだけど…。実は只のソート処理でも良さそうな気配があって…。

先日、 _C言語で作ったzソート処理をするサンプル では、4096個のスプライト相当をz値でクイックソートしても、CPU : Ryzen 5 5600X の環境なら 0.0005 - 0.0006 秒しかかからないようで…。60FPSなら1フレームの時間が 1/60 秒 = 0.0166667秒 だけど、1/27 とか 1/33 程度の処理時間でソート処理が終わってしまう。

2Dゲームを今時のハードウェア上で動かす分には、クイックソートで処理しちゃっても十分なのかもしれない…。非力なCPU上で動かすとか、もっと膨大なスプライト枚数を管理するなら話は違うのかもしれないけれど…。

以上です。

過去ログ表示

Prev - 2026/07 -
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project