Creating a symmetry system in Godot

Godot Version

4.4.1

Question

I am currently working on a prototype of a puzzle game focused on symmetry. As a complete beginner with Godot and programming in general, I have created a top-down 3D game where the player can move around a 3D map and manipulate pillars to establish perfect symmetry between objects. The goal is to restore symmetry in the scenes in order to progress to the next level. I would like to get solid guidance to make this system viable and advance in the game design of the project. For now, I have a simple script for movement and jumping in player.gd, as well as a rotation script for the pillars that allows them to be turned 15 degrees with each click of a keyboard command.

What part of this do you want help with?

1 Like

I want to create a viable symmetry system that would validate that each object is perfectly mirrored relative to a center of symmetry. Thus, each object must be manipulated to be perfectly identical to its mirror. I saw that I could have a lead by checking in the Transformation Inspector (position and rotation), but I want to create a viable script that could be functional no matter the grid I am on (the player can push and rotate the pillars as in the screenshot above), thus having a kind of base pedestal.

I am a beginner in GDScript, so despite my research to find references or viable sources, I admit that I haven’t found much. I tried to get help from GPT, but I admit that it didn’t give me anything really viable or understandable.

Here is my node structure in my scene 1:

Off the top of my head, what you need can be seperated into 2 “systems”

  1. A “Symmetry Line” Node that defines the symmetry, with a method to check if two nodes are symmetrical

  2. A way to define which nodes should be symmetrical

Number 2 is the easy part, heres some ways to do it:

  • Add a script to the pillar root and have its children all be symmetrical (Works, but not scalable)
  • Add a group “symmetrical” and add all symmetrical objects to it (Simple and elegant, but you can only have 1 symmetry per level)
  • Create a seperate script with a exported node array (Supports multiple symmetries, more manual)

Heres some code for a symmetry line, important to note is that is checks for exact mirroring.
Depending on how you use it, you might want to instead check if the points are within some deviation, replacing node1_position == node2_position with node1_position.distance_to(node2_position) < some_margin

extends Node3D
class_name Symmetry_Line

func are_symmetrical(node1 : Node3D, node2 : Node3D):
	# Get node positions in our basis
	var node1_position := global_basis * node1.global_position
	var node2_position := global_basis * node2.global_position
	
	# Get nodes forward and up vectors in our basis
	var node1_forwards := global_basis * node1.global_basis.x
	var node1_up 	   := global_basis * node1.global_basis.y
	var node2_forwards := global_basis * node2.global_basis.x
	var node2_up 	   := global_basis * node2.global_basis.y
	
	# Flip node 2's z axis
	node2_position.z = -node2_position.z
	node2_forwards.z = -node2_forwards.z
	node2_up.z       = -node2_up.z
	
	# Check if they're identical
	var same_position = node1_position == node2_position
	var same_forwards = node1_forwards == node2_forwards
	var same_up       = node1_up       == node2_up
	
	return same_position and same_forwards and same_up
1 Like

I’d think the easiest way to handle this is to create a parent Node3D and then make all the blocks you can push children of this node. Have the origin of the parent Node3D be the point of symmetry you’re trying to balance around. Then if you want to check if one block is the mirror of another, you can just check if it’s local position’s x, y, or z is equal to the negative of the block you’re comparing it to. That should work for any reflection across one of the axes.

You can get more complex symmetry checks by multiplying by rotation and scaling matrices.

1 Like

Do you want specific things to be symmetrical? In your screenshot, you have six very similar pillars. If the player swapped two of them (like, say, mid right and top right) and fixed the angles, is that a success, or does the top right one have to be symmetrical with a specific other pillar?

In this case, do you want every pillar to be rotationally symmetrical around the center? The left pillars to be mirror symmetrical with the right pillars, or the bottom with the top? There are various kinds of symmetry you could be looking for.

You could have fixed positions that everything needs to be at for a “correct” solution, in which case it’s a matter of checking that everything is where it should be, and the rotation on each object matches the rotation of the object on the opposing position.

You could also just scan all the “symmetry” objects each frame and see if they’re in an acceptable position. Something like:

# game level script

var ObjList: Array = [] # Add objects to this...

# call this function from `_process()`...

func check_obj_symmetry() -> bool:
    # Make an array of boolean, one per object, all false.
    var good: Array = []
    good.resize(ObjList.size())
    good.fill(false)

    # Compare every object to every object, testing if they're symmetrical.
    for a in ObjList.size():
        for b in range(a + 1, ObjList.size()):
            if symmetrical(a, b): # If yes, mark both objects good.
                good[a] = true
                good[b] = true

    # Scan the good list, seeing if everything has a symmetrical partner.
    for g in good:
        if g == false:
            return false
    return true
1 Like

First of all, thank you very much for your answers and responsiveness; it’s kind of you. I will try to explain as best as possible the desired result.

The player must “restore” the symmetry by deducing the point of symmetry relative to the other pillars (elements placed in our level).

The boards (levels) present a broken symmetry. In the case I gave, a single pillar represents a fracture in the symmetry, in this case, its rotation.

As the boards progress, the symmetry could potentially change axes, depending on the level, with the goal of playing with perspectives later on.

We have symmetry on the X-axis relative to the center of symmetry; each object must have its pair in the same opposite position.

As soon as a symmetry is restored, for example, a message is displayed in the console “Map Validated,” and the player can move on to the next level.

For the method @Aria, I think a grid system would be more viable to ensure perfect symmetry. This way, the player could move the Pillars to exact positions on the grid to avoid inconsistencies.

For your method, @hexgrid seems like a solid lead to experiment with. I will try to better understand the system you propose and see if I can implement it.

This is my first Godot project, and I admit that given my level, I might have been better off starting with something simpler. But I think that the core of the game relies on this mechanic, which I plan to make more complex over time.

I’m still racking my brain to see if I can put your suggestions and advice into practice, and I’ll come back to you if I’m still struggling. Thank you very much anyway <3

1 Like