本記事について
Pythonのレトロゲームエンジン"pyxel"を使ったブロッコリーのジェネラティブアートについてまとめたもの。
2023.07.08開催のJackasson Fes 2023 in Summerにて発表したもの。
Jackassonとは?
どんなにくだらないJackass!なものでも自分の表現したいものを作る!発表する!をコンセプトに 自身の発想の実現と技術への挑戦するためのLT大会。
当日はG's ACADEMYの卒業生であるVJ石井さんが登場するなど、大変盛り上がりました!
誰のための記事?
- pyxcelに興味がある人
- pythonを使ってアートを作ってみたい人
- ブロッコリーが好きな自分のため
Pyxcelについて
Pythonのレトロゲームエンジン。ドット絵スタイルのゲームが作れる。
画面サイズ 256 x 256 ドット、使える色は16色。
(参考:https://github.com/kitao/pyxel/blob/main/docs/README.ja.md)
Pyxcelを使用した背景
ドットの散らばりがブロッコリーの花蕾に似ていたため。
単純な2次元のイラストではなく、座標を使ってブロッコリーを表現しようと感得たため。
作ったもの
不完全なブロッコリーっぽいものが出来上がった。
形状はこれから精度を上げてブロッコリーに近づけていく。
手順
1.pyxelライブラリから必要な機能をインポート。
mathモジュールからsin、cos、sqrt、piの関数をインポートしてrandomモジュールも使用する。
from pyxel import *
from math import sin, cos, sqrt, pi
import random
2.画面のサイズや背景色、点の色などの設定。
w = 256 部分で画面のサイズの設定を行う。wは画面の幅と高さ、background_colorは背景の色、dot_colorは描画する点の色を示す。ブロッコリーを表現したいので11の緑を使用。
dot_countは描画する点の数、p_minとp_maxは座標の範囲。
init(w, w, fps=1)はpyxelを初期化するための関数で、引数に画面の幅と高さ、フレームレートを指定。
w = 256
background_color = 0
dot_color = 11
dot_count = 20000
p_min, p_max = -3, 3
init(w, w, fps=1)
3.描画関数を作成
cls(background_color)によって背景を指定。
xとyは描画する点の座標を表す変数で、最初は0で初期化。
aからdまでの4つの変数に、p_minからp_maxの範囲でランダムな値を代入し、text()関数を使ってランダムな値を表示。
def draw():
cls(background_color)
x, y = 0, 0
a, b, c, d = [round(random.uniform(p_min, p_max), 2) for _ in range(4)]
text(10, 10, "a="+str(a)+" b="+str(b)+" c="+str(c)+" d="+str(d), 7)
4.ブロッコリーの形状を描画
その後のforループでは、dot_count回の繰り返しで点を描画し、点の座標でsinとcosを使用し楕円の方程式を使って座標を変換。
ランダムに角度を選択し、最後に、座標にオフセットを適用して点を描画。
Peter de Jong Attractor という、ランダム生成したa,b,c,d の値と下記の式を用いて色々な形を形成する。
Xn+1 = sin(a * Yn) - cos(b * Xn)
Yn+1 = sin(c * Xn) - cos(d * Yn)
引用:http://paulbourke.net/fractals/peterdejong/
for _ in range(dot_count):
x, y = sin(a*y) - cos(b*x), sin(c*x) - cos(d*y)
r = sqrt(x**2 + y**2) * 20 # 点の距離に基づいて半径を設定(大きめの値)
# 楕円の方程式に基づいて座標を変換
# ex = x * 0.8 # 楕円のx方向の拡大率
ex = x * 0.8
ey = y # 楕円のy方向の拡大率
theta = random.uniform(0, 2 * pi) # ランダムな角度を選択
px = ex * w / 5 + 128 + r * cos(theta) # 円の中心からのオフセットを適用
py = ey * w / 5 + 140 + r * sin(theta) + 60 # 上方向にオフセット
pset(int(px), int(py), dot_color)
5.プログラムの実行とイベントループ
run()関数がpyxelのイベントループを開始し、更新関数と描画関数を繰り返し実行する。
run(update, draw)
6.コード全体
from pyxel import *
from math import sin, cos, sqrt, pi
import random
w = 256
background_color = 0
dot_color = 11
dot_count = 20000
p_min, p_max = -3, 3
init(w, w, fps=1)
def draw():
cls(background_color)
x, y = 0, 0
a, b, c, d = [round(random.uniform(p_min, p_max), 2) for _ in range(4)]
text(10, 10, "a="+str(a)+" b="+str(b)+" c="+str(c)+" d="+str(d), 7)
for _ in range(dot_count):
x, y = sin(a*y) - cos(b*x), sin(c*x) - cos(d*y)
r = sqrt(x**2 + y**2) * 20
ex = x * 0.8
ey = y
theta = random.uniform(0, 2 * pi)
px = ex * w / 5 + 128 + r * cos(theta)
py = ey * w / 5 + 140 + r * sin(theta) + 60
pset(int(px), int(py), dot_color)
def update():
pass
run(update, draw)
苦戦したところ
ブロッコリーらしさを追求するため、ランダムに生成されるモデルの中でも形状が最もブロッコリーに近しいものの値を、進化戦略を使ってa, b, c, dの値を最適化したが、逆に形状が崩れたり、過度に拡大され、うまくいかなかった。ただ、ブロッコリーを実物ではなく違った表現で見せたかったため、少しでも形状を本物に近づけられたところは良かったと思う。
参考にした記事
以上。
最後までお読みいただきありがとうございました。