2026/07/07(火) [n年前の日記]
#1 [prog] スプライトのzソートについて調べてる。その4
スプライトの描画順をz値に基づいて決定したい。
AI (Google Gemini) に、Python + pygame でオーダリングテーブル(ot, Ordering table)の処理をするサンプルを作ってもらった。
AI (Google Gemini) に、Python + pygame でオーダリングテーブル(ot, Ordering table)の処理をするサンプルを作ってもらった。
- オーダリングテーブルを使うと、ソート処理が不要になる。
- その代わり、テーブル分のメモリを余計に使う。
- 整数値で扱うから、誤差(?)が大きくなる。
◎ サンプル1 :
矩形で塗り潰すだけの複数のオブジェクトを生成して、z値に応じてオーダリングテーブルで描画順を決めるサンプルスクリプト。
_01_ot_sample1_main.py
pygbag を使って、Webブラウザ上でも実行できるようにした。マウスクリック、もしくはRキーで、オブジェクトを生成し直す。
_01_ot_sample1
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
これも pygbag でWebブラウザ上でも動かせるようにしてみた。
_02_ot_sample2
_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上で動かすとか、もっと膨大なスプライト枚数を管理するなら話は違うのかもしれないけれど…。
先日、 _C言語で作ったzソート処理をするサンプル では、4096個のスプライト相当をz値でクイックソートしても、CPU : Ryzen 5 5600X の環境なら 0.0005 - 0.0006 秒しかかからないようで…。60FPSなら1フレームの時間が 1/60 秒 = 0.0166667秒 だけど、1/27 とか 1/33 程度の処理時間でソート処理が終わってしまう。
2Dゲームを今時のハードウェア上で動かす分には、クイックソートで処理しちゃっても十分なのかもしれない…。非力なCPU上で動かすとか、もっと膨大なスプライト枚数を管理するなら話は違うのかもしれないけれど…。
[ ツッコむ ]
#2 [rust] RustをWindows11にインストールしておいた
プログラミング言語の Rust を Windows11 x64 25H2 にインストールしておいた。今回は Rust 1.96.1 がインストールされた。
一旦 rustup self uninstall でアンインストールしてから、rustup-init.exe を実行してインストールした。
作業手順については以前メモしてあった。
_mieki256's diary - Rustで使えるGUIライブラリを調べてる
_mieki256's diary - RustをWindows10にインストール
_mieki256's diary - RustをWindows10にインストール
利用するには C++コンパイラが必要らしいけど、Visual Studio Community 2022 をインストールしてある環境だからそのあたりはクリアしているだろう…。たぶん。
インストール場所を変更したいので、環境変数を指定しておく。事前に環境変数を指定しておくと、その場所にインストールしてくれるらしい。
入手した rustup-init.exe 64bit版を実行すると以下のメッセージが表示された。
1を選択してインストール処理を進める。色々なファイルがダウンロードされてインストールされていく。
rustc 1.96.1 がインストールされた、と表示された。
Enterを叩けばコンソールが終了して、環境変数PATHに cargo関連ツールのパスが追加される。ユーザ側の環境変数 PATH に追加された模様。追加というか、PATH の一番最初に挿入されてるけれど…。
インストール場所やバージョンを確認。
一旦 rustup self uninstall でアンインストールしてから、rustup-init.exe を実行してインストールした。
作業手順については以前メモしてあった。
_mieki256's diary - Rustで使えるGUIライブラリを調べてる
_mieki256's diary - RustをWindows10にインストール
_mieki256's diary - RustをWindows10にインストール
利用するには C++コンパイラが必要らしいけど、Visual Studio Community 2022 をインストールしてある環境だからそのあたりはクリアしているだろう…。たぶん。
インストール場所を変更したいので、環境変数を指定しておく。事前に環境変数を指定しておくと、その場所にインストールしてくれるらしい。
- CARGO_HOME : cargo のインストール場所のパスを指定。今回は D:\Rust\.cargo にした。
- RUSTUP_HOME : rustupのインストール場所のパスを指定。今回は D:\Rust\.rustup にした。
入手した rustup-init.exe 64bit版を実行すると以下のメッセージが表示された。
warn: It looks like you have an existing rustup settings file at:
warn: D:\Rust\.rustup\settings.toml
warn: Rustup will install the default toolchain as specified in the settings file,
warn: instead of the one inferred from the default host triple.
Welcome to Rust!
This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.
Rustup metadata and toolchains will be installed into the Rustup
home directory, located at:
D:\Rust\.rustup
This can be modified with the RUSTUP_HOME environment variable.
The Cargo home directory is located at:
D:\Rust\.cargo
This can be modified with the CARGO_HOME environment variable.
The cargo, rustc, rustup and other commands will be added to
Cargo's bin directory, located at:
D:\Rust\.cargo\bin
This path will then be added to your PATH environment variable by
modifying the PATH registry key at HKEY_CURRENT_USER\Environment.
You can uninstall at any time with rustup self uninstall and
these changes will be reverted.
urrent installation options:
default host triple: x86_64-pc-windows-msvc
default toolchain: stable (default)
profile: default
modify PATH variable: yes
1) Proceed with standard installation (default - just press enter)
2) Customize installation
3) Cancel installation
>
1を選択してインストール処理を進める。色々なファイルがダウンロードされてインストールされていく。
rustc 1.96.1 がインストールされた、と表示された。
Rust is installed now. Great! To get started you may need to restart your current shell. This would reload its PATH environment variable to include Cargo's bin directory (D:\Rust\.cargo\bin). Press the Enter key to continue.
Enterを叩けばコンソールが終了して、環境変数PATHに cargo関連ツールのパスが追加される。ユーザ側の環境変数 PATH に追加された模様。追加というか、PATH の一番最初に挿入されてるけれど…。
インストール場所やバージョンを確認。
> where cargo D:\Rust\.cargo\bin\cargo.exe > where rustup D:\Rust\.cargo\bin\rustup.exe > cargo --version cargo 1.96.1 (356927216 2026-06-26) > rustup --version rustup 1.29.0 (28d1352db 2026-03-05) info: This is the version for the rustup toolchain manager, not the rustc compiler. info: the currently active `rustc` version is `rustc 1.96.1 (31fca3adb 2026-06-26)`
[ ツッコむ ]
以上、1 日分です。