From 4c39bd120b00ceb2a1ff736c09addd0524567d57 Mon Sep 17 00:00:00 2001 From: Joey Eamigh <55670930+JoeyEamigh@users.noreply.github.com> Date: Fri, 10 Oct 2025 18:48:42 -0400 Subject: [PATCH] finish example game (minus ui lol) --- godot/Rust.gdextension | 3 ++ godot/mob.tscn | 64 +++++++++++++++++++++++++++ godot/scene.tscn | 33 ++++++++++++++ rust/Cargo.lock | 24 ++++------- rust/Cargo.toml | 4 +- rust/src/lib.rs | 2 + rust/src/mob.rs | 41 ++++++++++++++++++ rust/src/player.rs | 55 +++++++++++++++++++----- rust/src/scene.rs | 98 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 296 insertions(+), 28 deletions(-) create mode 100644 godot/mob.tscn create mode 100644 godot/scene.tscn create mode 100644 rust/src/mob.rs create mode 100644 rust/src/scene.rs diff --git a/godot/Rust.gdextension b/godot/Rust.gdextension index 89ab2a2..ef9692c 100644 --- a/godot/Rust.gdextension +++ b/godot/Rust.gdextension @@ -66,5 +66,8 @@ reloadable = true "windows.editor.x86_64" = "res://../rust/target/x86_64-pc-windows-msvc/debug/godottest_rs.dll" [icons] +MainScene = "res://addons/rust/NodeRustFerris.svg" +CrashFucker = "res://addons/rust/NodeRustFerris.svg" +Mob = "res://addons/rust/NodeRustFerris.svg" Player = "res://addons/rust/NodeRustFerris.svg" AsyncRuntime = "res://addons/rust/NodeRustFerris.svg" diff --git a/godot/mob.tscn b/godot/mob.tscn new file mode 100644 index 0000000..2d63b98 --- /dev/null +++ b/godot/mob.tscn @@ -0,0 +1,64 @@ +[gd_scene load_steps=9 format=3 uid="uid://dnn7s3hr6nkxo"] + +[ext_resource type="Texture2D" uid="uid://4s7lh46ylxx8" path="res://art/enemyFlyingAlt_1.png" id="1_b3mxk"] +[ext_resource type="Texture2D" uid="uid://cco4veogb0frk" path="res://art/enemyFlyingAlt_2.png" id="2_1qmh0"] +[ext_resource type="Texture2D" uid="uid://cjcxwln2jke6f" path="res://art/enemySwimming_1.png" id="3_gfurk"] +[ext_resource type="Texture2D" uid="uid://cgtr6ouepyo2u" path="res://art/enemySwimming_2.png" id="4_ieysi"] +[ext_resource type="Texture2D" uid="uid://bs171ydu8mvir" path="res://art/enemyWalking_1.png" id="5_cixyi"] +[ext_resource type="Texture2D" uid="uid://ogi16vdypeh3" path="res://art/enemyWalking_2.png" id="6_7ulmv"] + +[sub_resource type="SpriteFrames" id="SpriteFrames_pcqmr"] +animations = [{ +"frames": [{ +"duration": 1.0, +"texture": ExtResource("1_b3mxk") +}, { +"duration": 1.0, +"texture": ExtResource("2_1qmh0") +}], +"loop": true, +"name": &"fly", +"speed": 3.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": ExtResource("3_gfurk") +}, { +"duration": 1.0, +"texture": ExtResource("4_ieysi") +}], +"loop": true, +"name": &"swim", +"speed": 3.0 +}, { +"frames": [{ +"duration": 1.0, +"texture": ExtResource("5_cixyi") +}, { +"duration": 1.0, +"texture": ExtResource("6_7ulmv") +}], +"loop": true, +"name": &"walk", +"speed": 3.0 +}] + +[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_jbnni"] +radius = 34.0 +height = 82.0 + +[node name="Mob" type="Mob"] +collision_mask = 0 +gravity_scale = 0.0 +metadata/_edit_group_ = true + +[node name="AnimatedSprite2D" type="AnimatedSprite2D" parent="."] +scale = Vector2(0.75, 0.75) +sprite_frames = SubResource("SpriteFrames_pcqmr") +animation = &"fly" + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +rotation = 1.5707964 +shape = SubResource("CapsuleShape2D_jbnni") + +[node name="VisibleOnScreenNotifier2D" type="VisibleOnScreenNotifier2D" parent="."] diff --git a/godot/scene.tscn b/godot/scene.tscn new file mode 100644 index 0000000..af805ee --- /dev/null +++ b/godot/scene.tscn @@ -0,0 +1,33 @@ +[gd_scene load_steps=4 format=3 uid="uid://nn237gseigsq"] + +[ext_resource type="PackedScene" uid="uid://dnn7s3hr6nkxo" path="res://mob.tscn" id="1_ulcgi"] +[ext_resource type="PackedScene" uid="uid://bx7xynpbusro6" path="res://player.tscn" id="2_nxogm"] + +[sub_resource type="Curve2D" id="Curve2D_drvgu"] +_data = { +"points": PackedVector2Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 480, 0, 0, 0, 0, 0, 480, 720, 0, 0, 0, 0, 0, 720, 0, 0, 0, 0, 0, 0) +} +point_count = 5 + +[node name="MainScene" type="MainScene"] +mob_scene = ExtResource("1_ulcgi") + +[node name="Player" parent="." instance=ExtResource("2_nxogm")] +speed = 100.0 + +[node name="MobTimer" type="Timer" parent="."] +wait_time = 0.5 + +[node name="ScoreTimer" type="Timer" parent="."] + +[node name="StartTimer" type="Timer" parent="."] +wait_time = 2.0 +one_shot = true + +[node name="StartPosition" type="Marker2D" parent="."] +position = Vector2(240, 450) + +[node name="MobPath" type="Path2D" parent="."] +curve = SubResource("Curve2D_drvgu") + +[node name="MobSpawnLocation" type="PathFollow2D" parent="MobPath"] diff --git a/rust/Cargo.lock b/rust/Cargo.lock index badee07..a7c1f49 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -87,8 +87,7 @@ dependencies = [ [[package]] name = "gdextension-api" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25d88dabe9fdb2e064cb545312178ec4025eefb62d9a3bbce1769088e2c381d" +source = "git+https://github.com/godot-rust/godot4-prebuilt?branch=release-v0.3#f70baca72747869aaac7781a0cf139f53a2aac19" [[package]] name = "gimli" @@ -111,8 +110,7 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "godot" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90b20e19a25e45460c4fd2b11b519beea3e9bcda929c1566f8d17a369b41dd82" +source = "git+https://github.com/godot-rust/gdext?branch=master#4a582106a2162ebb1fd0258c84c017710a130407" dependencies = [ "godot-core", "godot-macros", @@ -121,8 +119,7 @@ dependencies = [ [[package]] name = "godot-bindings" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "508d56d01018c16b67a906bda8bcd9ade7f265990e4be7c2dffecbfdebcc3992" +source = "git+https://github.com/godot-rust/gdext?branch=master#4a582106a2162ebb1fd0258c84c017710a130407" dependencies = [ "gdextension-api", ] @@ -130,14 +127,12 @@ dependencies = [ [[package]] name = "godot-cell" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1794fbec9934ef375d717d02ec142d9e0c7d7482b24d7da463264b92bc14eaf" +source = "git+https://github.com/godot-rust/gdext?branch=master#4a582106a2162ebb1fd0258c84c017710a130407" [[package]] name = "godot-codegen" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791e05ae1859028c3a910aaed83e4910ab7701ce32fa663d2dc7cd957287cdf5" +source = "git+https://github.com/godot-rust/gdext?branch=master#4a582106a2162ebb1fd0258c84c017710a130407" dependencies = [ "godot-bindings", "heck", @@ -150,8 +145,7 @@ dependencies = [ [[package]] name = "godot-core" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1717ffba78bd2655c9ee1073752ac90d969b8a614800c469f00fc3ddc02439" +source = "git+https://github.com/godot-rust/gdext?branch=master#4a582106a2162ebb1fd0258c84c017710a130407" dependencies = [ "glam", "godot-bindings", @@ -163,8 +157,7 @@ dependencies = [ [[package]] name = "godot-ffi" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e46ba92fba076da67f35ad96faaa830c1de4e8175db2bb4251aeb58b14b08" +source = "git+https://github.com/godot-rust/gdext?branch=master#4a582106a2162ebb1fd0258c84c017710a130407" dependencies = [ "godot-bindings", "godot-codegen", @@ -175,8 +168,7 @@ dependencies = [ [[package]] name = "godot-macros" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a464e26854e63825d36655759c24556c970a8777535466ebf4ecc7f8e87a4638" +source = "git+https://github.com/godot-rust/gdext?branch=master#4a582106a2162ebb1fd0258c84c017710a130407" dependencies = [ "godot-bindings", "proc-macro2", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 97e0523..ebff9e3 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -7,7 +7,9 @@ edition = "2024" crate-type = ["cdylib"] [dependencies] -godot = { version = "0.4.0", features = ["api-4-5"] } +godot = { git = "https://github.com/godot-rust/gdext", branch = "master", features = [ + "api-4-5", +] } tokio = { version = "1.47.1", features = ["full"] } [build-dependencies] diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 869eea1..e5cae80 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -2,7 +2,9 @@ use godot::{classes::Engine, prelude::*}; mod runtime; +mod mob; mod player; +mod scene; struct MyExtension; diff --git a/rust/src/mob.rs b/rust/src/mob.rs new file mode 100644 index 0000000..6834614 --- /dev/null +++ b/rust/src/mob.rs @@ -0,0 +1,41 @@ +use godot::classes::{AnimatedSprite2D, IRigidBody2D, RigidBody2D}; +use godot::prelude::*; + +#[derive(GodotClass)] +#[class(base=RigidBody2D)] +pub struct Mob { + // required + base: Base, +} + +#[godot_api] +impl IRigidBody2D for Mob { + fn init(base: Base) -> Self { + Self { base } + } + + fn ready(&mut self) { + self + .signals() + .body_exited() + .connect_self(Self::on_visible_on_screen_notifier_2d_screen_exited); + + let mut animated_sprite_2d = self.base().get_node_as::("AnimatedSprite2D"); + let types = animated_sprite_2d + .get_sprite_frames() + .expect("failed to get sprite frames??") + .get_animation_names(); + + let animation = Array::from(&types).pick_random().expect("no animations found"); + + animated_sprite_2d.set_animation(animation.stringify().arg()); + animated_sprite_2d.play(); + } +} + +#[godot_api] +impl Mob { + fn on_visible_on_screen_notifier_2d_screen_exited(&mut self, _body: Gd) { + self.base_mut().queue_free(); + } +} diff --git a/rust/src/player.rs b/rust/src/player.rs index 62824ec..7981156 100644 --- a/rust/src/player.rs +++ b/rust/src/player.rs @@ -1,11 +1,16 @@ -use godot::classes::{AnimatedSprite2D, Area2D, IArea2D, Input}; +use std::f32::consts::PI; + +use godot::classes::{AnimatedSprite2D, Area2D, CollisionShape2D, IArea2D, Input}; +use godot::global::atan2; use godot::prelude::*; #[derive(GodotClass)] #[class(base=Area2D)] -struct Player { +pub struct Player { #[export] speed: f32, + #[export] + velocity: Vector2, screen_size: Rect2, // required @@ -17,6 +22,7 @@ impl IArea2D for Player { fn init(base: Base) -> Self { Self { speed: 400.0, + velocity: Vector2::ZERO, screen_size: Rect2::default(), base, @@ -25,37 +31,64 @@ impl IArea2D for Player { fn ready(&mut self) { self.screen_size = self.base().get_viewport_rect(); + self.base_mut().hide(); + + self.signals().body_entered().connect_self(Self::body_entered); } fn process(&mut self, delta: f32) { let input = Input::singleton(); let mut animated_sprite_2d = self.base().get_node_as::("AnimatedSprite2D"); - let mut velocity = Vector2::default(); - if input.is_action_pressed("move_right") { - velocity.x += 1.0; + self.velocity.x += 1.0 * self.speed; } if input.is_action_pressed("move_left") { - velocity.x -= 1.0; + self.velocity.x -= 1.0 * self.speed; } if input.is_action_pressed("move_down") { - velocity.y += 1.0; + self.velocity.y += 1.0 * self.speed; } if input.is_action_pressed("move_up") { - velocity.y -= 1.0; + self.velocity.y -= 1.0 * self.speed; } - if velocity.length() > 0.0 { - velocity = velocity.normalized() * self.speed; + if self.velocity.length() > 0.0 { + animated_sprite_2d.set_animation("up"); + animated_sprite_2d.set_rotation(atan2(self.velocity.y as f64, self.velocity.x as f64) as f32 + (PI / 2.0)); animated_sprite_2d.play(); } else { animated_sprite_2d.stop(); + animated_sprite_2d.set_frame(0); } let mut position = self.base().get_position(); - position = (position + (velocity * delta)).clamp(Vector2::ZERO, self.screen_size.size); + position = (position + (self.velocity * delta)).clamp(Vector2::ZERO, self.screen_size.size); + self.velocity *= 0.9; self.base_mut().set_position(position); } } + +#[godot_api] +impl Player { + #[signal] + pub fn hit(); + + fn body_entered(&mut self, _body: Gd) { + let mut collision_shape_2d = self.base().get_node_as::("CollisionShape2D"); + + self.base_mut().hide(); + self.signals().hit().emit(); + collision_shape_2d.set_deferred("disabled", &Variant::from(true)); + } + + #[func] + pub fn start(&mut self, position: Vector2) { + let mut collision_shape_2d = self.base().get_node_as::("CollisionShape2D"); + + self.base_mut().set_position(position); + self.base_mut().show(); + collision_shape_2d.set_disabled(false); + } +} diff --git a/rust/src/scene.rs b/rust/src/scene.rs new file mode 100644 index 0000000..ef2c5ad --- /dev/null +++ b/rust/src/scene.rs @@ -0,0 +1,98 @@ +use std::f64::consts::PI; + +use godot::classes::{Marker2D, Node, PathFollow2D, Timer}; +use godot::global::{randf, randf_range}; +use godot::prelude::*; + +use crate::mob::Mob; +use crate::player::Player; + +#[derive(GodotClass)] +#[class(init, base=Node)] +pub struct MainScene { + #[export] + mob_scene: OnEditor>, + #[var] + score: i64, + + // required + base: Base, +} + +#[godot_api] +impl INode for MainScene { + fn ready(&mut self) { + let player = self.base().get_node_as::("Player"); + player.signals().hit().connect_other(self, Self::game_over); + + let start_timer = self.base().get_node_as::("StartTimer"); + let score_timer = self.base().get_node_as::("ScoreTimer"); + let mob_timer = self.base().get_node_as::("MobTimer"); + + start_timer + .signals() + .timeout() + .connect_other(self, Self::on_start_timer_timeout); + score_timer + .signals() + .timeout() + .connect_other(self, Self::on_score_timer_timeout); + mob_timer + .signals() + .timeout() + .connect_other(self, Self::on_mob_timer_timeout); + + self.new_game(); + } +} + +#[godot_api] +impl MainScene { + fn game_over(&mut self) { + self.base().get_node_as::("ScoreTimer").stop(); + self.base().get_node_as::("MobTimer").stop(); + } + + fn new_game(&mut self) { + let mut player = self.base().get_node_as::("Player"); + let start_position = self.base().get_node_as::("StartPosition"); + player.bind_mut().start(start_position.get_position()); + + self.score = 0; + self.base().get_node_as::("ScoreTimer").start(); + self.base().get_node_as::("MobTimer").start(); + } + + fn on_start_timer_timeout(&mut self) { + self.new_game(); + } + + fn on_score_timer_timeout(&mut self) { + self.score += 1; + } + + fn on_mob_timer_timeout(&mut self) { + let mut mob = self.mob_scene.instantiate_as::(); + + let mut mob_spawn_location = self.base().get_node_as::("MobPath/MobSpawnLocation"); + mob_spawn_location.set_progress_ratio(randf() as f32); + + let mut direction = mob_spawn_location.get_rotation() as f64 + (PI / 2.0); + direction += randf_range(-PI / 4.0, PI / 4.0); + + mob.set_position(mob_spawn_location.get_position()); + mob.set_rotation(direction as f32); + + let velocity = Vector2::new(randf_range(150.0, 250.0) as f32, 0.0); + mob.set_linear_velocity(velocity.rotated(direction as f32)); + + self.base_mut().add_child(&mob); + } +} + +#[derive(GodotClass)] +#[class(init, base=Node)] +struct CrashFucker { + // required + base: Base, +}