1. voidProc

    Posted

    voidProc
Changes in title
+Siv3D でドット絵エディタを作ろう
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,466 @@
+![siv3d_dotedit.gif](https://qiita-image-store.s3.amazonaws.com/0/35599/1d0bce99-680b-3760-4df2-bf3cef979625.gif)
+
+# はじめに
+
+[Siv3D](http://play-siv3d.hateblo.jp/) を使うと、C++ でゲームやツールを簡単に作成することができます。Siv3D の便利なクラス群を活用して、ドット絵エディタを作成してみましょう。
+
+今回作成するドット絵エディタでは、こんな機能を実装してみます。
+
+* ドット絵を表示する
+* ドット絵のグリッドを表示する
+* クリックしたドットの色を変える
+* ドット絵を保存する
+* パレットから色を選ぶ
+
+# ドット絵を表示しよう、その前に
+
+まず最初に、エディタの外観を大まかに決めます。幅 16 × 高さ 16 のドット絵を、1 ドットあたり 32 ピクセルのサイズに拡大して表示することにします。
+
+あとはウィンドウの大きさ、背景色を自分好みに設定します。
+:information_source: [ウィンドウと背景](https://github.com/Siv3D/Reference-JP/wiki/%E3%82%A6%E3%82%A3%E3%83%B3%E3%83%89%E3%82%A6%E3%81%A8%E8%83%8C%E6%99%AF)
+
+```cpp:Main.cpp
+#include <Siv3D.hpp>
+
+void Main()
+{
+ Window::Resize(640, 530);
+
+ Graphics::SetBackground(Palette::Peachpuff);
+
+ // 1マスの大きさ
+ const int GridSize = 32;
+
+ // 横方向のマスの数
+ const int W = 16;
+
+ // 縦方向のマスの数
+ const int H = 16;
+
+ while (System::Update())
+ {
+ }
+}
+```
+
+
+
+# ドット絵を表示しよう
+
+Siv3D には、画像を編集するための [Image](https://github.com/Reputeless/Siv3D-Reference/blob/master/Programming%20Guide/Headers/Siv3D/Image.hpp) クラスがあります。今回はこのクラスを使ってドット絵を編集することにしましょう。
+:information_source: [画像編集](https://github.com/Siv3D/Reference-JP/wiki/%E7%94%BB%E5%83%8F%E7%B7%A8%E9%9B%86)
+
+幅 16 × 高さ 16 で、中身が真っ白の画像を作成します。
+
+```cpp
+ Image image(W, H, Palette::White);
+```
+
+ただ、Image クラスは画像の編集はできても表示はできません。画像の表示には [Texture](https://github.com/Reputeless/Siv3D-Reference/blob/master/Programming%20Guide/Headers/Siv3D/Texture.hpp) クラスを利用します。以下のようにして、既存の画像からテクスチャを生成します。
+:information_source: [テクスチャを描く](https://github.com/Siv3D/Reference-JP/wiki/%E3%83%86%E3%82%AF%E3%82%B9%E3%83%81%E3%83%A3%E3%82%92%E6%8F%8F%E3%81%8F)
+
+```cpp
+ Texture texture(image);
+```
+
+テクスチャの表示には `Texture::draw()` 関数を使用します。また、今回はドット絵を `GridSize` 倍に拡大して表示するので、`draw()` の前に `Texture::scale()` 関数で拡大しましょう。
+以下のようにして、左から 10px、上から 10px の位置に、拡大されたテクスチャを表示します。
+
+```cpp
+ texture.scale(GridSize).draw(10, 10);
+```
+
+:warning: テクスチャを拡大表示すると、ぼやけた感じになると思います。これをくっきりさせたい時は、[サンプラーステートの変更](https://github.com/Siv3D/Reference-JP/wiki/2D%E3%81%AE%E3%83%AC%E3%83%B3%E3%83%80%E3%83%BC%E3%82%B9%E3%83%86%E3%83%BC%E3%83%88#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%A9%E3%83%BC%E3%82%B9%E3%83%86%E3%83%BC%E3%83%88%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AA%E3%83%B3%E3%82%B0%E3%83%A2%E3%83%BC%E3%83%89) をします。
+
+```cpp
+ Graphics2D::SetSamplerState(SamplerState::WrapPoint);
+```
+
+ここまでをまとめると、ソースコードは以下のようになります。真っ白な画像が表示されたでしょうか。`Palette::White` のところを編集して、画像の色が変わることを確認してみましょう。
+
+```cpp
+#include <Siv3D.hpp>
+
+void Main()
+{
+ Window::Resize(640, 530);
+
+ Graphics::SetBackground(Palette::Peachpuff);
+
+ Graphics2D::SetSamplerState(SamplerState::WrapPoint);
+
+ // 1マスの大きさ
+ const int GridSize = 32;
+
+ // 横方向のマスの数
+ const int W = 16;
+
+ // 縦方向のマスの数
+ const int H = 16;
+
+ // ドット絵
+ Image image(W, H, Palette::White);
+
+ // ドット絵表示用のテクスチャ
+ Texture texture(image);
+
+ while (System::Update())
+ {
+ // ドット絵を描画
+ texture.scale(GridSize).draw(10, 10);
+ }
+}
+```
+
+![00.png](https://qiita-image-store.s3.amazonaws.com/0/35599/b65b1a4b-aff2-a5b9-8961-4754f5f256cb.png)
+
+# グリッドを表示しよう
+
+次に、ドット絵エディタらしくグリッドを表示してみましょう。大きさが `GridSize` の矩形(Rect = [Rectangle](https://github.com/Reputeless/Siv3D-Reference/blob/master/Programming%20Guide/Headers/Siv3D/Rectangle.hpp) クラス)を敷き詰めればよさそうです。
+`Rectangle::drawFrame()` 関数を使って、灰色の枠を描いています。
+:information_source: [長方形の枠を描く](https://github.com/Siv3D/Reference-JP/wiki/%E5%9B%B3%E5%BD%A2%E3%82%92%E6%8F%8F%E3%81%8F#%E9%95%B7%E6%96%B9%E5%BD%A2%E3%81%AE%E6%9E%A0%E3%82%92%E6%8F%8F%E3%81%8F)
+
+```cpp
+ for (int i : step(H))
+ {
+ for (int j : step(W))
+ {
+ // グリッドを描画
+ Rect dot(10 + j*GridSize, 10 + i*GridSize, GridSize);
+ dot.drawFrame(1.0, 1.0, Palette::Gray);
+ }
+ }
+```
+
+![01.png](https://qiita-image-store.s3.amazonaws.com/0/35599/a0484b5e-dbe1-6165-6fec-b0e56875635a.png)
+
+# クリックしたドットの色を変えてみよう
+
+Rectangle のような図形のクラスには、クリックされたかどうかを知るためのプロパティ `Rectangle::leftClicked`、`Rectangle::rightClicked` がありますので、それを利用します。
+:information_source: [図形とクリック](https://github.com/Siv3D/Reference-JP/wiki/%E5%9B%B3%E5%BD%A2%E3%81%AE%E5%BD%93%E3%81%9F%E3%82%8A%E5%88%A4%E5%AE%9A#%E5%9B%B3%E5%BD%A2%E3%81%A8%E3%82%AF%E3%83%AA%E3%83%83%E3%82%AF)
+
+クリックの判定には、グリッド表示に使った Rect を流用すると良さそうですね。左クリックされたドットを黒に、右クリックされたドットを白にしてみましょう。
+
+画像のピクセルの色を変更するときは、`Image::operator[]` を使用します。
+:information_source: [画像のピクセルに色を書き込む](https://github.com/Siv3D/Reference-JP/wiki/%E7%94%BB%E5%83%8F%E7%B7%A8%E9%9B%86#%E7%94%BB%E5%83%8F%E3%81%AE%E3%83%94%E3%82%AF%E3%82%BB%E3%83%AB%E3%81%AB%E8%89%B2%E3%82%92%E6%9B%B8%E3%81%8D%E8%BE%BC%E3%82%80)
+
+:warning: 画像を編集したあとは、忘れずに表示用の `texture` 変数を作り直しましょう。
+
+```cpp
+ for (int i : step(H))
+ {
+ for (int j : step(W))
+ {
+ // グリッドを描画
+ Rect dot(10 + j*GridSize, 10 + i*GridSize, GridSize);
+ dot.drawFrame(1.0, 1.0, Palette::Gray);
+
+ // ドットがクリックされていたら、ドットの色を変更する
+ if (dot.leftClicked)
+ {
+ image[i][j] = Palette::Black;
+ texture = Texture(image);
+ }
+ else if (dot.rightClicked)
+ {
+ image[i][j] = Palette::White;
+ texture = Texture(image);
+ }
+ }
+ }
+```
+
+![bw.gif](https://qiita-image-store.s3.amazonaws.com/0/35599/358c6d3f-5140-94cb-29dd-dbc0ecadd85c.gif)
+
+# ドット絵を保存してみよう
+
+ではここで画像を保存できるようにしてみましょう。「保存」ボタンを表示しておいて、クリックされたら「名前をつけて保存」ダイアログを出し、指定されたファイル名で保存するようにします。
+
+まずは「保存」ボタンを表示します。ボタン本体の表示には Rect を、「保存」の文字の表示には [Font](https://github.com/Reputeless/Siv3D-Reference/blob/master/Programming%20Guide/Headers/Siv3D/Font.hpp) クラスを使います。
+
+下記のようにして、画面左上のスペースにボタンを描きます。`Font::drawCenter()` 関数を使うと、指定した座標が中心になるように文字を描くことができるので、矩形の中心 `Rectangle::center` を指定して、ボタンの中央に文字を表示します。
+:information_source: [テキストを描く#中央に表示する](https://github.com/Siv3D/Reference-JP/wiki/%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%82%92%E6%8F%8F%E3%81%8F#%E4%B8%AD%E5%A4%AE%E3%81%AB%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B)
+
+```cpp
+ //ボタンのラベル用フォント
+ Font font(12);
+
+ // ・・・途中省略・・・
+
+ // 保存ボタン
+ Rect saveButton(531, 10, 100, 30);
+ saveButton.draw(Palette::Whitesmoke).drawFrame(1.0, 1.0, Palette::Black);
+ font(L"保存").drawCenter(saveButton.center, Palette::Black);
+```
+
+このボタンがクリックされた時に、「名前をつけて保存」ダイアログを出し、指定されたファイル名で画像を保存するには以下のようにします。
+:information_source: [ダイアログ#名前を付けて画像を保存する](https://github.com/Siv3D/Reference-JP/wiki/%E3%83%80%E3%82%A4%E3%82%A2%E3%83%AD%E3%82%B0#%E5%90%8D%E5%89%8D%E3%82%92%E4%BB%98%E3%81%91%E3%81%A6%E7%94%BB%E5%83%8F%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B)
+
+
+```cpp
+ if (saveButton.leftClicked)
+ {
+ const auto save = Dialog::GetSaveImage();
+ if (save)
+ {
+ image.save(save.value());
+ }
+ }
+```
+
+ここまでをまとめると、ソースコードは以下のようになります。
+
+```cpp
+#include <Siv3D.hpp>
+
+void Main()
+{
+ Window::Resize(640, 530);
+
+ Graphics::SetBackground(Palette::Peachpuff);
+
+ Graphics2D::SetSamplerState(SamplerState::WrapPoint);
+
+ // 1マスの大きさ
+ const int GridSize = 32;
+
+ // 横方向のマスの数
+ const int W = 16;
+
+ // 縦方向のマスの数
+ const int H = 16;
+
+ // ドット絵
+ Image image(W, H, Palette::White);
+
+ // ドット絵表示用のテクスチャ
+ Texture texture(image);
+
+ // ボタンのラベル用フォント
+ Font font(12);
+
+ while (System::Update())
+ {
+ // ドット絵を描画
+ texture.scale(GridSize).draw(10, 10);
+
+ for (int i : step(H))
+ {
+ for (int j : step(W))
+ {
+ // グリッドを描画
+ Rect dot(10 + j*GridSize, 10 + i*GridSize, GridSize);
+ dot.drawFrame(1.0, 1.0, Palette::Gray);
+
+ // ドットがクリックされていたら、ドットの色を変更する
+ if (dot.leftClicked)
+ {
+ image[i][j] = Palette::Black;
+ texture = Texture(image);
+ }
+ else if (dot.rightClicked)
+ {
+ image[i][j] = Palette::White;
+ texture = Texture(image);
+ }
+ }
+ }
+
+ // 保存ボタン
+ Rect saveButton(531, 10, 100, 30);
+ saveButton.draw(Palette::Whitesmoke).drawFrame(1.0, 1.0, Palette::Black);
+ font(L"保存").drawCenter(saveButton.center, Palette::Black);
+
+ if (saveButton.leftClicked)
+ {
+ const auto save = Dialog::GetSaveImage();
+ if (save)
+ {
+ image.save(save.value());
+ }
+ }
+
+ }
+}
+```
+
+# パレットから色を選べるようにしてみよう
+
+さて、この段階では使える色が白と黒の 2 つしかありません。右側にパレットを表示して、色を選択できるようにしてみましょう。
+
+その前に、現在選択している色を表示することにします。
+`leftColor`、`rightColor` という 2 つの変数を用意し、現在選択している色をここに格納するようにします。
+
+```cpp
+ Color leftColor = Palette::Black;
+ Color rightColor = Palette::White;
+
+ // ・・・途中省略・・・
+
+ // 現在選択中の色
+ Rect(531, 470, 50, 50).draw(leftColor).drawFrame(1.0, 1.0, Palette::Black);
+ Rect(581, 470, 50, 50).draw(rightColor).drawFrame(1.0, 1.0, Palette::Black);
+```
+
+そしてグリッドがクリックされたとき、これらの変数を使うようにします。
+
+```cpp
+ if (dot.leftClicked)
+ {
+ image[i][j] = leftColor;
+ texture = Texture(image);
+ }
+ else if (dot.rightClicked)
+ {
+ image[i][j] = rightColor;
+ texture = Texture(image);
+ }
+```
+
+![02.png](https://qiita-image-store.s3.amazonaws.com/0/35599/a7555299-b293-d04e-4ace-992a1a7a486e.png)
+
+ではパレットを作ります。[Array](https://github.com/Reputeless/Siv3D-Reference/blob/master/Programming%20Guide/Headers/Siv3D/Array.hpp) に色をいくつか入れて、右側のスペースに表示し、それらがクリックされたら選択中の色(`leftColor`、`rightColor`)を変更します。
+
+```cpp
+ Array<Color> palette{ Palette::White, Palette::Black, Palette::Red, Palette::Blue, Palette::Green };
+
+ // ・・・途中省略・・・
+
+ // パレット
+ for (int i = 0; i < palette.size(); i++)
+ {
+ // パレットのボタン
+ Rect palButton(531, 100 + i * 30, 100, 30);
+ palButton.draw(palette[i]).drawFrame(1.0, 1.0, Palette::Black);
+
+ // パレットのボタンがクリックされたら、現在の色を変える
+ if (palButton.leftClicked)
+ {
+ leftColor = palette[i];
+ }
+ else if (palButton.rightClicked)
+ {
+ rightColor = palette[i];
+ }
+ }
+```
+
+![03.png](https://qiita-image-store.s3.amazonaws.com/0/35599/c9a09d84-58ca-9203-742f-5f7330933d33.png)
+
+
+# 完成
+
+今回の記事ではここでひとまず完成とします。最終的にほぼ 100 行に収まりましたね。マウスやダイアログを使うような複雑な処理がありましたが、Siv3D の便利なクラス群のおかげで、短いソースコードで記述することができました。
+
+```cpp
+#include <Siv3D.hpp>
+
+void Main()
+{
+ Window::Resize(640, 530);
+
+ Graphics::SetBackground(Palette::Peachpuff);
+
+ Graphics2D::SetSamplerState(SamplerState::WrapPoint);
+
+ // 1マスの大きさ
+ const int GridSize = 32;
+
+ // 横方向のマスの数
+ const int W = 16;
+
+ // 縦方向のマスの数
+ const int H = 16;
+
+ // ドット絵
+ Image image(W, H, Palette::White);
+
+ // ドット絵表示用のテクスチャ
+ Texture texture(image);
+
+ // ボタンのラベル用フォント
+ Font font(12);
+
+ // 選択中の色
+ Color leftColor = Palette::Black;
+ Color rightColor = Palette::White;
+
+ // パレット
+ Array<Color> palette{ Palette::White, Palette::Black, Palette::Red, Palette::Blue, Palette::Green };
+
+ while (System::Update())
+ {
+ // ドット絵を描画
+ texture.scale(GridSize).draw(10, 10);
+
+ for (int i : step(H))
+ {
+ for (int j : step(W))
+ {
+ // グリッドを描画
+ Rect dot(10 + j*GridSize, 10 + i*GridSize, GridSize);
+ dot.drawFrame(1.0, 1.0, Palette::Gray);
+
+ // ドットがクリックされていたら、ドットの色を変更する
+ if (dot.leftClicked)
+ {
+ image[i][j] = leftColor;
+ texture = Texture(image);
+ }
+ else if (dot.rightClicked)
+ {
+ image[i][j] = rightColor;
+ texture = Texture(image);
+ }
+ }
+ }
+
+ // 保存ボタン
+ Rect saveButton(531, 10, 100, 30);
+ saveButton.draw(Palette::Whitesmoke).drawFrame(1.0, 1.0, Palette::Black);
+ font(L"保存").drawCenter(saveButton.center, Palette::Black);
+
+ if (saveButton.leftClicked)
+ {
+ const auto save = Dialog::GetSaveImage();
+ if (save)
+ {
+ image.save(save.value());
+ }
+ }
+
+ // 現在選択中の色
+ Rect(531, 470, 50, 50).draw(leftColor).drawFrame(1.0, 1.0, Palette::Black);
+ Rect(581, 470, 50, 50).draw(rightColor).drawFrame(1.0, 1.0, Palette::Black);
+
+ // パレット
+ for (int i = 0; i < palette.size(); i++)
+ {
+ // パレットのボタン
+ Rect palButton(531, 100 + i * 30, 100, 30);
+ palButton.draw(palette[i]).drawFrame(1.0, 1.0, Palette::Black);
+
+ // パレットのボタンがクリックされたら、現在の色を変える
+ if (palButton.leftClicked)
+ {
+ leftColor = palette[i];
+ }
+ else if (palButton.rightClicked)
+ {
+ rightColor = palette[i];
+ }
+ }
+ }
+}
+```
+
+# 今後の課題
+
+このプログラムにさらに機能を追加するとしたら、どんな機能が考えられるでしょうか。
+
+* 画像を読み込めるようにする
+* マウスが乗っているグリッドを強調する
+* グリッドのサイズを変えられるようにする
+* 画像のサイズを変えられるようにする
+* マウスをドラッグして連続して線を引けるようにする
+* etc.