Staggering snap or gridmap for level creation?

Godot Version

Godot 4.3 beta 2

Question

I’m working on a 3d brick breaker game. I’m trying to use gridmap or the .snapped() vector3 property to allow me to easily create levels.
I have a version working with the .snapped() kinda working. The issue is I have to keep the same vertical line.

_|_|_|_|_
_|_|_|_|_

But I want to have the option to turn on stagger/offset so I could potentially add bricks like this:

_|_|_|_|_
|_|_|_|_|_

I tried adding half the width of the brick to the position and snapped vector, but this doesn’t seem to help. This is either me doing something wrong or about the way snapped works.

I’ve read I could try using 2 gridmaps and seeing if I could add the offset there. But would need to prevent being able to overlap the meshes. If I went gridmap I could simply instantiate the scene after.

Any thoughts on how I could allow bricks to be placed in a staggered pattern? Ideally I’d want to be able to toggle the stagger so I could choose whether it was staggered or not.

One other idea I’m open to, is using multiple tilemap layers (or tilesets I forget where offset is added) and designing levels in 2d. I could then use that to potentially instantiate the 3d scenes and figure out how to translate to the Y axis as there are only certain times the Y axis would increase in 3d.

Here’s the code I’m using to try to set the offset currently with the snapped property. I ended up using the static body signal as it was easy to pull the position. I was unsure how to take the mouse position and adapt it to a 3d.

func create_brick(pos : Vector3):
	var new_brick : RigidBody3D= brick_scene.instantiate()
	var brick_size = new_brick.brick_size
	#print(brick_size_snap)
	brick_container.add_child(new_brick)
	new_brick.position = pos.snapped(brick_size_snap)
	#print("position: ", new_brick.position)
	new_brick.rotate_y(1.5708)


func _on_static_body_3d_input_event(_camera: Node, event: InputEvent, position: Vector3, _normal: Vector3, shape_idx: int) -> void:
	if Input.is_action_just_pressed("add_brick"):
		print("snapped: ", position.snapped(brick_size_snap))
		if offset:
			var offset_brick_pos = brick_size_snap + offset_size
			var new_pos = position
			new_pos.z += (brick_size_snap.z)
			if !brick_pos_array.has(position.snapped(brick_size_snap)) or !brick_pos_array.has(new_pos.snapped(offset_brick_pos)):
				print("offset: ", new_pos.snapped(offset_brick_pos))
				print("normal: ", position.snapped(brick_size_snap))
				create_brick(new_pos.snapped(offset_brick_pos))
				brick_pos_array.append(new_pos.snapped(offset_brick_pos))
				print("array: ",brick_pos_array.size())
			else:
				print("error")
		elif !brick_pos_array.has(position.snapped(brick_size_snap)):
			print("normal: ", brick_size_snap)
			create_brick(position.snapped(brick_size_snap))
			brick_pos_array.append(position.snapped(brick_size_snap))
			print("array: ",brick_pos_array.size())
		else: 
			print("error")

Could you only apply the offset if the snapped Y is even/odd?

if offset:
	var brick_snap := position.snapped(brick_size_snap)
	var apply_offset: bool = floori(brick_snap.y) % 2 == 0

	var offset_brick_pos = brick_size_snap + (offset_size if apply_offset else Vector3.ZERO)

Hmm. A few things, I think you mean the x axis. but ultimately that only works if I start a level on a specific line. I want to be able to toggle this functionality.
Second apply_offset in your version is a float or int. brick_size_snap is a Vector3. So I can’t add them that way.

If offset is true, I would always want to use offset_size. So getting the modulo wouldn’t really help. The issue I have with the approach is offset_size doesn’t seem to affect the snapped position. I think this is due to the way snapped works or me doing the math wrong. Regardless of if I have offset true or false, I never end up with an offset.

offset_size is really about updating the Z axis of the snap

Just to provide the full code in case having more info about the variables helps:


extends Node3D

@export var brick : RigidBody3D
@onready var collision_shape_3d: CollisionShape3D 
@onready var brick_container: Node3D = $BrickContainer
@onready var level_name: TextEdit = $CanvasLayer/LevelName

@export var brick_scene : PackedScene
@export var offset : bool = false
var brick_size_snap = Vector3(0.3*6, 1*6, 1*6)

var brick_pos_array : Array = []
var offset_size := Vector3(0,0,3)

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	brick_size_snap = brick_size_snap.rotated(Vector3.UP, 1.5708)
	#offset_size = offset_size.rotated(Vector3.UP, 1.5708)



func create_brick(pos : Vector3):

	var new_brick : RigidBody3D= brick_scene.instantiate()
	var brick_size = new_brick.brick_size
	#print(brick_size_snap)
	brick_container.add_child(new_brick)
	new_brick.position = pos.snapped(brick_size_snap)
	#print("position: ", new_brick.position)
	new_brick.rotate_y(1.5708)



func _on_static_body_3d_input_event(_camera: Node, event: InputEvent, position: Vector3, _normal: Vector3, shape_idx: int) -> void:
	if Input.is_action_just_pressed("add_brick"):
		print("snapped: ", position.snapped(brick_size_snap))
		if offset:
			var offset_brick_pos = brick_size_snap + offset_size
			var new_pos = position
			new_pos.z += (brick_size_snap.z)
			if !brick_pos_array.has(position.snapped(brick_size_snap)) or !brick_pos_array.has(new_pos.snapped(offset_brick_pos)):
				print("offset: ", new_pos.snapped(offset_brick_pos))
				print("normal: ", position.snapped(brick_size_snap))
				create_brick(new_pos.snapped(offset_brick_pos))
				brick_pos_array.append(new_pos.snapped(offset_brick_pos))
				print("array: ",brick_pos_array.size())
			else:
				print("error")
		elif !brick_pos_array.has(position.snapped(brick_size_snap)):
			print("normal: ", brick_size_snap)
			create_brick(position.snapped(brick_size_snap))
			brick_pos_array.append(position.snapped(brick_size_snap))
			print("array: ",brick_pos_array.size())
		else: 
			print("error")
		
		

Edit: Removed the final part of the big if statement as ultimately it was commented out and I feel added to confusion