Archived
1
0
Fork 0

Basic synced player movement

This commit is contained in:
Sebastian Benjamin 2025-02-08 23:01:29 -08:00
parent f85469dad4
commit 07f02d5a32
7 changed files with 193 additions and 112 deletions

1
.gitignore vendored
View file

@ -9,5 +9,6 @@
debug/
release/
target/
*.tmp
lib/

View file

@ -0,0 +1,116 @@
extends Node
var nakama_client: NakamaClient
var nakama_session: NakamaSession
var nakama_socket: NakamaSocket
const SERVER_WIDTH = 90.0
const SERVER_HEIGHT = 160.0
var predicted_tick = 0
var delta_counter = 0
var bullet_lerp_factor := 0.0
var bullets = []
var current_match_id = ""
func _ready() -> void:
print("Attempting auth.")
await attempt_auth()
print("Attempting to create debug match.")
await create_and_join_debug_match()
nakama_socket.received_match_state.connect(self._on_match_state)
func _process(delta: float) -> void:
if current_match_id == "":
return
predict_tick_and_broadcast(delta)
for bullet in bullets:
var prev_pos = bullet.get_current_pos(predicted_tick)
var next_pos = bullet.get_current_pos(predicted_tick + 1)
var interpolated_pos = prev_pos.lerp(next_pos, bullet_lerp_factor)
bullet.position = world_to_screen(interpolated_pos)
#var screen_size = get_viewport().size
#bullets = bullets.filter(func(bullet):
# return bullet.position.x >= 0 and bullet.position.x <= screen_size.x and \
# bullet.position.y >= 0 and bullet.position.y <= screen_size.y
#)
func _on_match_state(p_state : NakamaRTAPI.MatchData):
match p_state.op_code:
2: # Server state update
var data = JSON.parse_string(p_state.data)
var bullet = DanmakuBullet.new()
bullet.setup_bullet(
int(data["class"]),
int(data["tick"]),
data["x"],
data["y"],
data["vel_x"],
data["vel_y"])
bullet.position = world_to_screen(bullet.get_current_pos(int(data["tick"])))
bullet.texture = load("res://test-bullet.png")
add_child(bullet)
bullets.append(bullet)
#delta_counter = 0
#predicted_tick = int(data["tick"])
func attempt_auth() -> void:
nakama_client = Nakama.create_client("defaultkey", "127.0.0.1", 7350, "http")
nakama_session = await nakama_client.authenticate_device_async(OS.get_unique_id())
nakama_socket = Nakama.create_socket_from(nakama_client)
var connected: NakamaAsyncResult = await nakama_socket.connect_async(nakama_session)
if connected.is_exception():
print("An error occured when creating nakama socket: %s" % connected)
return
print("Oh baby we're ready.")
func create_and_join_debug_match() -> void:
var response: NakamaAPI.ApiRpc = await nakama_client.rpc_async(nakama_session, "manual_force_create_br_match_rpc")
if response.is_exception():
print("An error occurred when calling manual_force_create_br_match_rpc: %s" % response)
return
var debug_br_match: NakamaRTAPI.Match = await nakama_socket.join_match_async(response.payload)
if debug_br_match.is_exception():
print("An error occurred when joining debug BR match: %s" % response)
return
else:
current_match_id = response.payload
func world_to_screen(server_pos: Vector2) -> Vector2:
var screen_size = get_viewport().size
var scale_x = screen_size.x / SERVER_WIDTH
var scale_y = screen_size.y / SERVER_HEIGHT
var client_x = server_pos.x * scale_x
var client_y = server_pos.y * scale_y
return Vector2(client_x, client_y)
func screen_to_world(client_pos: Vector2) -> Vector2:
var screen_size = get_viewport().size
var scale_x = SERVER_WIDTH / screen_size.x
var scale_y = SERVER_HEIGHT / screen_size.y
var server_x = client_pos.x * scale_x
var server_y = client_pos.y * scale_y
return Vector2(server_x, server_y)
func predict_tick_and_broadcast(delta):
delta_counter += delta
# New tick, broadcast player inputs
if delta_counter >= 0.05:
predicted_tick += 1
delta_counter = 0
var position = screen_to_world(get_node("../Player").position)
var json_string = JSON.stringify({"x": position.x, "y": position.y})
nakama_socket.send_match_state_async(current_match_id, 0, json_string)
bullet_lerp_factor = delta_counter / 0.05

View file

@ -1,95 +1,17 @@
extends Node2D
var nakama_client: NakamaClient
var nakama_session: NakamaSession
var nakama_socket: NakamaSocket
@export var speed = 400
var velocity = 0
const SERVER_WIDTH = 90.0
const SERVER_HEIGHT = 160.0
var predicted_tick = 0
var delta_counter = 0
var lerp_factor := 0.0
var bullets = []
func _ready() -> void:
print("Attempting auth.")
await attempt_auth()
print("Attempting to create debug match.")
await create_and_join_debug_match()
nakama_socket.received_match_state.connect(self._on_match_state)
func _process(delta: float) -> void:
delta_counter += delta
if delta_counter >= 0.05:
predicted_tick += 1
delta_counter = 0 # Reset counter
lerp_factor = delta_counter / 0.05 # Normalize factor between 0 and 1
for bullet in bullets:
var prev_pos = bullet.get_current_pos(predicted_tick)
var next_pos = bullet.get_current_pos(predicted_tick + 1)
var interpolated_pos = prev_pos.lerp(next_pos, lerp_factor)
bullet.position = world_to_screen(interpolated_pos)
#var screen_size = get_viewport().size
#bullets = bullets.filter(func(bullet):
# return bullet.position.x >= 0 and bullet.position.x <= screen_size.x and \
# bullet.position.y >= 0 and bullet.position.y <= screen_size.y
#)
func get_input():
if Input.is_action_pressed("Slow Mode"):
speed = 200
else:
speed = 400
var input_direction = Input.get_vector("Left", "Right", "Up", "Down")
velocity = input_direction * speed
func _on_match_state(p_state : NakamaRTAPI.MatchData):
match p_state.op_code:
2: # Spawn bullet
var data = JSON.parse_string(p_state.data)
var bullet = DanmakuBullet.new()
bullet.setup_bullet(
int(data["class"]),
int(data["tick"]),
data["x"],
data["y"],
data["vel_x"],
data["vel_y"])
bullet.position = world_to_screen(bullet.get_current_pos(int(data["tick"])))
bullet.texture = load("res://test-bullet.png")
add_child(bullet)
bullets.append(bullet)
delta_counter = 0
predicted_tick = int(data["tick"])
func attempt_auth() -> void:
nakama_client = Nakama.create_client("defaultkey", "127.0.0.1", 7350, "http")
nakama_session = await nakama_client.authenticate_device_async(OS.get_unique_id())
nakama_socket = Nakama.create_socket_from(nakama_client)
var connected: NakamaAsyncResult = await nakama_socket.connect_async(nakama_session)
if connected.is_exception():
print("An error occured when creating nakama socket: %s" % connected)
return
print("Oh baby we're ready.")
func create_and_join_debug_match() -> void:
var response: NakamaAPI.ApiRpc = await nakama_client.rpc_async(nakama_session, "manual_force_create_br_match_rpc")
if response.is_exception():
print("An error occurred when calling manual_force_create_br_match_rpc: %s" % response)
return
var debug_br_match: NakamaRTAPI.Match = await nakama_socket.join_match_async(response.payload)
if debug_br_match.is_exception():
print("An error occurred when joining debug BR match: %s" % response)
return
func world_to_screen(server_pos: Vector2) -> Vector2:
var screen_size = get_viewport().size
var scale_x = screen_size.x / SERVER_WIDTH
var scale_y = screen_size.y / SERVER_HEIGHT
var client_x = server_pos.x * scale_x
var client_y = server_pos.y * scale_y
return Vector2(client_x, client_y)
func _physics_process(delta):
get_input()
position += velocity * delta

View file

@ -1,6 +1,10 @@
[gd_scene load_steps=2 format=3 uid="uid://cd3tqt7hr5pqs"]
[gd_scene load_steps=3 format=3 uid="uid://cd3tqt7hr5pqs"]
[ext_resource type="Script" path="res://danmaku!/player.gd" id="1_l6typ"]
[ext_resource type="Texture2D" uid="uid://bs3fntlmlqpt2" path="res://icon.svg" id="2_j7sx3"]
[node name="Player" type="Node2D"]
script = ExtResource("1_l6typ")
[node name="Sprite2D" type="Sprite2D" parent="."]
texture = ExtResource("2_j7sx3")

View file

@ -1,7 +1,11 @@
[gd_scene load_steps=2 format=3 uid="uid://b1m2pclbncn68"]
[gd_scene load_steps=3 format=3 uid="uid://b1m2pclbncn68"]
[ext_resource type="PackedScene" uid="uid://cd3tqt7hr5pqs" path="res://danmaku!/player.tscn" id="1_jeq34"]
[ext_resource type="Script" path="res://danmaku!/network_manager.gd" id="2_3453q"]
[node name="Testworld" type="Node2D"]
[node name="Player" parent="." instance=ExtResource("1_jeq34")]
[node name="NetworkManager" type="Node" parent="."]
script = ExtResource("2_3453q")

View file

@ -24,3 +24,31 @@ Nakama="*res://addons/com.heroiclabs.nakama/Nakama.gd"
animation_library={
"animation/fps": 120.0
}
[input]
Left={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
Right={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194321,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
Up={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
Down={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
"Slow Mode"={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}

View file

@ -12,7 +12,6 @@ import (
"context"
"database/sql"
"encoding/json"
"fmt"
"math/rand"
"slices"
@ -22,7 +21,8 @@ import (
const (
MATCH_LOADING = iota
MATCH_START
SPAWN_BULLET
STATE_UPDATE
FINAL_PHASE
MATCH_END
)
@ -35,6 +35,11 @@ type PlayerStageState struct {
bullets []*C.Bullet
}
type PlayerMessageData struct {
X float64 `json:"x"`
Y float64 `json:"y"`
}
type PresenceState struct { // present time! hahahahahahahah!
presence runtime.Presence
stageState PlayerStageState
@ -156,7 +161,8 @@ func (m *BattleRoyaleMatch) MatchLoop(ctx context.Context, logger runtime.Logger
// Test bullet spawning
if tick%1 == 0 {
for _, v := range lobbyState.presences {
vel := rand.Float64()*(STAGE_WIDTH/float64(lobbyState.tickRate)) + 1.0
velx := rand.Float64()*(STAGE_WIDTH/float64(lobbyState.tickRate)) + 1.0
vely := rand.Float64()*(STAGE_WIDTH/float64(lobbyState.tickRate)) + 1.0
vel_x_sign := 2*rand.Intn(2) - 1
vel_y_sign := 2*rand.Intn(2) - 1
@ -165,8 +171,8 @@ func (m *BattleRoyaleMatch) MatchLoop(ctx context.Context, logger runtime.Logger
C.int64_t(tick),
C.double(STAGE_WIDTH*rand.Float64()),
C.double(STAGE_HEIGHT*rand.Float64()),
C.double(float64(vel_x_sign)*vel),
C.double(float64(vel_y_sign)*vel),
C.double(float64(vel_x_sign)*velx),
C.double(float64(vel_y_sign)*vely),
)
var x, y C.double
@ -177,8 +183,8 @@ func (m *BattleRoyaleMatch) MatchLoop(ctx context.Context, logger runtime.Logger
"tick": tick,
"x": float64(x),
"y": float64(y),
"vel_x": float64(vel_x_sign) * vel,
"vel_y": float64(vel_y_sign) * vel,
"vel_x": float64(vel_x_sign) * velx,
"vel_y": float64(vel_y_sign) * vely,
}
data, err := json.Marshal(bulletData)
@ -187,19 +193,25 @@ func (m *BattleRoyaleMatch) MatchLoop(ctx context.Context, logger runtime.Logger
} else {
v.stageState.bullets = append(v.stageState.bullets, bullet)
reliable := true
dispatcher.BroadcastMessage(SPAWN_BULLET, data, nil, nil, reliable)
dispatcher.BroadcastMessage(STATE_UPDATE, data, nil, nil, reliable)
}
}
}
// Bullet cleanup
for _, v := range lobbyState.presences {
for _, bullet := range v.stageState.bullets {
var x, y C.double
C.bullet_get_current_pos(bullet, C.int64_t(tick), &x, &y)
fmt.Printf("Bullet at (%.2f, %.2f)\n", float64(x), float64(y))
// Respond to player input
for _, msg := range messages {
var pos PlayerMessageData
if err := json.Unmarshal(msg.GetData(), &pos); err != nil {
logger.Warn("Failed to parse input: %v", err)
continue
}
lobbyState.presences[msg.GetSessionId()].stageState.xPos = pos.X
lobbyState.presences[msg.GetSessionId()].stageState.yPos = pos.Y
}
// Bullet cleanup when off board
for _, v := range lobbyState.presences {
v.stageState.bullets = slices.DeleteFunc(v.stageState.bullets, func(b *C.Bullet) bool {
if C.bullet_beyond_kill_boundary(b, C.int64_t(tick)) {
C.destroy_bullet(b)
@ -214,12 +226,6 @@ func (m *BattleRoyaleMatch) MatchLoop(ctx context.Context, logger runtime.Logger
// RPC for force-creating a match for debugging/development, separate from the matchmaking process
func ManualForceCreateBRMatchRPC(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
/*params := make(map[string]interface{})
if err := json.Unmarshal([]byte(payload), &params); err != nil {
return "", err
}*/
modulename := "battle-royale"
if matchId, err := nk.MatchCreate(ctx, modulename, make(map[string]interface{})); err != nil {