ぬーぶのメモ帳

フリーゲームエンジン「Godot」でプログラム経験のない素人の備忘録 最近は色々な動作を考えて試すのが好き。

【Godot】マインスイーパーを作ってみた3

マス目に数字タイルを敷き詰めた前回
前: 【Godot】マインスイーパーを作ってみた2 - ぬーぶのメモ帳




前回マス目用の配列を作成、ループ処理を利用しマス目に Vector2 の位置情報を覚えさせました。
試しに適当なマス目を指定して Vector2 が保存されてるか確認してみましょう。

func _ready() -> void:

#	for x in cell_x:
#		for y in cell_y:
#			var cell_position := Vector2(x, y)
#			map_cell[x].append(cell_position)
#			map_flag[x].append(flag)

	# カーソルの位置を マス目配列とマス目のサイズでかけて場所を指定
	cursor.position =  map_cell[3][8] * cell_size

実行する前に、カーソルの Offset から Centered のチェックが外れてるか確認してください。

では確認してみましょう。

カーソルが指定したマスに配置されているので、配列内にちゃんとデータがあることが確認できました。

爆弾を配置

まずは爆弾の個数を表す変数を作ります。
export でインスペクタータブで爆弾の数を変えやすくします。

# 爆弾の数
export var mine := 10


次に爆弾をマスに追加する関数を作ろうと思います。
コード

func set_mine()-> void:
	randomize()
	for i in mine:
		# ランダムなタイルを選択
		var rand_tile = map_cell[randi() % cell_x][randi() % cell_y]
		
		# 連動しているタイルマップのタイル番号を取得
		var get_tile = num_tile.get_cellv(rand_tile)
		# 選択したタイルの番号が 9 つまり爆弾だった場合 while 内部の処理を実行
		# もう一度ランダムなマスを選択して while で再度比較する 爆弾以外のマスが出るまで内部の処理を続ける
		while get_tile == 9:
			randomize()
			rand_tile = map_cell[randi() % cell_x][randi() % cell_y]
			get_tile = num_tile.get_cellv(rand_tile)
		
		# 爆弾でないマスが確定したので爆弾を配置
		num_tile.set_cellv(rand_tile, 9)

上から順番に まず for文 で mine の数だけ繰り返し処理を行います。
次に map_cell で randi() を使いランダムなマスの Vector2座標 を取得します。


Vector2座標を取得したら連動する num_tileノード のタイル番号を get_cellv(rand_tile) コマンドで入手 While文 で比較を行います。


 While文 は条件を満たす限り内部に書かれたコードを繰り返し実行します。
 なので While から抜けない処理を行うと無限ループでゲームがフリーズしてしまう、使う時すこしドキドキするコマンドです。


話を戻すと 取得した num_tileノード のタイル番号が 9(爆弾) だった場合、再度ランダムにタイルを取得、比較して爆弾でないタイルになるまでランダムなタイルを取得します。


最後にタイルが確定してるで set_cellv() で爆弾を重複せずに設置することができます。



randomize() … 乱数をシャッフルしてくれるコマンド、これを使わないと再現性のある乱数で固定されてしまう。


randi() … ランダムな数値を int型 (整数) で返します。
コマンド横に を付けて、整数の要素を指定することでランダム幅を設定することもできます。

 randi() % 設定した整数 -1

今回の場合は実質 cell_x -1 なので0~9の整数がランダムで決定されます。
詳しくは Godot の randi の項目を参照してください。


set_cellv() … タイルマップのコマンド、指定されたタイルマップの Vector2 の座標に、指定の番号のタイルを貼り付ける

 タイルマップ名.set_cellv(タイルマップVector2座標, タイル番号) 

コードを例にすると map_cell[0~9][0~9] の位置に タイル番号9、爆弾のタイルを配置するコマンドを実行します。


get_cellv() … タイルマップのコマンド、指定したタイルマップの Vector2 座標のタイル番号を取得、番号をint型 (整数) で返します。

 タイルマップ名.get_cellv(タイルマップVector2座標) 



 mine_set() の関数を _ready() 関数の一番下に配置してください

func _ready() -> void:
#	for x in cell_x:
#		for y in cell_y:
#			var cell_position := Vector2(x, y)
#			map_cell[x].append(cell_position)
#			map_flag[x].append(flag)
			

	set_mine()

 

透明なタイルをしきつめる

ここでコード実行と行きたいのですが先に num_tile のマップタイルに タイル番号0 の 透明なタイル を敷きつめてください。

Ctrl + Shift キーでタイルを指定した四角の範囲で塗りつぶすことができます。


タイルを設置できていればタイル名がちゃんと表示されています。

透明なタイルを設置した理由は 先ほど使用したコマンドの get_cellv() のためです。
これは指定したタイルマップ座標のタイル番号を取得します、そしてタイルが設置されていない場合は -1を返すので想定した挙動をできません。

またこの後の爆弾周りのタイルに数字を付与する時にも0番タイルを利用するので0番タイルを num_tile に敷きつめて下さい。

何度かコードを実行してもちゃんと爆弾が10個あることが確認できると思います。

爆弾の周りにヒントステータスを追加

爆弾の設置ができたので爆弾の周りのタイルに数字を書き込む必要が出てきます。
参考にした動画やコードの処理からヒントを得て、爆弾のマスを取得した後に周りのタイルの番号を+1できれば表現できるのではと考えました。

コード

func num_plus()-> void:
	for x in cell_x:
		for y in cell_y:
			# 全マスを順番に取得
			var get_tile = num_tile.get_cellv(map_cell[x][y])
			# 取得したマスが爆弾だった場合処理をする
			if get_tile == 9:
				# 爆弾タイルのマップ位置を取得
				var current_tile = num_tile.to_global(map_cell[x][y])
				# 爆弾タイルの周辺の座標を取得
				var offset = [
					Vector2(current_tile.x -1, current_tile.y -1),
					Vector2(current_tile.x, current_tile.y -1),
					Vector2(current_tile.x +1, current_tile.y -1),
					Vector2(current_tile.x -1, current_tile.y),
					Vector2(current_tile.x +1, current_tile.y),
					Vector2(current_tile.x -1, current_tile.y +1),
					Vector2(current_tile.x, current_tile.y +1),
					Vector2(current_tile.x +1, current_tile.y +1),
					]
				# 座標の配列を for文 で処理する
				for pos in offset:
					# pos に Vector2 の情報が入っている
					# num_tile.get_cellv(pos) をそのまま使うとごちゃごちゃするので 変数を作ってすっきりさせる
					var vec = num_tile.get_cellv(pos)
					# 爆弾の周辺に別の爆弾があった場合は上書きしないように pass
					# 爆弾でない場合は 周辺タイルのステータス番号を +1する
					if vec == 9:
						pass
					else:
						num_tile.set_cellv(pos, vec +1)
					yield(get_tree().create_timer(0.2), "timeout")


まず for文 で またXY軸のマスを1つずつ取得していきます。
取得したマスを比較、爆弾だった場合 配列 offset 内を処理。


この offset は取得した爆弾の周囲の座標を格納しています。
取得した爆弾の座標は繰り返し処理のたびに取得しなおすので、同じ配列を使いまわしても問題がないのです。


offset 内の Vector2 を視覚的にした場合

for文 に offset の vector2 情報を順番に処理させる。


 for文 を使っていて気付いたのは、数字以外に今回のようにデータ、果てはノードを for文にぶち込んで処理させることが可能でした。
 ノードのコマンドなども実行できかなり柔軟なコマンドだなあと驚きました、他の言語でもこうなのだろうか?


では if文 で爆弾周辺のマスのタイルを比較します、仮に爆弾の周辺に別の爆弾が存在する場合はパス。
それ以外であれば爆弾周辺のマスのタイル番号をそれぞれ+1しながら set_cellv() でマスを変化させます。


for文 に offset の vector2 情報を順番に処理させる。


例のごとく段階的に処理をさせてみました、端からタイルを取得し爆弾マスに当たると周辺のマスを変化させています。
さらに爆弾が密集していれば1のタイルが2に、2のタイルが3に・・・とうまく処理できたと思います。



現状のコードをすべて合わせるとこうなります。

#extends Node2D

onready var num_tile: TileMap = $num_tile
onready var cover_tile: TileMap = $cover_tile
onready var cursor: Sprite = $Cursor


const cell_size := 32

var cell_x = 10
var cell_y = 10

# 爆弾の数
export var mine := 10

var map_cell := [
	[],
	[],
	[],
	[],
	[],
	[],
	[],
	[],
	[],
	[],
]


func _ready() -> void:
	for x in cell_x:
		
		for y in cell_y:
			var cell_position := Vector2(x, y)
			map_cell[x].append(cell_position)

	set_mine()
	num_plus()


func set_mine()-> void:
	randomize()
	for i in mine:
		var rand_tile = map_cell[randi() % cell_x][randi() % cell_y]
		
		var get_tile = num_tile.get_cellv(rand_tile)

		while get_tile == 9:
			randomize()
			rand_tile = map_cell[randi() % cell_x][randi() % cell_y]
			get_tile = num_tile.get_cellv(rand_tile)
		
		num_tile.set_cellv(rand_tile, 9)


func num_plus()-> void:
	for x in cell_x:
		for y in cell_y:

			var get_tile = num_tile.get_cellv(map_cell[x][y])

			if get_tile == 9:
				var current_tile = num_tile.to_global(map_cell[x][y])
				var offset = [
					Vector2(current_tile.x -1, current_tile.y -1),
					Vector2(current_tile.x, current_tile.y -1),
					Vector2(current_tile.x +1, current_tile.y -1),
					Vector2(current_tile.x -1, current_tile.y),
					Vector2(current_tile.x +1, current_tile.y),
					Vector2(current_tile.x -1, current_tile.y +1),
					Vector2(current_tile.x, current_tile.y +1),
					Vector2(current_tile.x +1, current_tile.y +1),
					]

				for pos in offset:
					# pos に Vector2 の情報が入っている
					var vec = num_tile.get_cellv(pos)

					if vec == 9:
						pass
					else:
						num_tile.set_cellv(pos, vec +1)
					# 後で消す
					yield(get_tree().create_timer(0.2), "timeout")


どうでしょうか、見た目はマインスイーパーになってきたと思います。
次回はカーソルを動かして指定したマスは開くようにしたいです。

次:【Godot】マインスイーパーを作ってみた4 - ぬーぶのメモ帳