Basic synced player movement
This commit is contained in:
parent
f85469dad4
commit
07f02d5a32
7 changed files with 193 additions and 112 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -9,5 +9,6 @@
|
|||
debug/
|
||||
release/
|
||||
target/
|
||||
*.tmp
|
||||
|
||||
lib/
|
||||
116
client/danmaku!/network_manager.gd
Normal file
116
client/danmaku!/network_manager.gd
Normal 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
|
||||
|
|
@ -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
|
||||
func get_input():
|
||||
if Input.is_action_pressed("Slow Mode"):
|
||||
speed = 200
|
||||
else:
|
||||
speed = 400
|
||||
|
||||
var predicted_tick = 0
|
||||
var delta_counter = 0
|
||||
var lerp_factor := 0.0
|
||||
var bullets = []
|
||||
var input_direction = Input.get_vector("Left", "Right", "Up", "Down")
|
||||
velocity = input_direction * speed
|
||||
|
||||
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 _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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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), ¶ms); err != nil {
|
||||
return "", err
|
||||
}*/
|
||||
|
||||
modulename := "battle-royale"
|
||||
|
||||
if matchId, err := nk.MatchCreate(ctx, modulename, make(map[string]interface{})); err != nil {
|
||||
|
|
|
|||
Reference in a new issue