ぬーぶのメモ帳

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

【Godot4】懲りずにマインスイーパーを作る3

爆弾を設置
前回: 【Godot4】懲りずにマインスイーパーを作る2 - ぬーぶのメモ帳




前回はステージの基礎部分を作成しました。

今回は爆弾の配置、爆弾の周りにヒントの配置まで行きたいと思います、よろしければお付き合いください。


全体コード

■ main.gd

extends Node2D

# ナンバー設置、パネルタッチの際に使う
const OFF_SET := [
	Vector2i(-1, -1), Vector2i(0, -1), Vector2i(1, -1), 
	Vector2i(-1, 0), Vector2i(1, 0), 
	Vector2i(-1, 1), Vector2i(0, 1), Vector2i(1, 1), 
]

@onready var tile_map: TileMap = $TileMap
# 大量に生成される壁のコンテナ
@onready var wall_container: Node2D = $wall_container

# ステージの構成データ
@export var cell_size :int = 64
@export var stage_scale :Vector2i = Vector2i(10, 10)

# 盤面のデータを格納
var stage_array := []

# 白と黒のタイルを格納する配列
var floor_array := []
var wall_array := []

var bomb_array := []
var bomb_count := 0

func _ready() -> void:
	get_stage()
	init_stage_array()
	set_stage()

	set_bomb()

func get_stage()-> void:
	# タイルマップの白黒タイルを取得
	floor_array = []
	wall_array = []
	wall_array = tile_map.get_used_cells_by_id(0,0,Vector2i.ZERO)
	floor_array = tile_map.get_used_cells_by_id(0,0,Vector2i(1, 0))

func init_stage_array()-> void:
	# 最初に全配列を初期化する
	for y in stage_scale.y:
		var y_array := []
		for x in stage_scale.x:
			y_array.append({"type": 0, "dig": false, "flag": false})
		stage_array.append(y_array)
	# 初期化した後、壁になる配列の値を変更
	for wall in wall_array:
		stage_array[wall.y][wall.x] = {"type": 99}
		
	#print(stage_array)

func set_stage()-> void:
	for i in wall_array: # 壁の配列に壁のグラフィックを設置
		var wall = Sprite2D.new()
		wall.texture= load("res://asset/art/tile_64x64.png")
		wall.global_position = i * cell_size
		wall.centered = false
		wall_container.add_child(wall)
		
	for i in floor_array:
		tile_map.set_cell(0, i, 1, Vector2i.ZERO)


func set_bomb()-> void:
	bomb_count = int(floor_array.size() /10)
	for i in range(bomb_count):
		var pos = find_random_cell()
		tile_map.set_cell(0, pos, 1, Vector2i(9, 0))
		stage_array[pos.y][pos.x].type = 9
		bomb_array.append(pos)
		
	set_number(bomb_array)

func set_number(array:Array)-> void:
	for i in array:
		for j in OFF_SET:
			var new_pos = i+j
			if is_bounds(new_pos) and stage_array[new_pos.y][new_pos.x].type <= 8:
				stage_array[new_pos.y][new_pos.x].type += 1
				tile_map.set_cell(0, new_pos, 1, Vector2i(stage_array[new_pos.y][new_pos.x].type, 0))
			else:
				continue


# パネル設置などで盤外を指定してエラーになるのを防ぐ
func is_bounds(pos:Vector2i)-> bool:
	return pos.x >= 0 and pos.x <= stage_scale.x-1 \
	and pos.y >= 0 and pos.y <= stage_scale.y-1

# 何もないポジションを取得する
func find_random_cell()-> Vector2i:
	var dx := 0
	var dy := 0
	while true:
		randomize()
		dx = randi() % stage_scale.x
		dy = randi() % stage_scale.y
		var cell = stage_array[dy][dx]
		if cell.type <= 8:
			break
	return Vector2i(dx, dy)



追加したコード

・爆弾関係の変数

# 爆弾設置後と爆弾を数を格納する変数
var bomb_array := []
var bomb_count := 0

今回爆弾の数はフロアの数によって変動させ、定義したらその数だけ爆弾を追加します。


・何もない場所を取得する関数

func find_random_cell()-> Vector2i:
	var dx := 0
	var dy := 0
	while true:
		randomize()
		dx = randi() % stage_scale.x
		dy = randi() % stage_scale.y
		var cell = stage_array[dy][dx]
		if cell.type <= 8:
			break
	return Vector2i(dx, dy)

まず find_random_cell()という作成した関数で重複を回避しながら何もないフロアを取得します。
この関数を使用して次の関数内で爆弾の位置を決めていきます。


・盤面に爆弾を追加する関数

func set_bomb()-> void:
	# 爆弾の数をフロアの数から計算してみる
	bomb_count = int(floor_array.size() /10)
	for i in range(bomb_count):
		var pos = find_random_cell()
		tile_map.set_cell(0, pos, 1, Vector2i(9, 0))
		stage_array[pos.y][pos.x].type = 9
		bomb_array.append(pos)
		
	set_number(bomb_array)

次に stage_array 内の対応する辞書プロパティから type を選択、 9 に設定。
9に設定した理由はタイルマップ登録で爆弾が x軸 9番目のタイルだからです。

同時に該当のタイルのプロパティtype の数値を set_cell() のタイル座標に使いグラフィックも更新します。

そして 次の関数 set_number() で爆弾の周りに番号を割り振っていきます。




・危険度を示す番号を配置

爆弾を設置し終えたら 次に爆弾の周りに危険度を示す番号を配置していきます。

func set_number(array:Array)-> void:
	for i in array:
		for j in OFF_SET:
			var new_pos = i+j
			if is_bounds(new_pos) and stage_array[new_pos.y][new_pos.x].type <= 8:
				stage_array[new_pos.y][new_pos.x].type += 1
				tile_map.set_cell(0, new_pos, 1, Vector2i(stage_array[new_pos.y][new_pos.x].type, 0))
			else:
				continue

爆弾のマスを配列 bomb_array で記録し、そこを起点として OFF_SET の座標と合わせることで爆弾の周囲8マスにアクセスできます。
この時 if文 で type 8 以下にすることで仮に爆弾が周囲にあってもスルーしながら危険度を増やすことが可能です。


ただ作成した OFF_SET が盤面の外にアクセスした場合 stage_array の配列範囲を越えるためそのままではエラーになります。


そこで is_bounds() と言う関数を作成して2次元配列の外へアクセスした際 false を返す事でエラーを防ぎます。

・is_bounds

func is_bounds(pos:Vector2i)-> bool:
	return pos.x >= 0 and pos.x <= stage_scale.x-1 \
	and pos.y >= 0 and pos.y <= stage_scale.y-1

追加したコード等がうまくいけば以下のように動作すると思います。



 

まとめ

■ ステップ処理を加えたプレビュー

上のgifでも左と下の端に位置する爆弾の周りへ番号の追加が上手くいっているのが見て取れると思います。

stage_array 内のプロパティ名 type ですが危険度であればもう少し分かりやすい名前が良かったかもしれません。
次回はカバーを実装、長くならなければ掘るための判定もつけようと思います。

今回はここまでになります、次回もお付き合いいただけると嬉しいです



■ 次 :【Godot4】懲りずにマインスイーパーを作る4 - ぬーぶのメモ帳