A Quadrilateral Cowboy clone intended to help me learn Game Dev

I will be moving this project to Rust and marking this branch as stale

+98 -78
+1
camera.go
··· 12 12 13 13 func (p *PlayerCamera) Ready() { 14 14 Input.SetMouseMode(Input.MouseModeConfined) 15 + p.AsCamera3D().SetCurrent(true) 15 16 } 16 17 17 18 func registerCamera() {
+6 -7
graphics/terminal/terminal.tscn
··· 37 37 grow_vertical = 2 38 38 color = Color(0, 0, 0, 1) 39 39 40 - [node name="RichTextLabel" type="RichTextLabel" parent="SubViewport/Control" unique_id=1709738328] 40 + [node name="RichTextLabel" type="TextEdit" parent="SubViewport/Control" unique_id=1709738328] 41 41 layout_mode = 1 42 - anchors_preset = -1 43 - anchor_left = 0.1 44 - anchor_top = 0.05 45 - anchor_right = 0.9 46 - anchor_bottom = 0.95 42 + anchors_preset = 15 43 + anchor_right = 1.0 44 + anchor_bottom = 1.0 47 45 grow_horizontal = 2 48 46 grow_vertical = 2 47 + placeholder_text = "this is a test" 49 48 50 49 [node name="Quad" type="MeshInstance3D" parent="." unique_id=1260793733] 51 - transform = Transform3D(-4.371139e-08, 1, 0, 4.371139e-08, 1.9106855e-15, -1, -1, -4.371139e-08, -4.371139e-08, 0.29435754, 1.222928, 0) 50 + transform = Transform3D(-3.5624783e-08, 0.815, -3.5624783e-08, 0, -3.5624783e-08, -0.815, -0.815, -3.5624783e-08, 1.5572087e-15, 0.29876256, 1.3031881, 0) 52 51 mesh = SubResource("PlaneMesh_amys7") 53 52 54 53 [node name="Area3D" type="Area3D" parent="Quad" unique_id=1172421738]
+1 -1
main.go
··· 16 16 } 17 17 18 18 func main() { 19 + startup.LoadingScene() 19 20 classdb.Register[MainScene]() 20 21 registerLevels() 21 22 registerTerminal() 22 23 registerCamera() 23 - startup.LoadingScene() 24 24 startup.Scene() 25 25 }
+90 -70
terminal.go
··· 1 1 package main 2 2 3 3 import ( 4 + "fmt" 5 + 4 6 "graphics.gd/classdb" 5 7 "graphics.gd/classdb/Area3D" 6 8 "graphics.gd/classdb/InputEvent" 7 9 "graphics.gd/classdb/InputEventMouse" 10 + "graphics.gd/classdb/InputEventMouseButton" 8 11 "graphics.gd/classdb/InputEventMouseMotion" 12 + "graphics.gd/classdb/InputEventScreenDrag" 13 + "graphics.gd/classdb/InputEventScreenTouch" 9 14 "graphics.gd/classdb/MeshInstance3D" 10 15 "graphics.gd/classdb/Node" 11 16 "graphics.gd/classdb/Node3D" 12 17 "graphics.gd/classdb/SubViewport" 13 18 "graphics.gd/classdb/Time" 14 - "graphics.gd/variant" 19 + "graphics.gd/variant/Object" 15 20 "graphics.gd/variant/Transform3D" 16 21 "graphics.gd/variant/Vector2" 17 22 "graphics.gd/variant/Vector3" ··· 20 25 type TerminalScene struct { 21 26 Node3D.Extension[TerminalScene] 22 27 23 - SubViewport SubViewport.Instance 24 - is_mouse_inside bool 25 - last_event_pos2D any 26 - last_event_time float32 28 + /// Used for checking if the mouse is inside the Area3D. 29 + isMouseInside bool 30 + /// The last processed input touch/mouse event. To calculate relative movement. 31 + lastEventPos2D Vector2.XY 32 + hasLastEventPos2D bool 33 + /// The time of the last event in seconds since engine start. 34 + lastEventTime float32 35 + 36 + nodeViewport SubViewport.Instance 37 + nodeQuad MeshInstance3D.Instance 38 + nodeArea Area3D.Instance 27 39 } 28 40 29 41 func (t *TerminalScene) Ready() { 30 - node_area_node := t.AsNode().GetNode("Quad/Area3D") 31 - node_area := Area3D.New() 32 - node_area.SetObject(node_area_node.AsObject()) 33 - node_area.AsCollisionObject3D().OnMouseEntered(t.mouse_entered_area) 34 - node_area.AsCollisionObject3D().OnMouseExited(t.mouse_exited_area) 35 - node_area.AsCollisionObject3D().OnInputEvent(t.mouse_input_event) 42 + t.nodeViewport = Object.To[SubViewport.Instance](t.AsNode().GetNode("SubViewport")) 43 + t.nodeQuad = Object.To[MeshInstance3D.Instance](t.AsNode().GetNode("Quad")) 44 + t.nodeArea = Object.To[Area3D.Instance](t.AsNode().GetNode("Quad/Area3D")) 45 + t.isMouseInside = false 46 + t.hasLastEventPos2D = false 47 + t.lastEventTime = -1.0 48 + 49 + t.nodeArea.AsCollisionObject3D().OnMouseEntered(t.MouseEnteredArea) 50 + t.nodeArea.AsCollisionObject3D().OnMouseExited(t.MouseExitedArea) 51 + t.nodeArea.AsCollisionObject3D().OnInputEvent(t.MouseInputEvent) 36 52 } 37 53 38 - func (t *TerminalScene) mouse_entered_area() { 39 - t.is_mouse_inside = true 54 + func (t *TerminalScene) Process(delta float32) { 55 + fmt.Println(t.lastEventTime) 40 56 } 41 57 42 - func (t *TerminalScene) mouse_exited_area() { 43 - t.is_mouse_inside = false 58 + func (t *TerminalScene) MouseEnteredArea() { 59 + t.isMouseInside = true 44 60 } 45 61 46 - func (t *TerminalScene) UnhandledInput(event InputEvent.Instance) { 47 - event_variant := variant.New(event) 48 - event_type_string := event_variant.Type().String() 62 + func (t *TerminalScene) MouseExitedArea() { 63 + t.isMouseInside = false 64 + } 49 65 50 - if (event_type_string == "InputEventMouseButton") || (event_type_string == "InputEventMouseMotion") || (event_type_string == "InputEventScreenDrag") || (event_type_string == "InputEventScreenTouch") { 66 + func (t *TerminalScene) UnhandledInput(event InputEvent.Instance) { 67 + // Check if the event is a non-mouse/non-touch event 68 + if Object.Is[InputEventMouseButton.Instance](event) || Object.Is[InputEventMouseMotion.Instance](event) || Object.Is[InputEventScreenDrag.Instance](event) || Object.Is[InputEventScreenTouch.Instance](event) { 69 + // If the event is a mouse/touch event, then we can ignore it here, because it will be 70 + // handled via Physics Picking. 51 71 return 52 72 } 53 73 54 - node_viewport_node := t.AsNode().GetNode("SubViewport") 55 - node_viewport := SubViewport.New() 56 - node_viewport.SetObject(node_viewport_node.AsObject()) 57 - node_viewport.AsViewport().PushInput(event) 74 + t.nodeViewport.AsViewport().PushInput(event.AsInputEvent()) 58 75 } 59 76 60 - func (t *TerminalScene) mouse_input_event(_camera Node.Instance, event InputEvent.Instance, event_position Vector3.XYZ, _normal Vector3.XYZ, _shape_idx int) { 61 - node_quad_node := t.AsNode().GetNode("Quad") 62 - node_quad := MeshInstance3D.New() 63 - node_quad.SetObject(node_quad_node.AsObject()) 64 - 65 - node_viewport_node := t.AsNode().GetNode("SubViewport") 66 - node_viewport := SubViewport.New() 67 - node_viewport.SetObject(node_viewport_node.AsObject()) 68 - 69 - mouse_event := InputEventMouse.New() 70 - mouse_event.SetObject(event.AsObject()) 71 - 72 - event_variant := variant.New(event) 73 - event_type_string := event_variant.Type().String() 77 + func (t *TerminalScene) MouseInputEvent(_camera Node.Instance, event InputEvent.Instance, eventPosition Vector3.XYZ, _normal Vector3.XYZ, _shapeIdx int) { 78 + // Get mesh size to detect edges and make conversions. This code only supports PlaneMesh and QuadMesh. 79 + quadMeshSize := t.nodeQuad.Mesh().GetAabb().Size 80 + // Event position in Area3D in world coordinate space. 81 + eventPos3D := eventPosition 82 + // Current time in seconds since engine start. 83 + now := float32(Time.GetTicksMsec()) / 1000.0 84 + // Convert position to a coordinate space relative to the Area3D node. 85 + // NOTE: affine_inverse accounts for the Area3D node's scale, rotation, and position in the scene! 86 + eventPos3D = transform_xform(Transform3D.AffineInverse(t.nodeQuad.AsNode3D().GlobalTransform()), eventPos3D) 87 + eventPos2D := Vector2.Zero 74 88 75 - quad_mesh_size := node_quad.Mesh().GetAabb().Size 76 - event_pos3D := event_position 77 - now := Time.GetTicksMsec() / 1000.0 78 - event_pos3D = transform_xform(Transform3D.AffineInverse(Transform3D.BasisOrigin{ 79 - Basis: node_quad.AsNode3D().GlobalTransform().Basis, 80 - Origin: node_quad.AsNode3D().GlobalTransform().Origin, 81 - }), event_pos3D) 89 + if t.isMouseInside { 90 + // Convert the relative event position from 3D to 2D. 91 + eventPos2D = Vector2.New(eventPos3D.X, -eventPos3D.Y) 92 + // Right now the event position's range is the following: (-quad_size/2) -> (quad_size/2) 93 + // We need to convert it into the following range: -0.5 -> 0.5 94 + eventPos2D.X /= quadMeshSize.X 95 + eventPos2D.Y /= quadMeshSize.Y 96 + // Then we need to convert it into the following range: 0 -> 1 97 + eventPos2D.X += 0.5 98 + eventPos2D.Y += 0.5 99 + // Finally we convert the position to the following range: 0 -> viewport.size 100 + eventPos2D.X *= float32(t.nodeViewport.Size().X) 101 + eventPos2D.Y *= float32(t.nodeViewport.Size().Y) 102 + // We need to do these conversions so the event's position is in the viewport's coordinate system 103 + } else if t.hasLastEventPos2D { 104 + // Fall back to the last known event position. 105 + eventPos2D = t.lastEventPos2D 106 + } 82 107 83 - event_pos2D := Vector2.Zero 108 + // Set the event's position and global position. 109 + Object.To[InputEventMouse.Instance](event).SetPosition(eventPos2D) 84 110 85 - if t.is_mouse_inside { 86 - event_pos2D = Vector2.New(event_pos3D.X, -event_pos3D.Y) 87 - event_pos2D.X = event_pos2D.X / quad_mesh_size.X 88 - event_pos2D.Y = event_pos2D.Y / quad_mesh_size.Y 89 - event_pos2D.X += 0.5 90 - event_pos2D.Y += 0.5 91 - event_pos2D.X *= float32(node_viewport.Size().X) 92 - event_pos2D.Y *= float32(node_viewport.Size().Y) 93 - } else if t.last_event_pos2D != nil { 94 - event_pos2D = t.last_event_pos2D.(Vector2.XY) 111 + if Object.Is[InputEventMouse.Instance](event) { 112 + Object.To[InputEventMouse.Instance](event).SetGlobalPosition(eventPos2D) 95 113 } 96 114 97 - mouse_event.SetPosition(event_pos2D) 98 - mouse_event.SetGlobalPosition(event_pos2D) 99 - 100 - if event_type_string == "InputEventMouseMotion" || event_type_string == "InputEventScreenDrag" { 101 - if t.last_event_pos2D == nil { 102 - other_event := InputEventMouseMotion.New() 103 - other_event.SetObject(event.AsObject()) 104 - other_event.SetRelative(Vector2.Zero) 115 + // Calculate the relative event distance. 116 + if Object.Is[InputEventMouseMotion.Instance](event) || Object.Is[InputEventScreenDrag.Instance](event) { 117 + // If there is not a stored previous position, then we'll assume there is no relative motion. 118 + // If there is a stored previous position, then we'll calculate the relative position by subtracting 119 + // the previous position from the new position. This will give us the distance the event traveled from 120 + // prev_pos. 121 + if !t.hasLastEventPos2D { 122 + Object.To[InputEventMouseMotion.Instance](event).SetRelative(Vector2.Zero) 105 123 } else { 106 - other_event := InputEventMouseMotion.New() 107 - other_event.SetObject(event.AsObject()) 108 - other_event.SetRelative(Vector2.Sub(event_pos2D, t.last_event_pos2D.(Vector2.XY))) 109 - other_event.SetVelocity(Vector2.DivX(other_event.Relative(), (float32(now) - t.last_event_time))) 124 + Object.To[InputEventMouseMotion.Instance](event).SetRelative(Vector2.Sub(eventPos2D, t.lastEventPos2D)) 125 + Object.To[InputEventMouseMotion.Instance](event).SetVelocity(Vector2.DivX(Object.To[InputEventMouseMotion.Instance](event).Relative(), now-t.lastEventTime)) 110 126 } 111 127 } 112 128 113 - t.last_event_pos2D = event_pos2D 114 - t.last_event_time = float32(now) 115 - node_viewport.AsViewport().PushInput(event) 129 + // Update lastEventPos2D with the position we just calculated 130 + t.lastEventPos2D = eventPos2D 131 + t.hasLastEventPos2D = true 132 + // Update lastEventTime to current time 133 + t.lastEventTime = now 134 + // Finally, send the processed input event to the viewport 135 + t.nodeViewport.AsViewport().PushInput(event.AsInputEvent()) 116 136 } 117 137 118 138 func registerTerminal() {