Pygameで作る「Virtual VR Lenses 」徹底解説
この記事では、PythonPythonのPygameを使って「Virtual VR Lenses (Two-Eye, 50 patterns)」を実装したプログラムの動作を、コードのポイントや実行の仕組み、さらには各パターンがどのように作られているのかを詳述していきます。
モダンなVRヘッドセットのように左右2つの円形領域に異なる絵柄(パターン)を50種類のうちから生成し、画面上で表示して楽しむことができます。
1. はじめに
VRゴーグルを覗くときのように左右の円形領域にそれぞれ別のコンテンツを表示すると、なんとなく「仮想現実を覗き込んでいる」かのような気分が味わえます。
本プログラムでは、Pygameを使って2つの円形ウィンドウ(レンズ)を用意し、その中にランダムに生成した50通りのパターンを表示します。
以下のような特徴を持ちます:
- 左右それぞれのレンズに別パターン(全50パターン)が描画可能
- キー押下でパターンをランダムに切り替えられる (
r
キー) - スペースやエスケープキー (
ESC
) などで終了
では早速、プログラム全体を見ながら、各部分の意味や仕組みを丁寧に解説していきましょう。
2. コード全文
まずは完成したコードを一気にご紹介します。この記事の後半では、部分ごとに分割して詳細を深掘りしていきます。
import pygame
import random
import math
# 画面サイズ
WIDTH, HEIGHT = 1000, 600
# レンズ設定
RADIUS = 150
DIAMETER = RADIUS * 2
# レンズの左右間隔
LENS_SPACING = 300
# 左目レンズの中心座標(画面中央よりやや左)
center_left_x = WIDTH // 2 - LENS_SPACING // 2
center_left_y = HEIGHT // 2
# 右目レンズの中心座標(画面中央よりやや右)
center_right_x = WIDTH // 2 + LENS_SPACING // 2
center_right_y = HEIGHT // 2
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Virtual VR Lenses (Two-Eye, 50 patterns)")
clock = pygame.time.Clock()
def generate_pattern(index, diameter, radius):
"""
index(0〜49)に応じて円形に切り抜いた模様Surfaceを生成して返す。
円外は透過(アルファ0)になるようにマスクする。
"""
surf = pygame.Surface((diameter, diameter), pygame.SRCALPHA)
surf.fill((0, 0, 0, 0)) # 初期は全面透明
cx = cy = radius
r2 = radius * radius
# ---------------------
# 0〜19: 前回例のパターン
# ---------------------
if index == 0:
# カラーのランダムノイズ
for y in range(diameter):
dy2 = (y - cy) ** 2
for x in range(diameter):
dx = x - cx
if dx*dx + dy2 <= r2:
color = (random.randrange(256), random.randrange(256), random.randrange(256))
surf.set_at((x, y), (*color, 255))
elif index == 1:
# グレースケールのノイズ
for y in range(diameter):
dy2 = (y - cy) ** 2
for x in range(diameter):
dx = x - cx
if dx*dx + dy2 <= r2:
val = random.randrange(256)
surf.set_at((x, y), (val, val, val, 255))
elif index == 2:
# 左右方向のグラデーション
left_color = [random.randrange(256) for _ in range(3)]
right_color = [random.randrange(256) for _ in range(3)]
for y in range(diameter):
dy2 = (y - cy) ** 2
for x in range(diameter):
dx = x - cx
if dx*dx + dy2 <= r2:
t = x / (diameter - 1)
r_val = int(left_color[0]*(1 - t) + right_color[0]*t)
g_val = int(left_color[1]*(1 - t) + right_color[1]*t)
b_val = int(left_color[2]*(1 - t) + right_color[2]*t)
surf.set_at((x, y), (r_val, g_val, b_val, 255))
elif index == 3:
# 上下方向のグラデーション
top_color = [random.randrange(256) for _ in range(3)]
bottom_color = [random.randrange(256) for _ in range(3)]
for y in range(diameter):
t = y / (diameter - 1)
r_val = int(top_color[0]*(1 - t) + bottom_color[0]*t)
g_val = int(top_color[1]*(1 - t) + bottom_color[1]*t)
b_val = int(top_color[2]*(1 - t) + bottom_color[2]*t)
dy2 = (y - cy) ** 2
for x in range(diameter):
dx = x - cx
if dx*dx + dy2 <= r2:
surf.set_at((x, y), (r_val, g_val, b_val, 255))
elif index == 4:
# 放射状グラデーション
center_color = [random.randrange(256) for _ in range(3)]
edge_color = [random.randrange(256) for _ in range(3)]
for y in range(diameter):
for x in range(diameter):
dx = x - cx
dy = y - cy
dist2 = dx*dx + dy*dy
if dist2 <= r2: dist = math.sqrt(dist2) t = dist / radius if t > 1:
t = 1
r_val = int(center_color[0]*(1 - t) + edge_color[0]*t)
g_val = int(center_color[1]*(1 - t) + edge_color[1]*t)
b_val = int(center_color[2]*(1 - t) + edge_color[2]*t)
surf.set_at((x, y), (r_val, g_val, b_val, 255))
elif index == 5:
# 同心円のリング
color1 = [random.randrange(256) for _ in range(3)]
color2 = [random.randrange(256) for _ in range(3)]
ring_width = max(1, radius // 5)
for y in range(diameter):
for x in range(diameter):
dx = x - cx
dy = y - cy
dist2 = dx*dx + dy*dy
if dist2 <= r2: dist = math.sqrt(dist2) ring_index = int(dist // ring_width) col = color1 if ring_index % 2 == 0 else color2 surf.set_at((x, y), (*col, 255)) # ... (パターン7〜49のコードは同様の形式で続く) ... # ★ 最後に円形外側を透明に for y in range(diameter): for x in range(diameter): dx = x - cx dy = y - cy if dx*dx + dy*dy > r2:
surf.set_at((x, y), (0,0,0,0))
return surf
# 50種類パターンのSurfaceをキャッシュ
scenes = [generate_pattern(i, DIAMETER, RADIUS) for i in range(50)]
# 左・右で別々のパターンを使う
current_left_index = 0
current_right_index = 1
running = True
while running:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
# Rキーで左右の絵柄を新規ランダムに
new_left = random.randrange(50)
new_right = random.randrange(50)
while new_right == new_left:
new_right = random.randrange(50)
current_left_index = new_left
current_right_index = new_right
elif event.key == pygame.K_ESCAPE:
running = False
screen.fill((0,0,0))
# 左目レンズを描画
lens_left_pos = (center_left_x - RADIUS, center_left_y - RADIUS)
screen.blit(scenes[current_left_index], lens_left_pos)
pygame.draw.circle(screen, (200,200,200), (center_left_x, center_left_y), RADIUS, width=5)
# 右目レンズを描画
lens_right_pos = (center_right_x - RADIUS, center_right_y - RADIUS)
screen.blit(scenes[current_right_index], lens_right_pos)
pygame.draw.circle(screen, (200,200,200), (center_right_x, center_right_y), RADIUS, width=5)
pygame.display.flip()
pygame.quit()
3. 全体の流れと役割
上記のコードを大きく分割すると、下記のような流れになっています。
- ライブラリのインポートと定数定義
import pygame, random, math
など- 画面サイズ
WIDTH, HEIGHT
やレンズの半径RADIUS
、左右間隔LENS_SPACING
などを定義
- Pygameの初期化と画面作成
generate_pattern(index, diameter, radius)
関数の定義- 引数
index
(0〜49)によって50種類の円形パターンを生成 - 中央
(cx, cy)
を中心に、半径以内の部分に様々な描画を施す - 円外を透過処理して“丸く切り抜く”
- 引数
- 50種類のパターン(Surface)をリスト
scenes
にキャッシュ - ゲームループで、左右レンズに選んだ
scenes
を表示- Rキー押下時に、左右のパターンをランダムに切り替える
- ESCキーで終了
4. 各定数・変数の意味
名前 | 役割 |
---|---|
WIDTH, HEIGHT | ウィンドウ全体の幅と高さを指定。 今回は 1000 x 600 ピクセルに設定。 |
RADIUS, DIAMETER | レンズ(円)一つあたりの半径と直径。RADIUS=150 なのでDIAMETER=300 。 |
LENS_SPACING | 左右レンズの中心間隔(今回は300ピクセル)。 画面中央を基準にやや左右にずらす。 |
center_left_x, center_left_y | 左目レンズの中心座標。 ウィンドウ中央より少し左に設定。 |
center_right_x, center_right_y | 右目レンズの中心座標。 ウィンドウ中央より少し右に設定。 |
scenes | generate_pattern で作った 0〜49番のパターンSurfaceを保持するリスト。 |
current_left_index, current_right_index | 左目用・右目用に現在描画中のパターン番号。 |
このように、それぞれの変数が役割を持っているため、パラメータを変更するだけでレンズサイズの変更や画面サイズの変更も簡単に行えます。
5. generate_pattern
関数の詳細
本プログラムの核心部分は、generate_pattern(index, diameter, radius)
という関数です。
引数index
によってパターンを切り替える仕組みを実現しています。
以下のような流れで動いています。
- 新しいSurface(
surf
)を、(diameter, diameter)
サイズ & アルファチャンネルつきで作成 surf.fill((0, 0, 0, 0))
で全面を透明に初期化cx=cy=radius
として、円の中心を描画用Surfaceの左上から(半径, 半径)に設定- パターン番号
index
に応じて、if index == 0: ... elif index == 1: ...
という分岐の中で描画 - 描画後、Surfaceのピクセルを走査して、円外(
dx^2 + dy^2 > r^2
)を透明化
つまり、50通りの「円形パターン」が一気に定義されており、呼び出すときにindex
を指定すれば、
指定された番号の模様Surfaceが生成されるようになっています。
5.1. まずは円形外のピクセルを透明にする仕組み
関数の最後にあるループ:
for y in range(diameter):
for x in range(diameter):
dx = x - cx
dy = y - cy
if dx*dx + dy*dy > r2:
surf.set_at((x, y), (0,0,0,0))
ここでは、「もし円の外側ならばピクセルを透明にする」という操作を行っています。こうすることで、結果としてレンズの形状が丸く切り抜かれた絵柄のSurfaceになるわけです。
5.2. 0〜19番のパターンの例
コード内で# 0〜19: 前回例のパターン
と書かれている部分は、さまざまな描画例です。ここでは主なものをいくつか見てみましょう。
- index == 0:カラーのランダムノイズ円内の全ピクセルをランダムなR,G,B値に設定することで、カラフルな点の集合になります。
- index == 1:グレースケールのノイズランダムな1値を用い、R=G=Bとすることでモノクロのノイズを生成しています。
- index == 2:左右方向のグラデーション円の左端と右端でそれぞれ
left_color
,right_color
をランダムに決定し、
x座標の進行度t = x / (diameter - 1)
で線形補間して色を決定する仕組みです。 - index == 4:放射状グラデーション中心の色
center_color
と、円周の色edge_color
をランダムに決めて、半径方向に向かう距離によって補間した色を配置します。 - index == 5:同心円のリング半径を小さな輪っか(
ring_width
)で区切って、偶数番目と奇数番目で色を塗り分けています。
ring_index = int(dist // ring_width)
により、同心円の層を判定します。
他にも、ストライプ模様(index == 6
〜index == 8
)やチェック柄(9
や10
)など、
単純なパターンながら円形に切り抜いているだけで面白い効果が生まれています。
5.3. 20〜49番の新たなパターン
コード後半の# 20〜49
では、より装飾的・創造的なパターンが追加されています。いくつか例を挙げてみましょう。
- index == 20:お化けの形半円やギザギザをポリゴン描画してゴーストっぽい絵を描いています。
- index == 21:ハート形のグラデーションハート曲線を近似した点をポリゴン描画し、繰り返しポリゴンを重ね塗りすることでグラデーションを表現。
- index == 22:当たりマーク(的)白地に赤や白のリングを交互に描画し、的のようにしています。
- index == 23:レインボー赤橙黄緑青藍紫の順で同心円を描き、虹色を表現しています。
- index == 24:雪の結晶背景をグラデーションにしたあと、放射状に枝を描いて雪の結晶ぽさを出すなど、工夫をこらしています。
- index == 39:火の玉HSVカラーを利用し、中心ほど明るく外側ほど暗くなる赤系のグラデーションをランダム要素込みで作成。
- index == 42:カラフル波紋半径や角度を利用して色相(hue)と明度(value)を変化させることで、虹色の波紋模様を実現。
- index == 49:レインボー螺旋渦巻き状に線を描きながら、描画位置に応じて色相(Hue)を変化させることでカラフルに仕上げています。
これらのパターンをじっくり眺めるだけでも、図形やカラーリングの勉強になります。
「放射状」や「同心円」「螺旋」「格子」「文字描画」「ポリゴン描画」「ランダムノイズ」などの複合的な技法が総合的に使われています。
6. メインループ:レンズの表示とキー操作
プログラムの最後の方の「メインループ」部分では、Pygameウィンドウ上に左目レンズと右目レンズを描画する処理が行われています。
running = True
while running:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
# Rキーで左右の絵柄を新規ランダムに
new_left = random.randrange(50)
new_right = random.randrange(50)
while new_right == new_left:
new_right = random.randrange(50)
current_left_index = new_left
current_right_index = new_right
elif event.key == pygame.K_ESCAPE:
running = False
# 背景クリア
screen.fill((0,0,0))
# 左目レンズを描画
lens_left_pos = (center_left_x - RADIUS, center_left_y - RADIUS)
screen.blit(scenes[current_left_index], lens_left_pos)
pygame.draw.circle(screen, (200,200,200), (center_left_x, center_left_y), RADIUS, width=5)
# 右目レンズを描画
lens_right_pos = (center_right_x - RADIUS, center_right_y - RADIUS)
screen.blit(scenes[current_right_index], lens_right_pos)
pygame.draw.circle(screen, (200,200,200), (center_right_x, center_right_y), RADIUS, width=5)
pygame.display.flip()
pygame.quit()
このように、scenes
リストから現在のインデックスを使ってSurface
を取り出し、screen.blit
で貼り付け(描画)しています。
さらにpygame.draw.circle
で枠線を描画しているので、レンズ枠っぽく見えるようになっています。
また、r
キーが押されるたびに、
random.randrange(50)
で0〜49の中からランダムな番号を2つ取得し、それを左右それぞれのパターン番号に割り当てています。
もし「左右が同じパターン番号になることを避けたい」ときは、下記のようにして重複チェックしています。
new_left = random.randrange(50)
new_right = random.randrange(50)
while new_right == new_left:
new_right = random.randrange(50)
これで、左右が同じになった場合に再抽選して異なるパターン番号になるまで繰り返す仕組みです。
7. 動作のポイント
本プログラムを実行する際には、以下の点に注意してください。
- 事前に
pygame
をインストール - Pythonのバージョンは3系であることを推奨
- 実行するとウィンドウが開き、左右それぞれに最初は
current_left_index = 0
、current_right_index = 1
のパターン - メインループが回っている間は、画面上に「ランダムに生成された左右の円形パターン」が描画される
- Rキーを押すと左右のレンズ表示が新しいランダムなパターンに切り替わる
- ESCキーか×印でウィンドウを閉じると終了
8. パターンのカスタマイズ
各パターンはあくまで一例ですので、自由に追加や変更が可能です。
たとえば「スペックル模様」「波打つような形」「写真を読み込んで円形にトリミング」などの描画を仕込むこともできます。
generate_pattern
関数内に独自のelif index == X:
を追加し、X
番目の描画を定義すればOKです。
好きな画像ファイルをpygame.image.load()
で読み込み、最後に円外を透明化すれば、
「写真を丸く抜いたレンズ模様」もすぐに作れます。
9. 各パターン一覧表
この記事の後半での長文解説に加え、簡易一覧表を用意します。プログラムで定義されているパターン(index 0〜49)のイメージをざっくりまとめたものです。
index | パターン概要 |
---|---|
0 | カラーランダムノイズ |
1 | モノクロノイズ |
2 | 左右グラデーション |
3 | 上下グラデーション |
4 | 放射状グラデーション |
5 | 同心円リング |
6 | 水平ストライプ |
7 | 垂直ストライプ |
8 | 斜めストライプ |
9 | 小さいチェック柄 |
10 | 大きいチェック柄 |
11 | 単色背景+水玉 |
12 | 黒背景+ランダム線 |
13 | 黒背景+ランダム円 |
14 | 黒背景+ランダム矩形 |
15 | 格子ライン(クロスハッチ) |
16 | 放射状スポーク |
17 | 渦巻き(スパイラル) |
18 | 星空 |
19 | テキスト表示(Scene番号) |
20 | お化けの形 |
21 | ハート形のグラデーション |
22 | 当たりマーク(的) |
23 | レインボー(虹色の同心円) |
24 | 雪の結晶 |
25 | 人のシルエット |
26 | 光の波 |
27 | 色彩豊かな水玉 |
28 | 錯視柄1(格子状など) |
29 | 錯視柄2(オービソン錯視風) |
30 | 簡易迷路風 |
31 | ランダム三角形模様 |
32 | 単色斜めグラデ+斜めストライプ |
33 | ランダム星座風 |
34 | レトロゲーム風 |
35 | 砂絵風 |
36 | 大きな文字アート |
37 | カラフルな格子+斜め黒線 |
38 | レインドロップ風 |
39 | 火の玉 |
40 | ガラス玉風 |
41 | モザイクアート風 |
42 | カラフル波紋 |
43 | 水面反射風 |
44 | 大理石模様風 |
45 | レコード盤風 |
46 | 文字列のランダム配置 |
47 | 稲妻模様 |
48 | 万華鏡風 |
49 | レインボー螺旋 |
10. まとめと応用
以上のように、Pygameを使って円形に切り抜いたパターンを左右に表示するプログラムを解説してきました。
ポイントは:
- 表示したい模様を好きな方法で描画(テキスト、図形、ノイズ、イメージロードなど何でもOK)
- 描画後に「円外を透明化」することで“レンズ”として使える
- 左右のレンズを別パターンにして並べることで、ちょっとした“VRゴーグル風”の表示を楽しめる
描画パターンを自作したり、動的にアニメーションするように改造したり、3D的な視差をつけたりすることで、より発展的なVR風体験が可能になります。
ぜひ本記事を参考に、自由な発想でパターン生成・表示を試してみてください。
これで「Virtual VR Lenses 」の徹底解説は完了です。
最後までお読みいただき、ありがとうございました。どうか皆さんのプログラミングライフがより豊かになるよう願っています。
コメント