1. voidProc

    No comment

    voidProc
Changes in body
Source | HTML | Preview
@@ -1,467 +1,469 @@
![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) クラスか、または描き換えが可能な [DynamicTexture](https://github.com/Reputeless/Siv3D-Reference/blob/master/Programming%20Guide/Headers/Siv3D/DynamicTexture.hpp) を使用します。今回は頻繁にテクスチャを書き換える事になりそうので、 DynamicTexture を使うことにします。
-以下のようにして、既存の画像からテクスチャを生成します。
: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) / [スケッチ](https://github.com/Siv3D/Reference-JP/wiki/%E3%82%B9%E3%82%B1%E3%83%83%E3%83%81)
+以下のようにして、既存の画像からテクスチャを生成します。
+
```cpp
DynamicTexture 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);
// ドット絵表示用のテクスチャ
DynamicTexture 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` 変数を描き換えましょう。テクスチャに画像を描き込むには、`DynamicTexture::fill()` 関数を使用します。
```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.fill(image);
}
else if (dot.rightClicked)
{
image[i][j] = Palette::White;
texture.fill(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);
// ドット絵表示用のテクスチャ
DynamicTexture 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.fill(image);
}
else if (dot.rightClicked)
{
image[i][j] = Palette::White;
texture.fill(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 つの変数を用意し、現在選択している色をここに格納するようにします。
+その前に、現在選択している色を表示することにします。`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.fill(image);
}
else if (dot.rightClicked)
{
image[i][j] = rightColor;
texture.fill(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);
// ドット絵表示用のテクスチャ
DynamicTexture 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.fill(image);
}
else if (dot.rightClicked)
{
image[i][j] = rightColor;
texture.fill(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.