Frogment
🎮 Link to game: https://coahl.itch.io/frogment
My Contributions:
- Node Path System
- Node Connection Tool
- Player Pathfinding
This was my second game project at The Game Assembly, completed in 5 weeks in Unity. This was a puzzle game made for Android where you play as a frog character. You automatically move to a block by tapping on it, and there are puzzles to solve by pressing buttons or rotating blocks.
Node connections

On this project my main task was working with the player’s pathfinding and node path system.
Every walkable block has a WalkableNode script assigned to it. To keep things simple, this script only contains references to connected nodes and what node type it is (regular, stairs or ladders). I also added gizmo lines to show the node connections, which made it easy to spot missing or incorrect connections.
1public class WalkableNode : MonoBehaviour
2{
3 [SerializeField] NodeType myType;
4 public NodeType Type => myType;
5
6 public List<WalkableNode> myConnectedNodes = new();
7
8 Vector3 myLastPosition;
9 Vector3 myLastForward;
10
11 [HideInInspector] public bool myIsBound;
12 private void Awake()
13 {
14 myLastPosition = transform.position;
15 }
16
17 public void ConnectNode(WalkableNode aNode)
18 {
19 if(myConnectedNodes.Contains(aNode))
20 {
21 Debug.Log("Already has node " + aNode.name + " in list", this);
22 return;
23 }
24
25 myConnectedNodes.Add(aNode);
26 }
27 public void DisconnectNode(WalkableNode aNode)
28 {
29 if (!myConnectedNodes.Contains(aNode))
30 {
31 Debug.Log("Does not have node " + aNode.name + " in its list", this);
32 return;
33 }
34
35 myConnectedNodes.Remove(aNode);
36 }
37
38 public bool IsConnectedWith(WalkableNode aOther)
39 {
40 bool meToOther = myConnectedNodes.Contains(aOther);
41 bool otherToMe = aOther.myConnectedNodes.Contains(this);
42
43 if(meToOther && !otherToMe)
44 {
45 Debug.LogWarning("This node has a 1 way connection with node " + aOther.name, this);
46 }
47 else if(!meToOther && otherToMe)
48 {
49 Debug.LogWarning("This node has a 1 way connection with node " + name, aOther);
50 }
51
52 return meToOther && otherToMe;
53 }
54
55 /// <summary>
56 /// Returns the offset position the player should walk to when this is the current node
57 /// </summary>
58 public Vector3 GetWalkPosition()
59 {
60 switch(myType)
61 {
62 default:
63 case NodeType.Regular:
64 return transform.position + transform.up;
65 case NodeType.Ladder:
66 return transform.position + 0.5f * transform.up + 0.75f * transform.forward;
67 case NodeType.Stair:
68 return transform.position + 0.5f * transform.up;
69
70 }
71 }
72
73 private void OnDrawGizmos()
74 {
75
76 Gizmos.color = Color.black;
77 Gizmos.DrawWireSphere(GetWalkPosition(), 0.15f);
78 Gizmos.color = Color.green;
79
80 if(myConnectedNodes == null)
81 {
82 return;
83 }
84
85 foreach (var node in myConnectedNodes)
86 {
87 switch(myType)
88 {
89 default:
90 Gizmos.DrawLine(GetWalkPosition() + Vector3.up * 0.15f, node.GetWalkPosition());
91 break;
92 case NodeType.Ladder:
93 Gizmos.DrawLine(GetWalkPosition() + transform.forward * 0.15f, node.GetWalkPosition());
94 break;
95
96 }
97
98 }
99 }
100}
101
102public enum NodeType
103{
104 Regular,
105 Ladder,
106 Stair,
107}
Connection Tool

Node connections were then configured in the editor. Manually connecting every block would take ages, so I made a editor window with node connections functions. I started off with a button that would remove all nodes and a button that would automatically connect nodes that should connect (depending on their placement & node type). Later I added a button to auto disconnect nodes, a button to check if node has snapped position and a button that would remove invalid connections
1public class NodeWindow : EditorWindow
2{
3 [MenuItem("Tools/NodeWindow")]
4 public static void CreateWindow()
5 {
6 NodeWindow window = GetWindow<NodeWindow>();
7 window.titleContent = new GUIContent("NodeWindow");
8 window.minSize = new Vector2(200,500);
9 }
10
11 private void OnGUI()
12 {
13 GUILayout.BeginVertical();
14 GUILayout.FlexibleSpace();
15 GUILayout.EndVertical();
16
17 GUILayout.BeginHorizontal();
18 GUILayout.FlexibleSpace();
19
20 GUIContent buttonContent = new GUIContent("Clear all connections", "Removes all node connections!");
21 if (GUILayout.Button(buttonContent, GUILayout.Width(130), GUILayout.Height(50)))
22 {
23 WalkableNode[] allNodes = FindObjectsOfType<WalkableNode>();
24 foreach (WalkableNode node in allNodes)
25 {
26 EditorUtility.SetDirty(node);
27 if (node.myConnectedNodes == null)
28 {
29 node.myConnectedNodes = new();
30 continue;
31 }
32
33 node.myConnectedNodes.Clear();
34 }
35 }
36 GUILayout.FlexibleSpace();
37 GUILayout.EndHorizontal();
38
39 GUILayout.BeginVertical();
40 GUILayout.FlexibleSpace();
41 GUILayout.EndVertical();
42
43 GUILayout.BeginHorizontal();
44 GUILayout.FlexibleSpace();
45
46 buttonContent = new GUIContent("Auto connect", "Goes through all Walkable Nodes and connects it with neighbouring nodes it should be connected with");
47 if (GUILayout.Button(buttonContent, GUILayout.Width(100), GUILayout.Height(50)))
48 {
49 WalkableNode[] allNodes = FindObjectsOfType<WalkableNode>();
50 foreach (WalkableNode node in allNodes)
51 {
52 EditorUtility.SetDirty(node);
53 NodeUtilities.AutoConnectNode(node);
54 }
55 }
56
57
58 GUILayout.FlexibleSpace(); // Add flexible space to the right of the button
59 GUILayout.EndHorizontal();
60
61 GUILayout.BeginVertical();
62 GUILayout.FlexibleSpace();
63 GUILayout.EndVertical();
64
65 GUILayout.BeginHorizontal();
66 GUILayout.FlexibleSpace();
67
68 buttonContent = new GUIContent("Auto disconnect", "Goes through all Walkable Nodes and disconncects it with neighbouring nodes it should be connected with");
69 if (GUILayout.Button(buttonContent, GUILayout.Width(120), GUILayout.Height(50)))
70 {
71 WalkableNode[] allNodes = FindObjectsOfType<WalkableNode>();
72 foreach (WalkableNode node in allNodes)
73 {
74 EditorUtility.SetDirty(node);
75 NodeUtilities.AutoDisconnectNode(node);
76 }
77 }
78
79 GUILayout.FlexibleSpace(); // Add flexible space to the right of the button
80 GUILayout.EndHorizontal();
81
82 GUILayout.BeginVertical();
83 GUILayout.FlexibleSpace();
84 GUILayout.EndVertical();
85
86 GUILayout.BeginHorizontal();
87 GUILayout.FlexibleSpace();
88
89 buttonContent = new GUIContent("Check every node", "Goes through all Walkable Nodes and detects any that are not snapped to a position");
90 if (GUILayout.Button(buttonContent, GUILayout.Width(150), GUILayout.Height(50)))
91 {
92 WalkableNode[] allNodes = FindObjectsOfType<WalkableNode>();
93 foreach (WalkableNode node in allNodes)
94 {
95 NodeUtilities.CheckNode(node);
96 }
97 }
98
99 GUILayout.FlexibleSpace(); // Add flexible space to the right of the button
100 GUILayout.EndHorizontal();
101
102 GUILayout.BeginVertical();
103 GUILayout.FlexibleSpace();
104 GUILayout.EndVertical();
105
106 GUILayout.BeginHorizontal();
107 GUILayout.FlexibleSpace();
108
109 buttonContent = new GUIContent("Fix every node", "Goes through all Walkable Nodes and removes connections to itself, null nodes and duplicate connections");
110 if (GUILayout.Button(buttonContent, GUILayout.Width(150), GUILayout.Height(50)))
111 {
112 WalkableNode[] allNodes = FindObjectsOfType<WalkableNode>();
113 foreach (WalkableNode node in allNodes)
114 {
115 EditorUtility.SetDirty(node);
116 NodeUtilities.FixNode(node);
117 }
118 }
119
120 GUILayout.FlexibleSpace(); // Add flexible space to the right of the button
121 GUILayout.EndHorizontal();
122
123 GUILayout.BeginVertical();
124 GUILayout.FlexibleSpace();
125 GUILayout.EndVertical();
126 }
127}