diff --git a/project/scenes/player.gd b/project/scenes/player.gd new file mode 100644 index 0000000..7c0687e --- /dev/null +++ b/project/scenes/player.gd @@ -0,0 +1,7 @@ +extends CharacterBody2D +class_name Player + +var session_id: String + +func _enter_tree() -> void: + $NameTag.text = session_id diff --git a/project/scenes/player.tscn b/project/scenes/player.tscn new file mode 100644 index 0000000..70a6592 --- /dev/null +++ b/project/scenes/player.tscn @@ -0,0 +1,29 @@ +[gd_scene load_steps=4 format=3 uid="uid://co4dwle8qgvn4"] + +[ext_resource type="Script" path="res://scenes/player.gd" id="1_0ulg3"] +[ext_resource type="Texture2D" uid="uid://brayuh2pw38ix" path="res://icon.svg" id="1_5um25"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_gcdh3"] +size = Vector2(128, 128) + +[node name="Player" type="CharacterBody2D"] +script = ExtResource("1_0ulg3") + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture = ExtResource("1_5um25") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("RectangleShape2D_gcdh3") + +[node name="NameTag" type="Label" parent="."] +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -20.0 +offset_top = -11.5 +offset_right = 20.0 +offset_bottom = 11.5 +grow_horizontal = 2 +grow_vertical = 2 diff --git a/project/scenes/worFF19.tmp b/project/scenes/worFF19.tmp new file mode 100644 index 0000000..937d4e4 --- /dev/null +++ b/project/scenes/worFF19.tmp @@ -0,0 +1,12 @@ +[gd_scene load_steps=2 format=3 uid="uid://br61u82ldlrxe"] + +[ext_resource type="Script" path="res://scenes/world.gd" id="1_r7dh1"] + +[node name="World" type="Node2D"] +script = ExtResource("1_r7dh1") + +[node name="Button" type="Button" parent="."] +offset_right = 8.0 +offset_bottom = 8.0 + +[connection signal="pressed" from="Button" to="." method="_on_button_pressed"] diff --git a/project/scenes/world.gd b/project/scenes/world.gd new file mode 100644 index 0000000..7a44e48 --- /dev/null +++ b/project/scenes/world.gd @@ -0,0 +1,29 @@ +extends Node2D + + +func _ready(): + UdpClient.player_join.connect(_on_player_join) + UdpClient.player_disconnect.connect(_on_player_disconnect) + +func _on_player_join(player_client: Dictionary, clients: Array) -> void: + add_player(player_client) + for client in clients: + add_player(client) + + +func add_player(client: Dictionary): + var player: Player = preload("res://scenes/player.tscn").instantiate() + player.session_id = client.session_id + player.position = Vector2(client.position.x, client.position.y) + add_child(player) + +func _on_button_pressed(): + var payload: Dictionary = { + "type": "init", + } + UdpClient.send_message("test") + +func _on_player_disconnect(session_id: String): + for child: Node in get_children(): + if child is Player && child.session_id == session_id: + child.queue_free() diff --git a/project/scenes/world.tscn b/project/scenes/world.tscn new file mode 100644 index 0000000..937d4e4 --- /dev/null +++ b/project/scenes/world.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=2 format=3 uid="uid://br61u82ldlrxe"] + +[ext_resource type="Script" path="res://scenes/world.gd" id="1_r7dh1"] + +[node name="World" type="Node2D"] +script = ExtResource("1_r7dh1") + +[node name="Button" type="Button" parent="."] +offset_right = 8.0 +offset_bottom = 8.0 + +[connection signal="pressed" from="Button" to="." method="_on_button_pressed"] diff --git a/project/scripts/autoload/udp_client.gd b/project/scripts/autoload/udp_client.gd index 707dc95..dd90d0a 100644 --- a/project/scripts/autoload/udp_client.gd +++ b/project/scripts/autoload/udp_client.gd @@ -1,14 +1,15 @@ extends Node +signal player_move(session_id: String, position_mod: Vector2) +signal player_join(player_client: Dictionary, clients: Array) +signal player_disconnect(session_id: String) + var udp_peer: PacketPeerUDP = PacketPeerUDP.new() -var server_ip: String = "45.93.249.177" # Replace with your server's IP +var server_ip: String = "127.0.0.1" # Replace with your server's IP var server_port: int = 4477 # Replace with your server's port func _ready(): - var err = udp_peer.bind(server_port, "*") - if err != OK: - print("Failed to bind to port: ", server_port) - return + udp_peer.bind(server_port, "*") print("Listening on port: ", server_port) # Send a message to the server to initiate session ID assignment var payload: Dictionary = { @@ -19,8 +20,16 @@ func _ready(): func _process(_delta): if udp_peer.get_available_packet_count() > 0: var packet = udp_peer.get_packet() - var message: String = packet.get_string_from_utf8() - print("Received: ", message) + var packet_json: Dictionary = JSON.parse_string(packet.get_string_from_utf8()) + match packet_json.type: + "init_success": + player_join.emit(packet_json.player_client, Array(packet_json.clients)) + "message": + print(packet_json.msg) + "move": + player_move.emit(packet_json.session_id, Vector2(packet_json.direction.x,packet_json.direction.y)) + "disconnect": + player_disconnect.emit(packet_json.session_id) func send_message(message: String) -> void: var payload: Dictionary = { @@ -34,12 +43,18 @@ func send_payload(payload: Dictionary): udp_peer.set_dest_address(server_ip, server_port) udp_peer.put_packet(packet) -func move(vec: Vector2i): +func move(position_mod: Vector2): var payload: Dictionary = { "type": "move", "direction": { - "x": vec.x, - "y": vec.y + "x": position_mod.x, + "y": position_mod.y } } send_payload(payload) + +func _exit_tree(): + var payload: Dictionary = { + "type": "disconnect" + } + send_payload(payload) diff --git a/server/main.go b/server/main.go index 3d08098..2db86ec 100644 --- a/server/main.go +++ b/server/main.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io" + r "math/rand" "net" "os" "time" @@ -14,13 +15,32 @@ import ( ) type Payload struct { + SessionID string `json:"session_id,omitempty"` + Type string `json:"type"` + Message string `json:"msg,omitempty"` + Direction *Vector2 `json:"direction,omitempty"` +} + +type InitPayload struct { + Type string `json:"type"` + PlayerClient *Client `json:"player_client"` + Clients []*Client `json:"clients"` +} + +type DisconnectPayload struct { Type string `json:"type"` - Message string `json:"msg,omitempty"` - Direction *struct { - X int `json:"x,omitempty"` - Y int `json:"y,omitempty"` - } `json:"direction,omitempty"` - SessionID string `json:"session_id,omitempty"` + SessionID string `json:"session_id"` +} + +type Client struct { + SessionID string `json:"session_id"` + LastSeen time.Time `json:"last_seen"` + Positon *Vector2 `json:"position"` +} + +type Vector2 struct { + X float64 `json:"x,omitempty"` + Y float64 `json:"y,omitempty"` } // Generate a unique session ID @@ -55,8 +75,7 @@ func main() { fmt.Println("Server running on " + port) buf := make([]byte, 1024) - clients := make(map[string]string) // Map to keep track of clients and their session IDs - clientLastSeen := make(map[string]time.Time) // Map to track the last seen time of each client + clients := make(map[string]*Client) // Map to keep track of clients and their session IDs for { n, addr, err := conn.ReadFromUDP(buf) @@ -75,63 +94,65 @@ func main() { // Use the client's IP and port as the key to uniquely identify the connection key := addr.String() - // Update the last seen time for the client - clientLastSeen[key] = time.Now() - // Check if the client's connection already has a session ID if _, ok := clients[key]; !ok { // The client is new, generate a session ID sessionID := generateSessionID() - clients[key] = sessionID + clients[key] = &Client{SessionID: sessionID, LastSeen: time.Now(), Positon: &Vector2{X: randomFloatInRange(300, 600), Y: randomFloatInRange(300, 600)}} // Initialize a new Client struct and store its pointer // Send the session ID back to the client - responsePayload := Payload{Type: "init_success", SessionID: sessionID} - response, _ := json.Marshal(responsePayload) - conn.WriteToUDP(response, addr) + clientsSlice := make([]*Client, 0, len(clients)) + for _, client := range clients { + clientsSlice = append(clientsSlice, client) + } + responsePayload := InitPayload{Type: "init_success", PlayerClient: clients[key], Clients: clientsSlice} + broadcastMessage(conn, clients, responsePayload) } + clients[key].LastSeen = time.Now() + switch payload.Type { case "init": - fmt.Printf("Received initiation generated SessionID: %s\n", clients[key]) + fmt.Printf("Received initiation generated SessionID: %s\n", clients[key].SessionID) case "move": - fmt.Printf("Received move message from Session ID: %s : X=%d, Y=%d\n", clients[key], payload.Direction.X, payload.Direction.Y) - // Handle movement logic here + fmt.Printf("Received move message from Session ID: %s : X=%f, Y=%f\n", clients[key].SessionID, payload.Direction.X, payload.Direction.Y) + broadcastMessage(conn, clients, payload) + case "disconnect": // Disconnect the client + fmt.Printf("Client %s disconnected\n", clients[key].SessionID) + broadcastMessage(conn, clients, DisconnectPayload{Type: "disconnect", SessionID: clients[key].SessionID}) delete(clients, key) - delete(clientLastSeen, key) - fmt.Printf("Client %s disconnected\n", clients[key]) case "message": - fmt.Printf("Received message from Session ID: %s : %s\n", clients[key], payload.Message) - broadcastMessage(conn, clients, payload.Message) + fmt.Printf("Received message from Session ID: %s : %s\n", clients[key].SessionID, payload.Message) + broadcastMessage(conn, clients, payload) default: - fmt.Printf("Received unknown message type Session ID: %s\n", clients[key]) + fmt.Printf("Received unknown message type Session ID: %s\n", clients[key].SessionID) } // Check for disconnected clients and reset their session ID - for clientKey, lastSeen := range clientLastSeen { - if time.Since(lastSeen) > 5*time.Minute { // 5 minutes timeout + for clientKey, client := range clients { + if time.Since(client.LastSeen) > 5*time.Minute { // 5 minutes timeout delete(clients, clientKey) - delete(clientLastSeen, clientKey) - fmt.Printf("Client %s disconnected and session ID reset\n", clients[clientKey]) + fmt.Printf("Client %s disconnected and session ID reset\n", clients[clientKey].SessionID) } } } } -func broadcastMessage(conn *net.UDPConn, clients map[string]string, message string) { - for clientKey, sessionID := range clients { - // Parse the clientKey to get the *net.UDPAddr +func broadcastMessage(conn *net.UDPConn, clients map[string]*Client, payload interface{}) { + for clientKey := range clients { clientAddr, err := net.ResolveUDPAddr("udp", clientKey) if err != nil { fmt.Println("Error resolving UDP address:", err) continue } - payload := Payload{ - Type: "message", - Message: message, - SessionID: sessionID, - } response, _ := json.Marshal(payload) conn.WriteToUDP(response, clientAddr) } } + +func randomFloatInRange(min, max float64) float64 { + seed := time.Now().UnixNano() + rf := r.New(r.NewSource(seed)) + return min + rf.Float64()*(max-min) +}