6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

LovyanGFXでPartyParrotを動かそう(M5Stack)

Last updated at Posted at 2020-07-07

なぜPartyParrotを作ろうと思ったのかはこちらのツイートが発端です

なんかPartyParrotの動きに似てるな・・・(そうか?)

こちらの3次ベジェ曲線追加のツイートでこれはいけそうだなと思って着手しました

##LovyanGFXをインストールする##

LovyanGFXとは
ESP32/SAMD51とSPI接続のLCDの組み合わせで動作するグラフィックライブラリです。
AdafruitGFX や TFT_eSPI と互換性をある程度持ちつつ、より高機能・高速動作を目標としています。(公式Githubより引用)

ライブラリマネージャでLovyanと検索すると出てきます

##PartyParrotを描画する##

ベジェ曲線を使用してPartyParrotを描画します
結構大変なのでいろいろと省略してます
後ほどアニメーションさせるのでspriteを使用します

#include <LovyanGFX.h>

static LGFX lcd;                // LGFXのインスタンスを作成。
static LGFX_Sprite sprite(&lcd); // スプライトを使う場合はLGFX_Spriteのインスタンスを作成。

void drawSpriteParrot() {
        sprite.clear();
	sprite.setColor(lcd.color332(255, 255, 255));
	sprite.drawBezier(225, 70, 200, 80, 225, 150); // クチバシ左半分
	sprite.drawBezier(225, 70, 250, 80, 225, 150); // クチバシ右半分
	sprite.drawBezier(40, 240, 160, 210, 110, 40, 220, 40); // 左下から頭の上まで
	sprite.drawBezier(220, 40, 280, 40, 300, 120); // 頭の上から右頬まで
	sprite.drawBezier(300, 120, 310, 180, 270, 200, 290, 240); // 右頬から右下まで
	sprite.fillEllipse(260, 80, 10, 15); // 右目
	sprite.fillEllipse(190, 80, 10, 15); // 左目
	sprite.pushSprite(0, 0);
}


void setup(void) {
	Serial.begin(115200);
	lcd.init();

	lcd.setRotation(1);

	lcd.setBrightness(200);

	sprite.setPsram(true);
	sprite.setColorDepth(8);
	sprite.createSprite(320, 240);
	lcd.clear();
}

void loop(void) {
    	drawSpriteParrot();
	delay(1000);
}

image.png

これを基本形とします

##PartyParrotをアニメーションさせる##

とりあえず細かい再現は抜きとして動かすことにしました

要点としては右下と左下の点以外をすべて楕円運動させるというだけです

楕円運動させればいいということでラジアンの変数を作り0~2までループさせることにしました

float rad = 0;
float delta = 0.05;

uint8_t r, g, b;

int dx = 0, dy = 0;

void loop(void) {

	dx = cos(rad * 3.14) * 60 - 60;
	dy = sin(rad * 3.14) * -20;
	delay(1);
	rad += delta;
	if (2 < rad) {
		rad = 0;
	}
}

dxは0~-120、dyは20~-20の間で楕円運動します
(基本形が右中央から始まるため)

ここで計算したdx,dyを描画関数に渡します


void drawSpriteParrot(int pdx, int pdy, uint8_t r, uint8_t g, uint8_t b) {
	sprite.clear();
	sprite.setColor(lcd.color332(255, 255, 255));
	sprite.drawBezier(225 + pdx, 70 + pdy, 200 + pdx, 80 + pdy, 225 + pdx, 150 + pdy);
	sprite.drawBezier(225 + pdx, 70 + pdy, 250 + pdx, 80 + pdy, 225 + pdx, 150 + pdy);
	// 左下の点のみ動かさない
	sprite.drawBezier(40, 240, 160 + pdx, 210 + pdy, 110 + pdx, 40 + pdy, 220 + pdx, 40 + pdy);
	sprite.drawBezier(220 + pdx, 40 + pdy, 280 + pdx, 40 + pdy, 300 + pdx, 120 + pdy);
	// 右下の点のみ動かさない
	sprite.drawBezier(300 + pdx, 120 + pdy, 310 + pdx, 180 + pdy, 270 + pdx, 200 + pdy, 290, 240);
	sprite.fillEllipse(260 + pdx, 80 + pdy, 10, 15);
	sprite.fillEllipse(190 + pdx, 80 + pdy, 10, 15);
	sprite.pushSprite(0, 0);
}

こうすることで動いてる頭と動かない肩への線がベジェ曲線で滑らかにつながることで
違和感のないアニメーションができます
(実際のPartyParrotはそこまで単純ではないがここでは省略)
spriteを使用しているのでちらつきもありません

##PartyParrotの色を変更する##

線画だと寂しいので色を変えます
先ほど使ったラジアンを使いRGBを1/3周期ずつずらしてループさせます

	r = abs(sin(rad * 3.14)) * 255;
	g = abs(sin((rad + 0.6) * 3.14)) * 255;
	b = abs(sin((rad + 1.2) * 3.14)) * 255;

そのRGBを領域内を塗りつぶす
sprite.floodFill( std::int32_t x, std::int32_t y, const T& color)
に渡します


void drawSpriteParrot2(int pdx, int pdy, uint8_t r, uint8_t g, uint8_t b) {
	sprite.clear();
	sprite.setColor(lcd.color332(255, 255, 255));
	sprite.drawBezier(225 + pdx, 70 + pdy, 200 + pdx, 80 + pdy, 225 + pdx, 150 + pdy);
	sprite.drawBezier(225 + pdx, 70 + pdy, 250 + pdx, 80 + pdy, 225 + pdx, 150 + pdy);
	sprite.drawBezier(40, 240, 160 + pdx, 210 + pdy, 110 + pdx, 40 + pdy, 220 + pdx, 40 + pdy);
	sprite.drawBezier(220 + pdx, 40 + pdy, 280 + pdx, 40 + pdy, 300 + pdx, 120 + pdy);
	sprite.drawBezier(300 + pdx, 120 + pdy, 310 + pdx, 180 + pdy, 270 + pdx, 200 + pdy, 290, 240);
	sprite.floodFill(120, 220, lcd.color332(r, g, b)); // オウムの内側を塗りつぶす
	sprite.setColor(lcd.color332(0, 0, 0));
        // 塗りつぶした後目を黒で描く
	sprite.fillEllipse(260 + pdx, 80 + pdy, 10, 15);
	sprite.fillEllipse(190 + pdx, 80 + pdy, 10, 15);
	sprite.pushSprite(0, 0);
}

これで完成です!

##ソースコード##

おまけ機能を付けておいたのでA(左)ボタンを押してみてください

ちなみにdeltaを変更するとスピードを変えられますが、
FPSが頭打ちになるので実際のGIFと同じスピードにはなりません(´・ω・`)

M5 PartyParrot

#include <Arduino.h>

#include <LovyanGFX.h>

static LGFX lcd;                // LGFXのインスタンスを作成。
static LGFX_Sprite sprite(&lcd); // スプライトを使う場合はLGFX_Spriteのインスタンスを作成。

// M5Stack
#define BUTTON_A_PIN 39
#define BUTTON_B_PIN 38

void drawSpriteParrot2(int pdx, int pdy, uint8_t r, uint8_t g, uint8_t b) {
	sprite.clear();
	sprite.setColor(lcd.color332(255, 255, 255));
	sprite.drawBezier(225 + pdx, 70 + pdy, 200 + pdx, 80 + pdy, 225 + pdx, 150 + pdy);
	sprite.drawBezier(225 + pdx, 70 + pdy, 250 + pdx, 80 + pdy, 225 + pdx, 150 + pdy);
	sprite.drawBezier(40, 240, 160 + pdx, 210 + pdy, 110 + pdx, 40 + pdy, 220 + pdx, 40 + pdy);
	sprite.drawBezier(220 + pdx, 40 + pdy, 280 + pdx, 40 + pdy, 300 + pdx, 120 + pdy);
	sprite.drawBezier(300 + pdx, 120 + pdy, 310 + pdx, 180 + pdy, 270 + pdx, 200 + pdy, 290, 240);
	sprite.floodFill(120, 220, lcd.color332(r, g, b));
	sprite.setColor(lcd.color332(0, 0, 0));
	sprite.fillEllipse(260 + pdx, 80 + pdy, 10, 15);
	sprite.fillEllipse(190 + pdx, 80 + pdy, 10, 15);
	sprite.pushSprite(0, 0);
}

void drawSpriteBokyou(int pdx, int pdy, uint8_t r, uint8_t g, uint8_t b) {
	sprite.clear();

	sprite.fillRect(215 + pdx, 70 + pdy, 10, 80, lcd.color332(r, g, b));

	sprite.setColor(lcd.color332(255, 255, 255));

	sprite.drawBezier(40, 240, 160 + pdx, 210 + pdy, 110 + pdx, 40 + pdy, 220 + pdx, 40 + pdy);
	sprite.drawBezier(220 + pdx, 40 + pdy, 280 + pdx, 40 + pdy, 300 + pdx, 120 + pdy);
	sprite.drawBezier(300 + pdx, 120 + pdy, 310 + pdx, 180 + pdy, 270 + pdx, 200 + pdy, 290, 240);
	sprite.pushSprite(0, 0);
}

void setup(void) {
	Serial.begin(115200);
	  lgfx::lgfxPinMode(BUTTON_A_PIN, lgfx::pin_mode_t::input);
	  lgfx::lgfxPinMode(BUTTON_B_PIN, lgfx::pin_mode_t::input);
	lcd.init();

	lcd.setRotation(1);

	lcd.setBrightness(200);

	sprite.setPsram(true);
	sprite.setColorDepth(8);
	sprite.createSprite(320, 240);
	lcd.clear();
}

float rad = 0;
float delta = 0.05;

uint8_t r, g, b;

int dx = 0, dy = 0;

bool bokyou = false;

void loop(void) {
    static bool pressed;
    if (BUTTON_A_PIN >=0 && lgfx::gpio_in(BUTTON_A_PIN) == 0)
    {
      if (!pressed)
      {
        pressed = true;
        bokyou = !bokyou;
      }
    }
    else
    {
      pressed = false;
    }

	r = abs(sin(rad * 3.14)) * 255;
	g = abs(sin((rad + 0.6) * 3.14)) * 255;
	b = abs(sin((rad + 1.2) * 3.14)) * 255;

	dx = cos(rad * 3.14) * 60 - 60;
	dy = sin(rad * 3.14) * -20;
	if (bokyou) {
		drawSpriteBokyou(dx, dy, r, g, b);
	}
	else {
    	drawSpriteParrot2(dx, dy, r, g, b);
	}
	delay(1);
	rad += delta;
	if (2 < rad) {
		rad = 0;
	}
}

6
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?