LoginSignup
13
7

More than 3 years have passed since last update.

「璃奈ちゃんボード」をつくろう!プログラム解説編

Last updated at Posted at 2020-07-17

前回までのあらすじ

「璃奈ちゃんボード」をつくろう!要件定義編
「璃奈ちゃんボード」をつくろう!設計編

ここまで要件定義編では「どんな機能を実装するか」を、設計編では「どうやって実装するか」を検討してきました。
今回は、実際に作成したプログラムを見ながら実装についての詳しい解説を行っていきます。

おおまかな製作工程・完成品の様子はこちらです。併せてご覧ください。
璃奈ちゃんボードの作り方【ラブライブ!虹ヶ咲学園スクールアイドル同好会】

また、今回の記事をもって璃奈ちゃんボード解説シリーズは終了となります。最後まで楽しんで(?)いただければ幸いです。

ソフトウェア解説

詳しいことは後述しますがArduino用の制御用プログラムの他に、GoogleAppsScript を用いて表情設定ファイル自動生成スクリプトも作成しました。
今回はこれら2つのプログラムについての解説を行っていきます。

Arduino

Arduinoに書き込むプログラムはスケッチと呼ばれます。スケッチにはC/C++をベースにしたArduino言語という専用の言語が用いられ、このArduino言語はC言語のすべての構造といくつかのC++の機能をサポートしています。僕はC言語は基礎を少々触れたことがありC++は全くの未経験という状況だったのでほぼC言語の知識でスケッチを作成しましたが、概ね問題なくコーディングを行うことができました。C言語の基礎を知っていれば違和感なく扱えるのではないかと思います。

Arduinoの基礎

プログラム解説を行う前に、Arduinoの動作について簡単に説明をします。
Arduinoは先述したように、Arduino言語で書かれたスケッチを書き込むことで動作します。
また、Arduinoは多数のI/Oポート(ピン)を備えており、そこに各種センサーやスイッチなどを接続することでハードウェアとソフトウェアを連携させることが可能です。

スケッチは基本的に以下の形式で記述されます。

void setup() 
{
  // 電源が入った時最初に一回だけ実行される処理
}

void loop() 
{
  // 電源が入っている間ずっと繰り返される処理
}

setup()関数は起動時に一度だけ行われる処理で、その後起動している間ずっとloop()関数が繰り返し実行され続けます。

簡単な具体例で説明します。
例えばArduinoの10番目のピンに普通のLEDを接続し、以下のスケッチを書き込んだとします。

void setup() 
{
  pinMode(10, OUTPUT);      //「10番のピンを出力に使います」という宣言
}

void loop() 
{
  digitalWrite(10, HIGH);   //「10番のピンを"HIGH"にする(=10番につないだLEDをオンにする)」という命令
  delay(1000);              //1000ミリ秒(1秒)待機
  digitalWrite(10, LOW);    //「10番のピンを"LOW"にする(=10番につないだLEDをオフにする)」という命令
  delay(1000);              //1000ミリ秒(1秒)待機
}

電源が入るとまずsetup()が実行され10番のピンを出力に使う準備が行われ、その後loop()に書かれた「LEDが光る→1秒待機→LEDが消える→1秒待機」という処理がずっと繰り返し実行されます。つまり、このスケッチが書き込まれたArduinoは 「電源を切るまで1秒間隔でLEDが点滅し続ける装置」 ということになります。

「璃奈ちゃんボード」のスケッチ

Arduinoの動作の基礎について説明が終わったところで、ついに璃奈ちゃんボードのスケッチがどんな動作をしているのか見ていきたいと思います。細かい実装については説明を端折るので、興味のある方は以下からソースを覗いてみてください。
https://github.com/hmskdagoyabai/rinachan-board

ヘッダインクルード&各種宣言部分

まずはヘッダインクルード&各種宣言部分を見ていきます。

#include <FastLED.h>
#include "faces.h"

今回はNeoPixelの制御にFastLEDというライブラリを使用するためFastLED.hをインクルードします。(事前にArduino IDEでライブラリのインストールを行う必要あり)
また、faces.hはGASで生成した表情設定ファイルです。これについては後ほど詳しく解説します。

/* PIN */
#define SW1_PIN 5
#define SW2_PIN 4
#define SW3_PIN 3
#define SW4_PIN 2
#define DATA_PIN 10

使用するピンについての宣言は設計編で決めたとおり、SW1~4をつなぐピンとNeoPixel制御用のデータピンだけです。

/* FastLED */
#define RINA_PINK 0xff1493
#define NUM_LEDS 154
CRGB leds[NUM_LEDS];

FastLEDで使うピンク色のカラーコードをRINA_PINKとして定義しています。ちなみにRINA_PINKこんな色です。
また、制御するLEDの数(今回は設計編で決めた通り154個)をNUM_LEDSと定義しています。FastLEDでは繋いだNeoPixelLEDを配列として扱うことができるので、その為の配列としてCRGB leds[NUM_LEDS]を宣言しています。CRGBFastLEDで定義されている構造体ですが、その内容についてはとりあえず意識しなくても大丈夫です。

各種操作用フラグなど

今回のプログラムでは各種モードや表情・プリセットなどをフラグで管理しており、それらのフラグはグローバル変数としてプログラムの最初に宣言しています。

/* 各種操作用フラグ・パラメータ */
int brightness = 30;            //default 30
int mode = 1;                   //1:random 2:manual 0:dokipipo
int random_timemode = 1;        //0:random 1:constant
int now_preset = 0;             //selected preset number
int now_preset_size = FACES;    //selected preset size
int now_manualmode_emote = 0;   //selected emotion in manualmode
int automode_preset = 0;        //selected preset in automode (only 0:dokipipo)
int automode_playing = 0;       //playing flag
int automode_waiting_emote = 0; //selected emotion in automode

それぞれ簡単に説明します。

  • brightness
    明るさを指定。SW4で明るさの調整が可能。

  • mode
    使用中のモード番号を保持するフラグ。(1:ランダムモード 2:マニュアルモード 0:プリセット再生モード)

  • random_timemode
    ランダムモード時の表情切り替え時間設定を保持するフラグ。(0:等間隔モード 1:ランダム間隔モード)
    オート感が減るかな?と思ってランダムな間隔で表情が切り替わるモードを実装したけど、いまいち。

  • now_preset
    現在使用中の表情プリセット番号を保持するフラグ。

  • now_preset_size
    使用中の表情プリセットの要素数を保持するフラグ。

  • now_manualmode_emote
    マニュアルモードで表示中の表情番号を保持するフラグ。

  • automode_preset
    再生モードで選択している再生プリセットを保持するフラグ。死に機能気味。

  • automode_playing
    再生モードの再生開始フラグ。死に機能気味。

  • automode_waiting_emote
    再生モードの再生待ち中に表示しておく表情番号を保持するフラグ。死に機能気味。

メインの処理

ようやくメインの処理についてです。
以下が今回のスケッチのsetup関数とloop関数です。

void setup()
{
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
  FastLED.setBrightness(brightness);
  pinMode(SW1_PIN, INPUT_PULLUP);
  pinMode(SW2_PIN, INPUT_PULLUP);
  pinMode(SW3_PIN, INPUT_PULLUP);
  pinMode(SW4_PIN, INPUT_PULLUP);
  randomSeed(analogRead(0)); //乱数初期化
}

void loop()
{
  operate_flags_by_switch();
  delay(50);
  operate_emotion_by_flag();
  delay(50);
}
setup()

まずsetup関数で初期処理を行います。
最初の2行では、FastLED.addLedsで使用するNeoPixelLEDを定義しFastLED.setBrightnessでLEDの明るさを指定しています。
その次の4行はSW1~4をインプットに使用するという宣言で(プルアップを有効)、最後の行はランダム関数の初期化を行っています。ここでは未使用の0番ピンのアナログ入力(=ノイズ)をseed値として利用しています。(参考:http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=2867

loop()

そしてメインの処理であるloop関数の内容です。
operate_flags_by_switchは「スイッチを読み取りフラグを操作する関数」で、 operate_emotion_by_flagは「フラグを読み取り表情(画面)を操作する関数」となっています。
一見かなりシンプルですが、ここの処理は少し複雑なのでフローチャートで示します。各処理の内容は詳しく説明しないので、興味のある方はコードを参照してみてください。

operate_flags_by_switch

operate_flags_by_switchでは4つのボタンの入力を読み取り、入力に応じてsw1()sw4()という関数を呼び出しています。
flag.png

SW3SW4は長押しに対応するためカウンタを用いて長押ししている時間を計測し、長押し/短押しによって異なる引数を与えてsw3sw4関数を呼び出しています。最終的に呼び出されたsw1()sw4()関数がフラグの操作を行います。
例として以下にsw3関数の内容を示します。

/* SW3の挙動 */
void sw3(bool is_long)
{
  //randmode時
  if (mode == 1)
  {
    //間隔モードを切り替える
    random_timemode = !random_timemode;
  }
  //manualmode時
  else if (mode == 2)
  {
    //短押し時はプリセットをひとつ進める
    if (!is_long)
    {
      now_preset = (now_preset + 1) % PRESETS;
      now_preset_size = presets_sizes[now_preset];
      now_manualmode_emote = 0;
    }
    //長押し時はプリセットをallに
    else if (is_long)
    {
      now_preset = 0;
      now_preset_size = presets_sizes[now_preset];
      now_manualmode_emote = 0;
    }
  }
  //automode時
  else if (mode == 0)
  {
    //誤操作防止のため長押し時のみ反応
    if (is_long)
    {
      //再生フラグオン
      automode_playing = 1;
    }
  }
}

modeの値と引数で与えられたis_longフラグの値によって行う操作を決定する動作です。要件定義編で示した操作方法の表と見比べてみると何をしているのかがわかりやすいかもしれません。興味のある方は他のsw関数も操作方法の表と見比べてみてください。

モード スイッチ 動作
全モード共通 SW4 短押し : 明るさを1段階上げる
長押し : モードを1つ進める
ランダムモード SW1 プリセットを1つ進める
ランダムモード SW2 プリセットを1つ戻す
ランダムモード SW3 表情が切り替わる間隔を変更する(等間隔 or ランダム間隔)
マニュアルモード SW1 表情を1つ進める
マニュアルモード SW2 表情を1つ戻す
マニュアルモード SW3 短押し : プリセットを1つ進める
長押し : プリセットを初期値(全表情)に変更する
再生モード SW1 再生待機の表情を1つ進める
再生モード SW2 再生待機の表情をランダムに変更する
再生モード SW3 短押し : 何もしない(誤操作防止)
長押し : パターン再生開始
operate_emotion_by_flag

operate_emotion_by_flagでは現在のモードや各種フラグを読み取り、最終的にset_face関数によって状態に応じた表情を出力します。
emotion.png

このset_face関数に 選択中のプリセット番号選択中の表情番号 を引数として渡すことで表情が切り替わる仕組みになっています。こちらも操作方法の表を見比べながらソースを見てみると動作がわかりやすいと思います。また、set_face関数で用いられているpresets_sizelistpresetsなどの定義は表情定義ファイルfaces.hに記載されています。これについては詳しく後述します。

/* 選んだ表情に光らせる */
void set_face(int preset_no, int face_no)
{
  //対象の表情の要素数と表情配列を取得
  int size = presets_sizelist[preset_no][face_no];
  int *emotion = presets[preset_no][face_no];

  //一旦全部OFF
  set_black();
  //その後表情配列を読み該当箇所のみON
  for (int i = 0; i < size; i++)
  {
    leds[emotion[i]] = RINA_PINK;
  }
  FastLED.show();
}

表情設定ファイル

表情設定ファイルfaces.hの内容についても説明していきます。この内容は、おそらくポインタについてある程度理解していないと何を言ってるか全くわからないと思うのでな~んとなくで聞き流してください。

ファイルの内容はこんな感じになっています。

#define FACES 14
#define PRESETS 4

/* 表情配列定義 */
int smile[] = {143, 144, 151, 152, 138, 137, 134, 133, 123, 124, 114, 113, 110, 109, 95, 96, 103, 104, 71, 73, 78, 80, 41, 42, 43, 44, 45, 46, 47, 48, 38, 31, 22, 27, 16, 13, 4, 5};
int evil[] = {119, 120, 127, 128, 116, 115, 114, 113, 110, 109, 108, 107, 96, 97, 102, 103, 68, 66, 53, 51, 38, 31, 22, 23, 24, 25, 26, 27};
int cry1[] = {139, 138, 133, 132, 119, 120, 122, 125, 126, 128, 116, 115, 114, 113, 110, 109, 108, 107, 95, 96, 97, 102, 103, 93, 91, 71, 37, 36, 35, 34, 33, 32};
int me[] = {143, 144, 151, 152, 138, 137, 134, 133, 123, 124, 114, 113, 110, 109, 95, 96, 103, 104, 71, 73, 78, 80, 43, 44, 45, 46, 37, 32, 22, 27, 17, 12, 3, 4, 5, 6};
int patiri[] = {140, 134, 120, 121, 126, 127, 115, 114, 109, 108, 96, 97, 102, 103, 91, 90, 85, 84, 41, 42, 43, 44, 45, 46, 47, 48, 38, 31, 21, 28, 17, 12, 3, 4, 5, 6};
int suya[] = {116, 107, 96, 97, 98, 99, 100, 101, 102, 103, 68, 66, 53, 51, 41, 42, 43, 44, 45, 46, 47, 48, 38, 31, 22, 27, 16, 13, 4, 5};
int mu[] = {140, 139, 132, 131, 121, 122, 125, 126, 112, 111, 97, 98, 101, 102, 92, 91, 84, 83, 68, 66, 53, 51, 38, 31, 22, 27, 16, 15, 14, 13};
int nikori[] = {139, 138, 133, 132, 119, 120, 121, 126, 127, 128, 116, 115, 114, 113, 110, 109, 108, 107, 96, 97, 102, 103, 68, 66, 53, 51, 41, 42, 43, 44, 45, 46, 47, 48, 38, 31, 21, 28, 17, 12, 3, 4, 5, 6};
int cry2[] = {139, 138, 133, 132, 119, 120, 122, 125, 126, 128, 116, 115, 114, 113, 110, 109, 108, 107, 96, 97, 102, 103, 104, 84, 82, 80, 43, 44, 45, 46, 37, 32, 22, 27, 17, 12, 3, 4, 5, 6};
int u[] = {140, 139, 133, 132, 121, 122, 126, 127, 112, 109, 108, 97, 98, 102, 103, 92, 91, 68, 66, 53, 51, 44, 45, 36, 33, 23, 26, 15, 14};
int tere[] = {140, 134, 120, 121, 126, 127, 115, 114, 109, 108, 96, 97, 102, 103, 91, 90, 85, 84, 68, 66, 53, 51, 21, 28, 17, 12, 3, 4, 5, 6};
int upset[] = {144, 145, 146, 150, 140, 136, 133, 119, 123, 126, 113, 109, 97, 102, 90, 85, 66, 63, 62, 61, 58, 57, 56, 53, 40, 44, 45, 49, 39, 30, 20, 29, 19, 15, 14, 10, 1, 2, 3, 6, 7, 8};
int wink[] = {132, 131, 121, 122, 125, 126, 114, 113, 111, 97, 98, 101, 102, 90, 89, 84, 83, 68, 66, 53, 51, 41, 42, 43, 44, 45, 46, 47, 48, 38, 31, 22, 27, 16, 15, 14, 13};
int pero[] = {140, 139, 132, 131, 121, 126, 113, 112, 111, 110, 93, 91, 84, 82, 63, 62, 40, 43, 39, 38, 36, 31, 30, 22, 23, 26, 27, 15, 14};

/* プリセット定義 */
int *all[] = {smile, evil, cry1, me, patiri, suya, mu, nikori, cry2, u, tere, upset, wink, pero};
int *happy[] = {smile, patiri, suya, mu, nikori, u, tere, wink};
int *clever[] = {evil, pero};
int *sad[] = {cry1, me, cry2, upset};

/* プリセット要素数定義 */
int all_sizes[] = {38, 28, 32, 36, 36, 30, 30, 44, 40, 29, 30, 42, 37, 29};
int happy_sizes[] = {38, 36, 30, 30, 44, 29, 30, 37};
int clever_sizes[] = {28, 29};
int sad_sizes[] = {32, 36, 40, 42};

/* プリセット配列定義 */
int **presets[PRESETS] = {all, happy, clever, sad};
int *presets_sizelist[PRESETS] = {all_sizes, happy_sizes, clever_sizes, sad_sizes};
int presets_sizes[PRESETS] = {FACES, 8, 2, 4};

マクロ定義
#define FACES 14
#define PRESETS 4

まず最初にマクロで宣言しているFACESPRESETSは、登録されている表情の総数プリセットの総数です。上記のファイルだと、14個の表情と4個のプリセットが登録されているということですね。

表情配列定義
/* 表情配列定義 */
int smile[] = {143, 144, 151, 152, 138, 137, 134, 133, 123, 124, 114, 113, 110, 109, 95, 96, 103, 104, 71, 73, 78, 80, 41, 42, 43, 44, 45, 46, 47, 48, 38, 31, 22, 27, 16, 13, 4, 5};
int evil[] = {119, 120, 127, 128, 116, 115, 114, 113, 110, 109, 108, 107, 96, 97, 102, 103, 68, 66, 53, 51, 38, 31, 22, 23, 24, 25, 26, 27};
int cry1[] = {139, 138, 133, 132, 119, 120, 122, 125, 126, 128, 116, 115, 114, 113, 110, 109, 108, 107, 95, 96, 97, 102, 103, 93, 91, 71, 37, 36, 35, 34, 33, 32};
以下略

次に表情配列です。これは直列に繋げたNeoPixelLEDの何番目を光らせるかを定義した配列です。例えばint smile[]は下の画像でピンク色になっている数字の配列です。
smile.png

プリセット定義
/* プリセット定義 */
int *all[] = {smile, evil, cry1, me, patiri, suya, mu, nikori, cry2, u, tere, upset, wink, pero};
int *happy[] = {smile, patiri, suya, mu, nikori, u, tere, wink};
int *clever[] = {evil, pero};
int *sad[] = {cry1, me, cry2, upset};

次にプリセット定義です。プリセット定義は表情配列のポインタの配列になっています。(出たな、ポインタ)
例えばint *clever[]というプリセットは、int evil[]int pero[]という2つの表情のポインタを格納した配列、ということになります。

プリセット要素数定義
/* プリセット要素数定義 */
int all_sizes[] = {38, 28, 32, 36, 36, 30, 30, 44, 40, 29, 30, 42, 37, 29};
int happy_sizes[] = {38, 36, 30, 30, 44, 29, 30, 37};
int clever_sizes[] = {28, 29};
int sad_sizes[] = {32, 36, 40, 42};

さて次はプリセット要素数定義です。これはプリセット定義と対応する形で、各表情配列の要素数(=光るLEDの数)を格納している配列です。
どうしてこれが必要になるのイメージがつきにくいと思いますが、これは表情配列を読み出してLEDを光らせる際、表情配列の要素数分ループを回すために用いられます。C言語ではポインタから配列の要素数を取得することができないため、別途要素数の配列を保持しています。

プリセット配列定義
/* プリセット配列定義 */
int **presets[PRESETS] = {all, happy, clever, sad};
int *presets_sizelist[PRESETS] = {all_sizes, happy_sizes, clever_sizes, sad_sizes};
int presets_sizes[PRESETS] = {FACES, 8, 2, 4};

次にプリセット配列定義です。ここでは以下の3つの配列を定義しています。
- int **presets[PRESETS]
これはプリセット定義配列をまとめた配列、つまり表情配列のポインタの配列のポインタの配列となります。ついてこれてますか?
この配列を 選択中のプリセット番号選択中の表情番号 を用いて参照することで、選択中の表情配列のポインタを得ることができます。

  • int *presets_sizelist[PRESETS]
    これはプリセット要素数定義のポインタの配列です。この配列を 選択中のプリセット番号選択中の表情番号 を用いて参照することで、選択中の表情配列の要素数を得ることができます。

  • int presets_sizes[PRESETS]
    これは各プリセットの表情数の配列です。これはプリセット切り替え時にオーバーフロー/アンダーフローさせるために用います。

さて、ガーっと説明しましたが良くわからないと思います。ポインタといえばC言語初学者がぶつかる壁として有名であり、かくいう僕もなんとなくでしかポインタを理解していませんでした。今回の制作を通してあれこれ仕様を検討していくなかで 「もしかしてこれがポインタの出番か!?」 となり、初めてちゃんとポインタについて知れたような気がします。

最終的にこの構造で実装しましたがかなり複雑になってしまったので、よりよい実装方法の提案などがあればぜひお聞かせ願いたいところです。

GoogleAppsScript

表情設定ファイルのついての説明を行いましたが、見てのとおりかなりややこしい構造です。
というわけで、GoogleAppsScript通称GASを使って簡単に表情設定ファイルを生成できるようにします。

表情設定ファイル生成の流れ

表情の設定はGoogleスプレッドシートで1シート1表情で行います。セルの色で表情を作成し、表情名と所属プリセット名(任意)を入力します。
faces.png
表情設定の例

入力が完了したら 「生成開始」 ボタンを入力するとGAS関数が実行され、各シートの入力を集計し同シート内に結果を出力します。あとはこれをArduinoに書き込めば表情設定の更新完了です。簡単!
gen.png

コード

こちらはあまり具体的な説明はしません。ハードコーディングの箇所も多くあまりキレイなコードではないですがコードを貼っておくので、興味のある方は読んでみてください。
「生成開始」ボタンに割り当てているのはgen_facesfile関数です。

function gen_facesfile() {
  var sheets = get_sheets();
  var faces = get_faces(sheets);
  var presets = get_presets(sheets, faces);

  var today = new Date();

  var date =
    Utilities.formatDate(today, "Asia/Tokyo", "yyyy/MM/dd HH:mm") + " 生成";

  var result =
    "/* " +
    date +
    " */\n\n#define FACES " +
    String(faces.name.length) +
    "\n#define PRESETS " +
    String(presets.list.length) +
    "\n\n/* 表情配列定義 */\n" +
    faces.list.join("\n") +
    "\n" +
    "\n/* プリセット定義 */\n" +
    presets.list.join("\n") +
    "\n\n/* プリセット要素数定義 */\n" +
    presets.sizes.join("\n") +
    "\n" +
    "\n/* プリセット配列定義 */\n" +
    presets.other.join("\n") +
    "\n";

  write_result(result, date);
}

function get_sheets() {
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheets = spreadsheet.getSheets();

  return sheets;
}

function get_faces(sheets) {

  var faces_str = [];
  var faces_num = [];
  var faces_name = [];
  for (var p = 3; p < sheets.length - 1; p++) {
    var range = sheets[p].getRange("D2:W14");
    var vals = range.getValues();
    var colors = range.getBackgrounds();
    var name = sheets[p].getRange("I18").getValue();
    var face_val = [];
    for (var i = 0; i < 13; i++) {
      for (var j = 0; j < 20; j++) {
        if (colors[i][j] == "#ff00ff") {
          face_val.push(vals[i][j]);
        }
      }
    }
    var str = "int " + name + "[] = {" + face_val.join(", ") + "};";
    faces_str.push(str);
    faces_num.push(face_val.length);
    faces_name.push(name);
  }
  var faces = {};
  faces.num = faces_num;
  faces.list = faces_str;
  faces.name = faces_name;
  return faces;
}

function get_presets(sheets, faces) {

  var presets_names = [];
  var presets_num = [];

  for (var p = 3; p < sheets.length - 1; p++) {
    var range = sheets[p].getRange("I19:Q19");
    var preset = range.getValues()[0];

    for (var i = 0; i < preset.length; i++) {
      //プリセット名が初登場の場合インデックス追加
      if (preset[i]) {
        if (presets_names.indexOf(preset[i]) == -1) {
          presets_names.push(preset[i]);
          presets_num.push(1);
          //初登場でなければカウント追加
        } else {
          presets_num[presets_names.indexOf(preset[i])]++;
        }
      }
    }
  }

  var presets_list = [];
  var str = "int *all[] = {" + faces.name.join(", ") + "};";
  presets_list.push(str);

  var presets_sizes = [];
  str = "int all_sizes[] = {" + faces.num.join(", ") + "};";
  presets_sizes.push(str);

  for (var i = 0; i < presets_names.length; i++) {
    var names = [];
    var nums = [];

    for (var j = 0; j < sheets.length; j++) {
      var range = sheets[j].getRange("I19:Q19");
      var preset = range.getValues()[0];
      var name = sheets[j].getRange("I18").getValue();
      if (preset.indexOf(presets_names[i]) != -1) {
        names.push(name);
        nums.push(faces.num[faces.name.indexOf(name)]);
      }
    }

    str = "int *" + presets_names[i] + "[] = {" + names.join(", ") + "};";
    presets_list.push(str);

    str = "int " + presets_names[i] + "_sizes[] = {" + nums.join(", ") + "};";
    presets_sizes.push(str);
  }

  var presets_other = [];
  str = "int **presets[PRESETS] = {all, " + presets_names.join(", ") + "};";
  presets_other.push(str);
  var sizesstr = [];
  for (var i = 0; i < presets_names.length; i++) {
    sizesstr.push(presets_names[i] + "_sizes");
  }
  str =
    "int *presets_sizelist[PRESETS] = {all_sizes, " +
    sizesstr.join(", ") +
    "};";
  presets_other.push(str);

  str = "int presets_sizes[PRESETS] = {FACES, " + presets_num.join(", ") + "};";
  presets_other.push(str);

  var presets = {};
  presets.list = presets_list;
  presets.sizes = presets_sizes;
  presets.other = presets_other;

  return presets;
}

function write_result(str, date) {
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getSheets()[2];
  sheet.getRange("B10").setValue(str);

  sheet.getRange("C9").setValue(date);
  sheet.getRange("C9").setFontColor("red");

最後に

長々とお読みいただきありがとうございました。
というわけで、無事に璃奈ちゃんボードを完成させることができました。
出発点はキャラ愛でしたが、結果としてかなり多くのことを学ぶことができてとてもいい経験になりました。ものづくりは楽しいですね。
もしも本記事や動画が誰かの手助けになれたのであれば幸いです。
ついでに動画の方も再掲しておきます。ぜひご覧ください!
璃奈ちゃんボードの作り方【ラブライブ!虹ヶ咲学園スクールアイドル同好会】

かなりガーっと書いたので分かりにくい点も多々あると思いますので、ご指摘や質問などお待ちしております。それではここまでお読みいただきありがとうございました!

13
7
4

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
13
7