Developer

さくさく理解する Godot 入門(ただし2Dに限る)応用編 お絵かきパズル【第1回】
2021.12.08
Lv1

さくさく理解する Godot 入門(ただし2Dに限る)応用編 お絵かきパズル【第1回】

目次

  • はじめに
  • メイン画面
  • プロジェクト作成・ルートノード・背景
  • タイトルバー
  • グローバル定数・変数

はじめに

上図の様な、2画面から成る 15×15 サイズ固定のお絵かきパズルを Godot で実装する方法の解説を行う。 本稿で示す方法はあくまでもひとつの実装方法であり、必ずしも最適な実装方法ではないかもしれないが、 この方法で実装可能なのだと理解していただければ幸いだ。 もし本稿よりもよりよい実装方法があれば、筆者にメール等でご教授していただければ助かる。

お絵かきパズルのルールは、「ピクロス」「お絵かきロジック」「イラストロジック」と同一で、 左・上左部の手がかり数字部分で、その行・列に存在するキャンパスエリアの黒の数を示す。 本稿のアプリは、「さくさくロジック15」というアプリ名でフリーゲーム夢現 様で公開しているので、実際に試してみたい場合はそれを参照されたい。

これまで本連載では、画面をすべて作成→スクリプトの実装を解説 という順序だったが、 本シリーズでは機能ごとに画面を作成→スクリプトの実装を解説を行うものとする。
解説順序は、おおむね実際に筆者が機能を実装していった順序とする。 この方が、実装方法を理解しやすいのではないかと考えたからだ。

ソースコード全体は github にて、MIT ライセンスにて公開している。 ただし、問題の著作権は各問題作者にあるので、各問題を別プロジェクトで再利用する場合は、著作権者の承諾が必要なので注意されたい。

※「ピクロス」は任天堂株式会社の、「お絵かきロジック」は 株式会社世界文化ホールディングスの、 「イラストロジック」は 株式会社日本文芸社、株式会社コナミデジタルエンタテインメントの登録商標です。

メイン画面

本稿の最初に示したスクショを見るとわかるように、本アプリは問題一覧画面とパズルを作る・解くメイン画面の2画面からなる。 まずは、作って楽しいメイン画面から実装・解説してくことにする。 また、パズルを解くには問題が必要なので、先に問題を作るための機能を実装していく。

■プロジェクト作成・ルートノード・背景

なにはともあれ、下図のように新規プロジェクトを作成する。プロジェクト名は「GDLogic15」だ。
WebGL での実行を可能にするため、レンダラー:「OpenGLES 2.0」を選択している。

プロジェクトを作成後、設定 > プロジェクト設定 メニューを選び、 下図のように、画面サイズを 480×800 とする。

これは、横・縦比をおおむね 1:1.618 の黄金比にするためと、 セルの幅をキリのいい20ピクセルとし、手がかり数字最大8個なので、20*(15*8) = 460ピクセルで、 左右の空白を10ピクセルにするとちょうどいいと判断したからだ。 今考えると、左右の余白を20ピクセル*左右にし、横幅をキリのいい500ピクセルにしてもよかったかもしれない。 まあ、このへんは気分だ。;^p

次に、ルートノードとして Node2D を作成し、「MainScene」とリネームすると下図のようになる。

背景として ColorRect ノードを追加し、「BG」という名前にする。
インスペクタでサイズ・色を指定する。サイズは画面全体なので 480×800、色は #c0ffc0 とした。

■タイトルバー

次にタイトルバーとして ColorRect ノードを追加し、「TitleBar」という名前にする。
サイズは 480×50、色は #2e4f4f とした(下図参照)。

次に、タイトルバーに影を付けるのだが、タイトルバーの明度が低く、影が見えづらいので、 緑角丸矩形枠を StyleBoxFlat を使って描画している。

「TitleBar」ノードを選択し、右ボタンメニュー の 「スクリプトをアタッチ」、または「+巻物」のようなアイコンをクリックし、 スクリプトをアタッチする。スクリプトの中身は以下のように記述する。

extends ColorRect

const RADIUS = 5
const POSITION = Vector2(0, 0)

func _draw():   # 描画関数
    var style_box = StyleBoxFlat.new()      # 影、ボーダなどを描画するための矩形スタイルオブジェクト
    style_box.bg_color = color              # 矩形背景色
    style_box.border_color = Color.green
    style_box.set_border_width_all(2)
    style_box.set_corner_radius_all(RADIUS)
    style_box.shadow_offset = Vector2(4, 4)     # 影オフセット
    style_box.shadow_size = 8                   # 影(ぼかし)サイズ
    draw_style_box(style_box, Rect2(POSITION, self.rect_size))      # style_box に設定した矩形を描画

_draw() は自前でノードを描画するための関数だ。
ここでは StyleBoxFlat クラスを使って、角丸矩形枠を描画している。 使い方は簡単で、背景色や角丸矩形半径などのパラメータを設定し、draw_style_box() をコールするだけだ。 詳しくはリファレンスの StyleBoxFlat を参照していただきたい。

以上を実行すると下図のようになる。タイトルバーの影がいい感じに表示されたのではないだろうか?

タイトルバーに任意の画像を表示したい場合は ColorRect ではなく TextureRect を使用する。
筆者にとっては、画像を自分で作ったり、いい画像を探すよりスクリプトで記述した方が楽なのだ。 20×20等、別のサイズに変更したい場合も、画像を修正するよりもスクリプトを変更する方がはるかに楽だ。

次に、タイトルバーに「戻る」ボタンを設置する。
TextureButton を TitleBar の子ノードとしてノードツリーに追加し、「BackButton」にリネームする。 通常・押下時・ディセーブル時画像に、それぞれ白・黒・グレイの「←」画像を割り当てる(下図参照)。 なお、矢印画像は Google様の マテリアルアイコン をありがたく利用させていただいている。

戻るボタンが押されると、問題一覧画面に遷移するのだが、今はまだその画面を作成していないので、 イベントハンドラの設定・記述は問題一覧画面作成後に行うことにする。

同様に、Label を TitleBar の子ノードとして追加し、タイトルラベルをとりあえず設置しておく(下図参照)。

以上で、背景・タイトルバーの実装は終わりだ。ここまでを実行すると下図のようになる。

■グローバル定数・変数

GDScript には何故かグローバル定数・変数がないのだが、シーンをまたいでデータを保持したい場合などに困るので、 スクリプトを自動ロード・シングルトンにしておくことで、実質的にグローバル定数・変数を利用することができる。 その手順は以下のとおりだ。

新規シーンとして「Global」を作成する。クラス名は「Node2D」で「Global」にリネームする。

Global にスクリプトをアタッチし、Global.gd というファイル名で保存しておく。

設定 > プロジェクト設定… メニューで「自動読み込み」タブを選び、パス:部分で Global.gd を選び、 最右の「追加」ボタンを押す。そうすると下図のように Global.gd が各シーンで自動読み込みされるようになる。
「シングルトン」のところが有効になっているので、自動読み込みされる Global.gd はシングルトンとなり、 グローバル定数・変数として機能するというわけだ。

Global.gd の内容は、とりあえず下記のように、画面・盤面サイズなどの定数を定義しておく。 後で必要に応じてグローバル変数なども追加していく。

const SCREEN_WIDTH = 500.0      # 画面横サイズ
const SCREEN_HEIGHT = 800.0     # 画面縦サイズ
const BOARD_WIDTH = 460.0           # 盤面横サイズ
const BOARD_HEIGHT = BOARD_WIDTH    # 盤面縦サイズ
const LR_SPC = (SCREEN_WIDTH - BOARD_WIDTH) / 2

const N_CLUES_CELL_HORZ = 8     # 手がかり数字 セル数
const N_IMG_CELL_HORZ = 15      # 画像 セル数
const N_TOTAL_CELL_HORZ = N_CLUES_CELL_HORZ + N_IMG_CELL_HORZ
const N_CLUES_CELL_VERT = 8     # 手がかり数字 セル数
const N_IMG_CELL_VERT = 15      # 画像 セル数
const N_TOTAL_CELL_VERT = N_CLUES_CELL_VERT + N_IMG_CELL_VERT
const CELL_WIDTH = BOARD_WIDTH / N_TOTAL_CELL_HORZ      # セル幅
const CLUES_WIDTH = CELL_WIDTH * N_CLUES_CELL_HORZ      # 手がかり数字部分幅
const IMG_AREA_WIDTH = CELL_WIDTH * N_IMG_CELL_HORZ     # キャンパスエリアサイズ
const IMAGE_ORG = Vector2(CELL_WIDTH*(N_CLUES_CELL_HORZ), CELL_WIDTH*(N_CLUES_CELL_VERT)+1)

なお、Global.gd で定義された定数・変数は「Global.識別子」で参照することができる。
が、個人的には、このままではちょっと長いと考えているので、各スクリプトの最初のあたりで「var g = Global」と定義し、 「g.識別子」で定数・変数を参照するようにしている。