Implement colliders
This commit is contained in:
parent
d5cf0528f8
commit
5d11565a4d
12 changed files with 177 additions and 40 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -12,3 +12,4 @@ target/
|
|||
*.tmp
|
||||
|
||||
lib/
|
||||
*.vscode/
|
||||
|
|
@ -39,6 +39,7 @@ func _on_match_state(p_state : NakamaRTAPI.MatchData):
|
|||
match p_state.op_code:
|
||||
2:
|
||||
var data = JSON.parse_string(p_state.data)
|
||||
print(data)
|
||||
|
||||
# Set player position given server bounds-checking
|
||||
if data["forcePlayerPos"]:
|
||||
|
|
@ -52,6 +53,7 @@ func _on_match_state(p_state : NakamaRTAPI.MatchData):
|
|||
int(b["tick"]),
|
||||
b["x"],
|
||||
b["y"],
|
||||
b["radius"],
|
||||
b["vel_x"],
|
||||
b["vel_y"])
|
||||
bullet.texture = load("res://test-bullet.png")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ var velocity := Vector2.ZERO
|
|||
|
||||
func get_input():
|
||||
if Input.is_action_pressed("Slow Mode"):
|
||||
speed = 40
|
||||
speed = 30
|
||||
else:
|
||||
speed = 80
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
use shared::bullet::Bullet;
|
||||
use shared::collision::Circle;
|
||||
|
||||
// Bullet routines
|
||||
#[no_mangle]
|
||||
pub extern "C" fn new_bullet(
|
||||
class: u8, spawn_time: i64, spawn_x: f64, spawn_y: f64, param_x: f64, param_y: f64
|
||||
class: u8, spawn_time: i64, spawn_x: f64, spawn_y: f64, radius: f64, param_x: f64, param_y: f64
|
||||
) -> *mut Bullet {
|
||||
let bullet = Bullet::new(
|
||||
class,
|
||||
spawn_time,
|
||||
spawn_x,
|
||||
spawn_y,
|
||||
radius,
|
||||
[param_x, param_y],
|
||||
);
|
||||
Box::into_raw(Box::new(bullet))
|
||||
|
|
@ -42,3 +45,39 @@ pub extern "C" fn destroy_bullet(bullet: *mut Bullet) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn bullet_collides_with(tick: i64, bullet: *mut Bullet, c: *const Circle) -> bool {
|
||||
if bullet.is_null() || c.is_null() {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsafe { (*bullet).collides_with(tick, &*c) }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Collision
|
||||
#[no_mangle]
|
||||
pub extern "C" fn new_circle(x: f64, y: f64, radius: f64) -> *mut Circle {
|
||||
let circle = Circle::new(x, y, radius);
|
||||
Box::into_raw(Box::new(circle))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn destroy_circle(circle: *mut Circle) {
|
||||
if !circle.is_null() {
|
||||
unsafe {
|
||||
drop(Box::from_raw(circle));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn circle_collides_with(c1: *const Circle, c2: *const Circle) -> bool {
|
||||
if c1.is_null() || c2.is_null() {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsafe { (*c1).collides_with(&*c2) }
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
mod ffi;
|
||||
|
||||
pub use shared::bullet::Bullet;
|
||||
pub use shared::collision::Circle;
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
use godot::prelude::*;
|
||||
use shared::bullet::Bullet;
|
||||
use godot::classes::{ Sprite2D, ISprite2D };
|
||||
use crate::collision::DanmakuCircle;
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(base=Sprite2D)]
|
||||
struct DanmakuBullet {
|
||||
bullet_state: Bullet,
|
||||
|
||||
base: Base<Sprite2D>,
|
||||
}
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ struct DanmakuBullet {
|
|||
impl ISprite2D for DanmakuBullet {
|
||||
fn init(base: Base<Sprite2D>) -> Self {
|
||||
Self {
|
||||
bullet_state: Bullet::new(0, 0, 0.0, 0.0, [0.0, 0.0]),
|
||||
bullet_state: Bullet::new(0, 0, 0.0, 0.0, 0.0, [0.0, 0.0]),
|
||||
base,
|
||||
}
|
||||
}
|
||||
|
|
@ -23,8 +23,8 @@ impl ISprite2D for DanmakuBullet {
|
|||
#[godot_api]
|
||||
impl DanmakuBullet {
|
||||
#[func]
|
||||
fn setup_bullet(&mut self, class: u8, spawn_time: i64, spawn_x: f64, spawn_y: f64, param_x: f64, param_y: f64) {
|
||||
self.bullet_state = Bullet::new(class, spawn_time, spawn_x, spawn_y, [param_x, param_y]);
|
||||
fn setup_bullet(&mut self, class: u8, spawn_time: i64, spawn_x: f64, spawn_y: f64, radius: f64, param_x: f64, param_y: f64) {
|
||||
self.bullet_state = Bullet::new(class, spawn_time, spawn_x, spawn_y, radius, [param_x, param_y]);
|
||||
}
|
||||
|
||||
#[func]
|
||||
|
|
@ -38,4 +38,8 @@ impl DanmakuBullet {
|
|||
self.bullet_state.beyond_kill_boundary(tick)
|
||||
}
|
||||
|
||||
#[func]
|
||||
fn collides_with_circle(&self, tick: i64, circle: Gd<DanmakuCircle>) -> bool {
|
||||
self.bullet_state.collides_with(tick, &circle.bind().circle_state)
|
||||
}
|
||||
}
|
||||
54
godot-extension/src/collision.rs
Normal file
54
godot-extension/src/collision.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
use godot::prelude::*;
|
||||
use shared::collision::Circle;
|
||||
use godot::classes::{ Node2D, INode2D };
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(base=Node2D)]
|
||||
pub struct DanmakuCircle {
|
||||
pub circle_state: Circle,
|
||||
base: Base<Node2D>,
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl INode2D for DanmakuCircle {
|
||||
fn init(base: Base<Node2D>) -> Self {
|
||||
Self {
|
||||
circle_state: Circle::new(0.0, 0.0, 0.0),
|
||||
base,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl DanmakuCircle {
|
||||
#[func]
|
||||
fn setup_circle(&mut self, x: f64, y: f64, radius: f64) {
|
||||
self.circle_state = Circle::new(x, y, radius);
|
||||
}
|
||||
|
||||
#[func]
|
||||
fn get_position(&self) -> Vector2 {
|
||||
Vector2::new(self.circle_state.x as f32, self.circle_state.y as f32)
|
||||
}
|
||||
|
||||
#[func]
|
||||
fn set_position(&mut self, new_x: f64, new_y: f64) {
|
||||
self.circle_state.x = new_x;
|
||||
self.circle_state.y = new_y;
|
||||
}
|
||||
|
||||
#[func]
|
||||
fn get_radius(&self) -> f64 {
|
||||
self.circle_state.radius
|
||||
}
|
||||
|
||||
#[func]
|
||||
fn set_radius(&mut self, new_radius: f64) {
|
||||
self.circle_state.radius = new_radius;
|
||||
}
|
||||
|
||||
#[func]
|
||||
fn collides_with(&self, other: Gd<DanmakuCircle>) -> bool {
|
||||
self.circle_state.collides_with(&other.bind().circle_state)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
mod bullet;
|
||||
mod collision;
|
||||
|
||||
use godot::prelude::*;
|
||||
struct Danmaku;
|
||||
|
|
|
|||
|
|
@ -40,20 +40,22 @@ const (
|
|||
// Interface for registering match handlers
|
||||
type BattleRoyaleMatch struct{}
|
||||
|
||||
type Position struct {
|
||||
type PlayerStageState struct {
|
||||
col *C.Circle
|
||||
bullets []*C.Bullet
|
||||
updatePlayerPos bool
|
||||
health int
|
||||
graze int
|
||||
}
|
||||
|
||||
type PlayerUpdate struct {
|
||||
X float64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
}
|
||||
|
||||
type PlayerStageState struct {
|
||||
pos Position
|
||||
bullets []*C.Bullet
|
||||
updatePlayerPos bool
|
||||
}
|
||||
|
||||
type GameTickUpdate struct {
|
||||
Tick int64 `json:"tick"`
|
||||
PlayerPos Position `json:"playerPos"`
|
||||
PlayerPos map[string]interface{} `json:"playerPos"`
|
||||
NewBullets []map[string]interface{} `json:"newBullets"`
|
||||
ForcePlayerPos bool `json:"forcePlayerPos"`
|
||||
}
|
||||
|
|
@ -109,12 +111,10 @@ func (m *BattleRoyaleMatch) MatchJoin(ctx context.Context, logger runtime.Logger
|
|||
lobbyState.presences[presences[i].GetSessionId()] = &PresenceState{
|
||||
presence: presences[i],
|
||||
stageState: PlayerStageState{
|
||||
pos: Position{
|
||||
X: STAGE_WIDTH * 0.5,
|
||||
Y: STAGE_HEIGHT - STAGE_HEIGHT*0.1,
|
||||
},
|
||||
col: C.new_circle(C.double(STAGE_WIDTH*0.5), C.double(STAGE_HEIGHT-STAGE_HEIGHT*0.1), C.double(STAGE_WIDTH*0.09)),
|
||||
bullets: []*C.Bullet{},
|
||||
updatePlayerPos: true,
|
||||
health: 3,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -138,7 +138,7 @@ func (m *BattleRoyaleMatch) MatchLeave(ctx context.Context, logger runtime.Logge
|
|||
for _, bullet := range playerState.stageState.bullets {
|
||||
C.destroy_bullet(bullet)
|
||||
}
|
||||
|
||||
C.destroy_circle(playerState.stageState.col)
|
||||
delete(lobbyState.presences, sessionID)
|
||||
}
|
||||
}
|
||||
|
|
@ -197,21 +197,21 @@ func (m *BattleRoyaleMatch) MatchLoop(ctx context.Context, logger runtime.Logger
|
|||
}
|
||||
|
||||
// Parse player message
|
||||
var pos Position
|
||||
if err := json.Unmarshal(msg.GetData(), &pos); err != nil {
|
||||
var update PlayerUpdate
|
||||
if err := json.Unmarshal(msg.GetData(), &update); err != nil {
|
||||
logger.Warn("Failed to parse input: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Player movement bounds detection
|
||||
clampedX := pos.X < 0 || pos.X > STAGE_WIDTH
|
||||
clampedY := pos.Y < 0 || pos.Y > STAGE_HEIGHT
|
||||
clampedX := update.X < 0 || update.X > STAGE_WIDTH
|
||||
clampedY := update.Y < 0 || update.Y > STAGE_HEIGHT
|
||||
|
||||
pos.X = math.Max(0, math.Min(pos.X, STAGE_WIDTH))
|
||||
pos.Y = math.Max(0, math.Min(pos.Y, STAGE_HEIGHT))
|
||||
update.X = math.Max(0, math.Min(update.X, STAGE_WIDTH))
|
||||
update.Y = math.Max(0, math.Min(update.Y, STAGE_HEIGHT))
|
||||
|
||||
lobbyState.presences[msg.GetSessionId()].stageState.pos.X = pos.X
|
||||
lobbyState.presences[msg.GetSessionId()].stageState.pos.Y = pos.Y
|
||||
lobbyState.presences[msg.GetSessionId()].stageState.col.x = C.double(update.X)
|
||||
lobbyState.presences[msg.GetSessionId()].stageState.col.y = C.double(update.Y)
|
||||
|
||||
if clampedX || clampedY {
|
||||
lobbyState.presences[msg.GetSessionId()].stageState.updatePlayerPos = true
|
||||
|
|
@ -235,6 +235,7 @@ func (m *BattleRoyaleMatch) MatchLoop(ctx context.Context, logger runtime.Logger
|
|||
if tick%10 == 0 {
|
||||
velx := (rand.Float64() * STAGE_WIDTH) / float64(lobbyState.tickRate)
|
||||
vely := (rand.Float64() * STAGE_WIDTH) / float64(lobbyState.tickRate)
|
||||
radius := 0.03
|
||||
vel_x_sign := 2*rand.Intn(2) - 1
|
||||
vel_y_sign := 2*rand.Intn(2) - 1
|
||||
|
||||
|
|
@ -243,6 +244,7 @@ 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(radius),
|
||||
C.double(float64(vel_x_sign)*velx),
|
||||
C.double(float64(vel_y_sign)*vely),
|
||||
)
|
||||
|
|
@ -257,6 +259,7 @@ func (m *BattleRoyaleMatch) MatchLoop(ctx context.Context, logger runtime.Logger
|
|||
"tick": tick,
|
||||
"x": float64(x),
|
||||
"y": float64(y),
|
||||
"radius": float64(radius),
|
||||
"vel_x": float64(vel_x_sign) * velx,
|
||||
"vel_y": float64(vel_y_sign) * vely,
|
||||
}
|
||||
|
|
@ -266,9 +269,9 @@ func (m *BattleRoyaleMatch) MatchLoop(ctx context.Context, logger runtime.Logger
|
|||
|
||||
var tickData = GameTickUpdate{
|
||||
Tick: tick,
|
||||
PlayerPos: Position{
|
||||
X: v.stageState.pos.X,
|
||||
Y: v.stageState.pos.Y,
|
||||
PlayerPos: map[string]interface{}{
|
||||
"x": v.stageState.col.x,
|
||||
"y": v.stageState.col.y,
|
||||
},
|
||||
NewBullets: newBulletsToBroadcast,
|
||||
ForcePlayerPos: v.stageState.updatePlayerPos,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use crate::collision;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Bullet {
|
||||
|
|
@ -5,18 +7,20 @@ pub struct Bullet {
|
|||
pub spawn_time: i64,
|
||||
pub spawn_x: f64,
|
||||
pub spawn_y: f64,
|
||||
pub radius: f64,
|
||||
pub parameters: [f64; 2],
|
||||
}
|
||||
|
||||
impl Bullet {
|
||||
pub const BULLET_LINEAR: u8 = 0;
|
||||
|
||||
pub fn new(class: u8, spawn_time: i64, spawn_x: f64, spawn_y: f64, parameters: [f64; 2]) -> Self {
|
||||
pub fn new(class: u8, spawn_time: i64, spawn_x: f64, spawn_y: f64, radius: f64, parameters: [f64; 2]) -> Self {
|
||||
Self {
|
||||
class,
|
||||
spawn_time,
|
||||
spawn_x,
|
||||
spawn_y,
|
||||
radius,
|
||||
parameters,
|
||||
}
|
||||
}
|
||||
|
|
@ -43,4 +47,9 @@ impl Bullet {
|
|||
|| y < -BULLET_KILL_BUFFER_WIDTH
|
||||
|| y > STAGE_HEIGHT + BULLET_KILL_BUFFER_WIDTH
|
||||
}
|
||||
|
||||
pub fn collides_with(&self, tick: i64, circle: &collision::Circle) -> bool {
|
||||
let (current_x, current_y) = self.get_current_pos(tick);
|
||||
circle.collides_with(&collision::Circle::new(current_x, current_y, self.radius))
|
||||
}
|
||||
}
|
||||
22
shared/src/collision.rs
Normal file
22
shared/src/collision.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Circle {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
pub radius: f64,
|
||||
}
|
||||
|
||||
impl Circle {
|
||||
pub fn new(x: f64, y: f64, radius: f64) -> Self {
|
||||
Circle { x, y, radius }
|
||||
}
|
||||
|
||||
pub fn collides_with(&self, other: &Circle) -> bool {
|
||||
let dx = self.x - other.x;
|
||||
let dy = self.y - other.y;
|
||||
let distance_squared = dx * dx + dy * dy;
|
||||
let radius_sum = self.radius + other.radius;
|
||||
|
||||
distance_squared <= radius_sum * radius_sum
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1,2 @@
|
|||
pub mod bullet;
|
||||
pub mod collision;
|
||||
Reference in a new issue