パーティクルを作ってみた

たまたま見かけた情報教材の動画で、きれいな?かわいい?アニメーションを見かけたので作ってみることにしました。

情報教材の動画はColosoってところの「魅力的なライブイラストを描く」といやつです。絵は描けないけど、右のようなアニメーションだったらプログラミングで作れるかな?

STEP1 ひな形作成

とりあえず色は頂戴する。

def setup():
    size(600, 300)
def draw():
    background(3, 88, 134)
    stroke(249, 170, 173)
    strokeWeight(5)
    line(0, height / 2, width, height / 2)

クラスを使って作るだろうからとりあえずの雛形

class particleA(object):
    def __init__(self, x, y, sizeMax, speed):
        self.x       = x
        self.y       = y
        self.sizeMax = sizeMax
        self.speed   = speed
        self.frame   = 0
    def drawParticle(self):
        line(self.x - self.sizeMax / 2, self.y, self.x + self.sizeMax / 2, self.y)
def setup():
    global p1, p2
    size(600, 300)
    p1 = particleA(100, 100, 10, 2)
    p2 = particleA(200, 200, 20, 2)
def draw():
    background(3, 88, 134)
    stroke(249, 170, 173)
    strokeWeight(5)
    p1.drawParticle()
    p2.drawParticle()

STEP2 分析

お手本のムービーを観察。フレームごとの内側の空白部分と線の長さを調べた。

フレームサイズ余白余白(比)線(比)
12001000.0625
22001000.0625
335017.500.109375
435017.500.109375
55602800.175
68016240.10.15
78016240.10.15
811040150.250.09375
91507140.443750.025
101607820.48750.0125
1100000
1200000

STEP4 固定値だけど作ってみる

class particleA(object):
    def __init__(self, x, y, sizeMax, speed):
        self.x        = x
        self.y        = y
        self.sizeMax  = sizeMax
        self.speed    = speed
        self.frame    = 0
        self.frameMax = 12
        self.sizes    = [10, 10, 17.5, 17.5, 28, 24, 24, 15, 4, 2, 0, 0]
        self.paddings = [0, 0, 0, 0, 0, 16, 16, 40, 71, 78, 0, 0]
    def drawParticle(self):
        w = self.sizes[self.frame]
        h = w
        p = self.paddings[self.frame]
        line(self.x + p, self.y, self.x + p + w, self.y) # 右
        line(self.x - p, self.y, self.x - p - w, self.y) # 左
        line(self.x, self.y - p, self.x, self.y - p - h) # 上
        line(self.x, self.y + p, self.x, self.y + p + h) # 下
        self.frame += 1
        if self.frame >= self.frameMax:
            self.frame = 0
def setup():
    global p1, p2
    size(600, 300)
    p1 = particleA(100, 100, 10, 2)
    p2 = particleA(400, 200, 20, 2)
def draw():
    background(3, 88, 134)
    stroke(249, 170, 173)
    strokeWeight(5)
    p1.drawParticle()
    p2.drawParticle()

STEP4 固定値から比率に変更

class particleA(object):
    def __init__(self, x, y, wMax, hMax, speed):
        self.x        = x
        self.y        = y
        self.wMax     = wMax
        self.hMax     = hMax
        self.speed    = speed
        self.frame    = 0
        self.frameMax = 12
        self.delay    = random(60, 180)
        self.sizes    = [0.0625, 0.0625, 0.109375, 0.109375, 0.175, 0.15, 0.15, 0.09375, 0.025, 0.0125, 0, 0]
        self.paddings = [0, 0, 0, 0, 0, 0.1, 0.1, 0.25, 0.44375, 0.4875, 0, 0]
    def drawParticle(self):
        if self.delay > 0:
            self.delay -= 1
            return
        w = self.sizes[self.frame] * self.wMax
        h = self.sizes[self.frame] * self.hMax
        pW = self.paddings[self.frame] * self.wMax
        pH = self.paddings[self.frame] * self.hMax
        line(self.x + pW, self.y, self.x + pW + w, self.y) # 右
        line(self.x - pW, self.y, self.x - pW - w, self.y) # 左
        line(self.x, self.y - pH, self.x, self.y - pH - h) # 上
        line(self.x, self.y + pH, self.x, self.y + pH + h) # 下
        if frameCount % self.speed == 0:
            self.frame += 1
            if self.frame >= self.frameMax:
                self.frame = 0
                # self.wMax = random(20, 160)
                # self.hMax = random(20, 160)
                # self.x = random(0, width)
                # self.y = random(0, height)
                self.delay = random(60, 180)

def setup():
    global p1, p2
    size(600, 300)
    p1 = particleA(100, 100, 160, 160, 2)
    p2 = particleA(400, 200, 50, 50, 2)

def draw():
    background(3, 88, 134)
    stroke(249, 170, 173)
    strokeWeight(2)
    p1.drawParticle()
    p2.drawParticle()

STEP5 別パターンの追加

3パターン追加されるので基底クラスを作成した。

  • ParticleA … 十字
  • ParticleB … ひし形(パラメーターでカーブを調整できる)
  • ParticleC … ひし形~十字
  • ParticleD … フラフラ十字
class Particle(object): # 基底クラス
    def __init__(self, x, y, wMax, hMax, speed):
        self.x        = x
        self.y        = y
        self.wMax     = wMax
        self.hMax     = hMax
        self.speed    = speed
        self.frame    = 0
        self.frameMax = 12
        self.delay    = 0
        
    def setRandom(self):
        self.wMax  = random(20, 160)
        self.hMax  = random(20, 160)
        self.x     = random(0, width)
        self.y     = random(0, height)
        self.delay = random(60, 180)

    def countUp(self):
        if frameCount % self.speed == 0:
            self.frame += 1
            if self.frame >= self.frameMax:
                self.frame = 0
                # self.setRandom()
        
class ParticleA(Particle): # 十字
    def __init__(self, x, y, wMax, hMax, speed):
        super(ParticleA, self).__init__(x, y, wMax, hMax, speed)
        self.sizes    = [0.0625, 0.0625, 0.109375, 0.109375, 0.175, 0.15, 0.15, 0.09375, 0.025, 0.0125, 0, 0]
        self.paddings = [0, 0, 0, 0, 0, 0.1, 0.1, 0.25, 0.44375, 0.4875, 0, 0]

    def drawParticle(self):
        if self.delay > 0:
            self.delay -= 1
            return
        w  = self.sizes[self.frame]    * self.wMax
        h  = self.sizes[self.frame]    * self.hMax
        pW = self.paddings[self.frame] * self.wMax
        pH = self.paddings[self.frame] * self.hMax
        line(self.x + pW, self.y,      self.x + pW + w, self.y         ) # 右
        line(self.x - pW, self.y,      self.x - pW - w, self.y         ) # 左
        line(self.x,      self.y - pH, self.x,          self.y - pH - h) # 上
        line(self.x,      self.y + pH, self.x,          self.y + pH + h) # 下
        self.countUp()

class ParticleB(Particle): # ひし形
    def __init__(self, x, y, wMax, hMax, speed, par=0.2):
        super(ParticleB, self).__init__(x, y, wMax, hMax, speed)
        self.sizes = [0.2884615385, 0.5192307692, 0.7115384615, 0.8365384615, 1, 1, 1, 0.8365384615, 0.7115384615, 0.7115384615, 0.5192307692, 0.5192307692]
        self.par   = par

    def drawParticle(self):
        if self.delay > 0:
            self.delay -= 1
            return
        w = self.sizes[self.frame] * self.wMax / 2
        h = self.sizes[self.frame] * self.hMax / 2
        bezier(self.x,      self.y - h, self.x,                self.y - h * self.par, self.x + w * self.par, self.y,                self.x + w, self.y     ) # 右上
        bezier(self.x + w,  self.y,     self.x + w * self.par, self.y,                self.x,                self.y + h * self.par, self.x,      self.y + h) # 右下
        bezier(self.x,      self.y + h, self.x,                self.y + h * self.par, self.x - w * self.par, self.y,                self.x - w, self.y     ) # 左下
        bezier(self.x - w,  self.y,     self.x - w * self.par, self.y,                self.x,                self.y - h * self.par, self.x,      self.y - h) # 左上
        self.countUp()

class ParticleC(Particle): # ひし形~十字
    def __init__(self, x, y, wMax, hMax, speed):
        super(ParticleC, self).__init__(x, y, wMax, hMax, speed)
        self.sizes     = [0.158974359, 0.2820512821, 0.3948717949, 0.1487179487, 0.08205128205, 0.05641025641, 0.01025641026, 0, 0, 0, 0, 0]
        self.pars      = [0.2, 0.2, 0.2, 0.8, 0.2, 0.2, 0.2, 0.0, 0.0, 0.0, 0.0, 0.0]
        self.paddings1 = [0, 0, 0, 0.1179487179, 0.2666666667, 0.358974359, 0, 0, 0, 0, 0, 0]
        self.lines1    = [0, 0, 0, 0.07692307692, 0.02051282051, 0.01538461538, 0, 0, 0, 0, 0, 0]
        self.paddings2 = [0, 0, 0, 0.02564102564, 0.06153846154, 0.08717948718, 0, 0, 0, 0, 0, 0]
        self.lines2    = [0, 0, 0, 0.05641025641, 0.02051282051, 0.01025641026, 0, 0, 0, 0, 0, 0]

    def drawParticle(self):
        if self.delay > 0:
            self.delay -= 1
            return

        # ひし形
        if self.sizes[self.frame] > 0:
            w = self.sizes[self.frame] * self.wMax / 2
            h = self.sizes[self.frame] * self.hMax / 2
            p = self.pars[self.frame]
            bezier(self.x,      self.y - h, self.x,         self.y - h * p, self.x + w * p, self.y,         self.x + w, self.y    ) # 右上
            bezier(self.x + w,  self.y,     self.x + w * p, self.y,         self.x,         self.y + h * p, self.x,     self.y + h) # 右下
            bezier(self.x,      self.y + h, self.x,         self.y + h * p, self.x - w * p, self.y,         self.x - w, self.y    ) # 左下
            bezier(self.x - w,  self.y,     self.x - w * p, self.y,         self.x,         self.y - h * p, self.x,     self.y - h) # 左上

        # 十字
        if self.lines1[self.frame] > 0:
            p1 = self.paddings1[self.frame] * self.wMax
            l1 = self.lines1[self.frame]    * self.wMax
            p2 = self.paddings2[self.frame] * self.wMax + p1 + l1
            l2 = self.lines2[self.frame]    * self.wMax

            # 内側直線
            line(self.x + p1, self.y,      self.x + p1 + l1, self.y          ) # 右
            line(self.x - p1, self.y,      self.x - p1 - l1, self.y          ) # 左
            line(self.x,      self.y - p1, self.x,           self.y - p1 - l1) # 上
            line(self.x,      self.y + p1, self.x,           self.y + p1 + l1) # 下

            # 外側直線
            line(self.x + p2, self.y,      self.x + p2 + l2, self.y          ) # 右
            line(self.x - p2, self.y,      self.x - p2 - l2, self.y          ) # 左
            line(self.x,      self.y - p2, self.x,           self.y - p2 - l2) # 上
            line(self.x,      self.y + p2, self.x,           self.y + p2 + l2) # 下
        self.countUp()

class ParticleD(Particle): # フラフラ十字
    def __init__(self, x, y, wMax, hMax, speed):
        super(ParticleD, self).__init__(x, y, wMax, hMax, speed)
        self.sizes  = [0.73684, 0.73684, 0.73684, 0.73684, 1.00000, 1.00000, 1.00000, 0.89474, 0.89474, 0.89474, 1.00000, 1.00000]
        self.bureXs = [1, 1, 1, 1, 0, 0, 0, -1, -1, -1, 0, 0]
        self.bureYs = [0, 0, 0, 0, 0, 0, 0, -1, -1, -1, 0, 0]

    def drawParticle(self):
        if self.delay > 0:
            self.delay -= 1
            return
        w = self.sizes[self.frame] * self.wMax / 2
        h = self.sizes[self.frame] * self.hMax / 2
        bx = self.bureXs[self.frame]
        by = self.bureYs[self.frame]
        line(self.x + bx - w , self.y + by,     self.x + bx + w, self.y + by    ) # 左右
        line(self.x + bx,      self.y + by - h, self.x + bx,     self.y + by + h) # 上下
        self.countUp()

def setup():
    global stars, fontSize
    size(500, 500)

    fontSize = 48
    font = createFont(u"美咲ゴシック", fontSize)
    textFont(font)

    stars = list()
    stars.append(ParticleA(320, 90, 150, 150, 3))
    stars.append(ParticleB(70, 170, 30, 50, 3))
    stars.append(ParticleC(240, 250, 200, 200, 3))
    stars.append(ParticleD(470, 240, 14, 16, 3))
    stars.append(ParticleC(50, 255, 200, 200, 3))
    stars.append(ParticleB(90, 350, 50, 80, 3))
    stars.append(ParticleC(340, 360, 200, 200, 3))
    stars.append(ParticleC(420, 370, 250, 250, 3))
    stars.append(ParticleB(140, 380, 40, 60, 3, 0.05))
    stars.append(ParticleD(340, 450, 22, 28, 3))

def draw():
    background(3, 88, 134)

    # 文字
    fill(23, 108, 154)
    textAlign(CENTER)

    textSize(fontSize)
    x = width / 2
    y = height / 2.3
    text("STAR PARTICLE", x, y)

    textSize(fontSize / 2)
    y += fontSize / 2
    text("Animation By vivinos__", x, y)
    y += fontSize / 1.5
    text("2022.10.9", x, y)

    # 星
    stroke(249, 170, 173)
    strokeWeight(2)
    noFill()
    for star in stars:
        star.drawParticle()

残骸

タイプ2は当初arc()を使って描こうとした。(完成形はbezier()にした。シンプルなので。)

class ParticleBOld(Particle):
    def __init__(self, x, y, wMax, hMax, speed):
        super(ParticleBOld, self).__init__(x, y, wMax, hMax, speed)
        self.inWidth   = [0.2884615385, 0.3269230769, 0.4230769231, 0.4903846154, 0.5384615385, 0.5384615385, 0.5384615385, 0.4903846154, 0.4230769231, 0.4230769231, 0.3269230769, 0.3269230769]
        self.inHeight  = [0.2884615385, 0.3269230769, 0.4230769231, 0.4903846154, 0.5384615385, 0.5384615385, 0.5384615385, 0.4903846154, 0.4230769231, 0.4230769231, 0.3269230769, 0.3269230769]
        self.outWidth  = [0.2884615385, 0.5192307692, 0.7115384615, 0.8365384615, 1, 1, 1, 0.8365384615, 0.7115384615, 0.7115384615, 0.5192307692, 0.5192307692]
        self.outHeight = [0.4166666667, 0.6666666667, 0.7083333333, 0.8958333333, 1, 1, 1, 0.8958333333, 0.7083333333, 0.7083333333, 0.6666666667, 0.6666666667]

    def drawParticle(self):
        if self.delay > 0:
            self.delay -= 1
            return

        # 比率から実サイズへ
        oW = self.outWidth[self.frame]  * self.wMax / 2
        oH = self.outHeight[self.frame] * self.hMax / 2
        iW = self.inWidth[self.frame]   * self.wMax / 2
        iH = self.inHeight[self.frame]  * self.hMax / 2

        # 試作の直線
        # line(self.x, self.y - oH, self.x + oW, self.y) # 右上
        # line(self.x + oW, self.y, self.x, self.y + oH) # 右下
        # line(self.x, self.y + oH, self.x -oW, self.y) # 左下
        # line(self.x -oW, self.y, self.x, self.y - oH) # 左上

        # ダイアモンド
        arc(self.x + iW, self.y - iH, iW * 2, iH * 2, PI * 0.5, PI      ) # 右上
        arc(self.x + iW, self.y + iH, iW * 2, iH * 2, PI,       PI * 1.5) # 右下
        arc(self.x - iW, self.y + iH, iW * 2, iH * 2, PI * 1.5, PI * 2.0) # 左下
        arc(self.x - iW, self.y - iH, iW * 2, iH * 2, 0,        PI * 0.5) # 左上
        line(self.x + iW, self.y, self.x + oW, self.y) # 右
        line(self.x - iW, self.y, self.x - oW, self.y) # 左
        line(self.x, self.y - iH, self.x, self.y - oH) # 上
        line(self.x, self.y + iH, self.x, self.y + oH) # 下
        self.countUp()

コメントする