【コピペOK】センサーで色が変わる!ハートが浮かぶ電子工作|Arduino×Python【Vol.1】」

Arduino 機器制作

 

 

Color Wave Borderとハートエフェクトを組み合わせたPygameとArduino連携の大冒険

 

本記事では、Arduinoからのセンサー入力をトリガーとして、Pygameで「Color Wave Border」と「Hearts Effect」を描画するPythonコードを徹底解説します。記事後半にかけて、各クラスの設計意図やアルゴリズム、そして実際のPygame描画フローまで詳しく見ていきます。この記事を読み終わる頃には、シリアル通信とPygameを掛け合わせたインタラクティブな作品の基礎アイデアをがっちり掴めることでしょう。

目次

  1. はじめに
  2. プログラム概要
    • 2.1 動作イメージ
    • 2.2 Arduinoとの連携ポイント
    • 2.3 Pygameでの描画ポイント
  3. 環境設定とライブラリ
    • 3.1 シリアル通信(pySerial)
    • 3.2 Pygame
    • 3.3 colorsys
  4. コード全体の流れ
  5. コード詳細解説
    • 5.1 シリアルポートの設定
    • 5.2 Pygameの初期化と画面設定
    • 5.3 枠(ボーダー)や内側描画領域の準備
    • 5.4 センサー状態を管理する変数
    • 5.5 便利関数
      • get_rainbow_color(t)
      • draw_heart(surface, x, y, size, color)
    • 5.6 クラス解説: ColorWaveEffect
    • 5.7 クラス解説: HeartsEffect
    • 5.8 メインループ main() の詳細
  6. テーブルでみる主要変数の役割
  7. 応用アイデア
    • 7.1 ハート以外の図形アニメーション
    • 7.2 センサーの種類を増やしてみる
    • 7.3 音や振動フィードバックへの拡張
  8. まとめ

1. はじめに

ゲーム開発やアート作品をPythonで行う場合、よく使用されるライブラリが「Pygame」です。今回のコードは、Arduinoから受け取るセンサー信号(TRIGGERという文字列)を合図に、Pygame画面上でハート形のパーティクルを浮遊させるエフェクトと、画面の四周(ボーダー)に虹色に変化するアニメーションを組み合わせています。
インタラクティブな作品を作りたい方、Arduino × Python連携による即興的・遊び心あるグラフィックを実装したい方には、格好の学習題材になるでしょう。

2. プログラム概要

2.1 動作イメージ

画面の縁(ボーダー)が、時間とともに虹色に移り変わります。
センサーが反応している間(Arduinoから一定時間内にTRIGGERが飛んでくる状態)、画面の内側にハートのパーティクルが湧き出て、上方向へ飛んでいきます。
センサーが一定時間反応しない(タイムアウトする)と、ハートは生成されなくなり、静かな状態に戻ります。

2.2 Arduinoとの連携ポイント

Arduinoスケッチ側で何らかのセンサー(例えばフォトインタラプタ、赤外線センサー、距離センサーなど)がトリガーを検出すると、シリアル通信でTRIGGERという文字列を送信すると想定。
Python側(本プログラム)がそれを受け取り、センサーのアクティブ状態(sensor_active = True)を更新します。
最後のTRIGGER受信時刻から1.5秒(1500ミリ秒)経過しても次のTRIGGERが来なければ、センサーアクティブがFalseになります。

2.3 Pygameでの描画ポイント

ColorWaveEffect: HSV色空間を用いて虹色を循環させる。画面ボーダーの色をゆっくりと変化。
HeartsEffect: 0.5秒おきにハートを発生させ、一定速度で上方向に移動。
内側を表すinner_rect(枠を除いた矩形)に、ハートエフェクトを描画。

3. 環境設定とライブラリ

3.1 シリアル通信(pySerial)

import serial
ArduinoとPythonをやり取りするためのライブラリです。
serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1) でシリアルポートに接続。環境によりCOM3部分などは変更が必要です。

3.2 Pygame

import pygame
Pythonで簡易的に2Dゲームや描画処理を行うための定番ライブラリ。

3.3 colorsys

import colorsys
HSV(色相・彩度・明度)とRGBの変換などを便利に行える標準ライブラリ。虹色アニメーションを作る際に非常に重宝します。

4. コード全体の流れ

下記が今回のプログラムの全体コードです。既に質問文で掲載されていますが、再掲しながら手順を整理します。

import serial
import pygame
import sys
import random
import math
import time
import colorsys

# --- シリアル通信の設定 ---
SERIAL_PORT = 'COM3'  # 環境によって変更
BAUD_RATE = 9600

try:
    ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
except Exception as e:
    print(f"シリアルポート {SERIAL_PORT} に接続できませんでした: {e}")
    sys.exit()

# --- Pygameの初期設定 ---
pygame.init()
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Color Wave Border with Hearts Effect")
clock = pygame.time.Clock()

# 枠の厚みと内側領域
BORDER_THICKNESS = 20
inner_rect = pygame.Rect(BORDER_THICKNESS, BORDER_THICKNESS, 
                         WIDTH - 2 * BORDER_THICKNESS, 
                         HEIGHT - 2 * BORDER_THICKNESS)

# センサー状態管理
sensor_active = False
last_trigger_time = 0
SENSOR_TIMEOUT = 1500  # 1500ms以内にTRIGGERがなければセンサー非アクティブ

# ---------- 補助関数 ----------
def get_rainbow_color(t):
    """tは0.0~1.0の値。HSV→RGB変換"""
    hue = t % 1.0
    r, g, b = colorsys.hsv_to_rgb(hue, 1.0, 1.0)
    return (int(r * 255), int(g * 255), int(b * 255))

def draw_heart(surface, x, y, size, color):
    """簡易的なハート形を多角形で描画"""
    points = [
        (x, y),
        (x - size, y - size),
        (x - size * 1.5, y - size * 0.2),
        (x, y + size),
        (x + size * 1.5, y - size * 0.2),
        (x + size, y - size)
    ]
    pygame.draw.polygon(surface, color, points)

# ---------- Color Wave Effect ----------
class ColorWaveEffect:
    def __init__(self):
        self.hue = 0.0

    def update(self, dt):
        self.hue += dt * 0.0001  # ゆっくり変化
        if self.hue > 1.0:
            self.hue -= 1.0

    def get_color(self):
        r, g, b = colorsys.hsv_to_rgb(self.hue, 1.0, 1.0)
        return (int(r * 255), int(g * 255), int(b * 255))

    def draw_border(self, surface, thickness):
        color = self.get_color()
        pygame.draw.rect(surface, color, surface.get_rect(), thickness)

# ---------- Hearts Effect ----------
class HeartsEffect:
    def __init__(self, area_width, area_height):
        self.area_width = area_width
        self.area_height = area_height
        self.hearts = []  # [x, y, speed]
        self.spawn_timer = 0

    def update(self, dt):
        self.spawn_timer += dt
        # 約0.5秒ごとにハート生成
        if self.spawn_timer > 500:
            self.spawn_timer = 0
            x = random.randint(0, self.area_width)
            y = self.area_height + 20
            speed = random.uniform(0.5, 1.5)
            self.hearts.append([x, y, speed])

        # ハートを上方向に移動
        for heart in self.hearts:
            heart[1] -= heart[2]

        # 画面外に出たハートを除去
        self.hearts = [h for h in self.hearts if h[1] > -50]

    def draw(self, surface):
        # 内側背景はfillしない → 背景は別途設定
        t = time.time() % 1.0
        for heart in self.hearts:
            color = get_rainbow_color((t + (heart[0] % 100) / 100.0) % 1.0)
            draw_heart(surface, heart[0], heart[1], 20, color)

# インスタンス生成
color_wave_effect = ColorWaveEffect()
hearts_effect = HeartsEffect(inner_rect.width, inner_rect.height)

# ---------- メインループ ----------
def main():
    global sensor_active, last_trigger_time
    running = True
    while running:
        dt = clock.tick(60)  # ミリ秒単位の経過時間
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        # Arduinoからシリアル入力
        if ser.in_waiting:
            try:
                line = ser.readline().decode('utf-8').strip()
            except UnicodeDecodeError:
                line = ""
            if line == "TRIGGER":
                last_trigger_time = pygame.time.get_ticks()
                sensor_active = True

        # センサーが一定時間反応しなければ非アクティブ
        if pygame.time.get_ticks() - last_trigger_time > SENSOR_TIMEOUT:
            sensor_active = False

        # センサーがアクティブならHeartsEffectを更新
        if sensor_active:
            hearts_effect.update(dt)

        # ColorWaveEffectを更新
        color_wave_effect.update(dt)

        # 画面クリア
        screen.fill((30, 30, 30))
        color_wave_effect.draw_border(screen, BORDER_THICKNESS)

        # 内側のサーフェスを作成し背景色を塗る
        inner_surface = pygame.Surface((inner_rect.width, inner_rect.height))
        inner_surface.fill((20, 20, 20))
        hearts_effect.draw(inner_surface)
        screen.blit(inner_surface, (inner_rect.x, inner_rect.y))

        pygame.display.flip()

    pygame.quit()
    ser.close()
    sys.exit()

if __name__ == "__main__":
    main()

5. コード詳細解説

5.1 シリアルポートの設定

SERIAL_PORT は環境に合わせたシリアルポート名(例: COM3)です。
BAUD_RATE はArduinoスケッチ側で設定したボーレートに合わせる必要があります。一般的には9600115200
timeout=1 は1秒待ってもデータが受信できない場合は読み込みを諦める設定。
万一ポート指定にミスがあればexcept Exceptionでキャッチし、プログラム終了(sys.exit())します。

5.2 Pygameの初期化と画面設定

pygame.init() でPygameを初期化し、pygame.display.set_mode()で画面(ウィンドウ)を生成しています。
画面解像度は800x600に設定。
pygame.display.set_caption() でウィンドウタイトルを設定。
pygame.time.Clock() はFPS制御と経過時間計測用。

5.3 枠(ボーダー)や内側描画領域の準備

BORDER_THICKNESS = 20 でピクセル単位の枠の厚みを指定。
inner_rect は枠を除いた内側の矩形領域を定義しており、pygame.Rectを利用。

5.4 センサー状態を管理する変数

sensor_active はセンサーが最近反応したかどうかの状態を示すブール値。
last_trigger_time は最後にTRIGGERを受信した時刻(ミリ秒)。
SENSOR_TIMEOUT は1.5秒(1500ミリ秒)以内に再度TRIGGERが来なければセンサーを非アクティブにする閾値。

5.5 便利関数

5.5.1 get_rainbow_color(t)

tは0.0~1.0の値。
colorsys.hsv_to_rgb を利用してHSVからRGBへ変換し、RGB値を0~255にスケーリングして返却。

5.5.2 draw_heart(surface, x, y, size, color)

簡易的にハート形に見える多角形を定義し、pygame.draw.polygon を使って描画します。

5.6 クラス解説: ColorWaveEffect

__init__()
self.hue = 0.0 で色相の初期値を設定。

update(self, dt)
dtは前フレームからの経過時間(ミリ秒)。毎フレーム少しずつhueを増加させ、1.0を超えたら循環させます。

get_color(self)
現在のhueに基づいてRGBカラーを生成し、整数値に変換して返却。

draw_border(self, surface, thickness)
画面全体の矩形(surface.get_rect())を対象に、現在の虹色を枠線として描画。

5.7 クラス解説: HeartsEffect

__init__(self, area_width, area_height)
ハートが動き回るエリアの幅と高さを受け取り、self.heartsに各ハートの状態([x, y, speed])を格納します。

update(self, dt)
spawn_timer に経過時間を加算し、約0.5秒ごとに新たなハートを生成。生成後は各ハートの位置を上方向へ移動させ、画面外のハートを削除します。

draw(self, surface)
現在時刻に基づく虹色を各ハートに適用し、ハート形を描画するためにdraw_heart関数を呼び出します。

5.8 メインループ main() の詳細

ゲームループ内で、以下の処理を順次実行しています。

  • FPS制御と経過時間の取得(dt = clock.tick(60)
  • Pygameのイベント処理(ウィンドウの閉じるボタンの検知)
  • Arduinoからのシリアル入力の確認とTRIGGERの処理
  • センサーが一定時間反応しなければ非アクティブにする処理
  • センサーがアクティブな場合はHeartsEffectを更新
  • ColorWaveEffectの更新
  • 画面全体のクリアとボーダーの描画
  • 内側サーフェスの作成とハートエフェクトの描画
  • 画面更新(pygame.display.flip()
  • ループ終了後のクリーンアップ(Pygame終了、シリアルポートのクローズ、プログラム終了)

6. テーブルでみる主要変数の役割

変数/属性役割
SERIAL_PORTstrシリアルポート名 (COM3 など)
BAUD_RATEintボーレート (9600 など)
serserial.Serialシリアル通信オブジェクト
WIDTH, HEIGHTint画面幅・高さ (800, 600)
screenpygame.Surfaceメイン描画先の画面サーフェス
clockpygame.time.Clockフレームレート管理と経過時間計測用
BORDER_THICKNESSint枠線の太さ (20)
inner_rectpygame.Rect枠を除いた内側の領域
sensor_activeboolセンサーが一定時間内に反応しているかどうか
last_trigger_timeint最後の TRIGGER 受信時刻 (ms)
SENSOR_TIMEOUTintセンサーが非アクティブとみなす閾値 (1500 ms)
ColorWaveEffect.huefloat色相を管理する変数 (0.0~1.0で循環)
HeartsEffect.heartslistハート情報を格納 ([x, y, speed] のリスト)
HeartsEffect.spawn_timerint次にハートを生成するまでのタイマー

7. 応用アイデア

7.1 ハート以外の図形アニメーション

ハートのポリゴンを星形や独自の図形に置き換えることも簡単です。draw_heart()関数を改造して様々な形を描画してみましょう。
あるいはPygameのblit()を使い、好きな画像を貼り付けることも可能です(透過PNGを使えば多彩なエフェクトが楽しめます)。

7.2 センサーの種類を増やしてみる

例えば「センサー1が反応したら虹色の速さを上げる」「センサー2が反応したらハートが爆発的に増える」など、複数の入力を同時に処理するとよりインタラクティブな体験が作れます。
Arduino側で複数のセンサー値を取得し、それに応じて異なる文字列(TRIGGER_A, TRIGGER_Bなど)を送信し、Python側で条件分岐するのも面白いです。

7.3 音や振動フィードバックへの拡張

Pygameには簡単なサウンド再生機能があります。センサーが反応した瞬間にSEを鳴らすと、臨場感がアップ。
Arduino側にブザーや振動モーターをつないでフィードバックを返すのも、インタラクションの幅が広がります。

8. まとめ

今回のコードは、
1. シリアル通信 を通じてArduinoのセンサー信号を受信し、
2. Pygame を用いて画面を更新し、
3. インタラクティブなハートのアニメーションボーダーの虹色エフェクトを実装する
という構成でした。

Pygameは初心者でも比較的扱いやすく、リアルタイム描画を視覚化するのに向いています。Arduinoなどの物理センサーと組み合わせることで、より体験型・操作型の作品を作り出すことができます。本プログラムをベースに以下のような発展が考えられます。

  • 複数種類のセンサーと連動した演出 (超音波センサーがトリガーしたら画面が揺れる等)
  • AIを組み合わせて、センサー値や画像認識を基に画面演出を変更
  • ハート以外にも、星、雪の結晶、弾幕シューティングの弾などをエフェクトとして応用

制作の幅は無限大です。是非、今回の解説を参考にご自身のアイデアを盛り込んだ拡張作品を作ってみてください。
少しでも参考になりましたら幸いです。ここまでお読みいただき、ありがとうございました!

おまけ: PythonとArduinoを連携させる際の注意

– ArduinoとPythonでボーレートやシリアルポートを正しく合わせる。
– Windowsの場合、デバイスマネージャでArduinoのポート名 (COM3 など) を確認。macOSやLinuxは/dev/tty...などを正確に把握。
– センサーの値を送る際は、Arduino側でSerial.println("TRIGGER");のように改行文字を送信すると、readline()が行単位で受信しやすくなります。

これらを意識すれば、スムーズにPygameとArduinoを連携できるでしょう。
どうぞワクワクするデジタルアートの世界をお楽しみください!

 

特集記事

コメント

この記事へのコメントはありません。

TOP