├── .gitignore ├── .idea ├── .gitignore ├── action-rpg-tutorial-rs.iml ├── modules.xml └── vcs.xml ├── README.md ├── action-rpg-rs ├── .gitignore ├── .idea │ ├── .gitignore │ ├── action-rpg-rs.iml │ ├── deployment.xml │ ├── modules.xml │ ├── runConfigurations │ │ ├── Build_Debug.xml │ │ ├── Build_Release.xml │ │ ├── Build___Debug.xml │ │ ├── Build___Debug2.xml │ │ ├── Build___Release.xml │ │ └── Build___Release2.xml │ └── vcs.xml ├── Cargo.toml ├── build.ps1 ├── build.sh └── src │ ├── bat.rs │ ├── effect.rs │ ├── grass.rs │ ├── has_effect.rs │ ├── health_ui.rs │ ├── hurt_box.rs │ ├── hurt_sound.rs │ ├── lib.rs │ ├── player.rs │ ├── player_camera.rs │ ├── player_detection.rs │ ├── soft_collision.rs │ ├── stats.rs │ ├── sword.rs │ ├── utils.rs │ └── wander.rs └── action-rpg ├── .gitignore ├── Colliders ├── HitBox.tscn ├── HurtBox.gdns ├── HurtBox.tscn ├── SoftCollision.gdns └── SoftCollision.tscn ├── Effects ├── Effect.gdns ├── EnemyDeathEffect.gdns ├── EnemyDeathEffect.png ├── EnemyDeathEffect.png.import ├── EnemyDeathEffect.tscn ├── GrassEffect.png ├── GrassEffect.png.import ├── GrassEffect.tscn ├── HitEffect.gdns ├── HitEffect.png ├── HitEffect.png.import └── HitEffect.tscn ├── Enemies ├── Bat.gdns ├── Bat.png ├── Bat.png.import ├── Bat.tscn ├── PlayerDetectionZone.gdns ├── PlayerDetectionZone.tscn ├── WanderController.gdns └── WanderController.tscn ├── Music and Sounds ├── EnemyDie.wav ├── EnemyDie.wav.import ├── Evade.wav ├── Evade.wav.import ├── Hit.wav ├── Hit.wav.import ├── Hurt.wav ├── Hurt.wav.import ├── Menu Move.wav ├── Menu Move.wav.import ├── Menu Select.wav ├── Menu Select.wav.import ├── Music.mp3 ├── Music.mp3.import ├── Pause.wav ├── Pause.wav.import ├── Swipe.wav ├── Swipe.wav.import ├── Unpause.wav └── Unpause.wav.import ├── Player ├── Player.gdns ├── Player.png ├── Player.png.import ├── Player.tscn ├── PlayerHurtSound.gdns ├── PlayerHurtSound.tscn ├── PlayerStats.tscn └── SwordHitBox.gdns ├── PlayerCamera.gdns ├── PlayerCamera.tscn ├── Shaders └── WhiteColorShader.gdshader ├── Shadows ├── LargeShadow.png ├── LargeShadow.png.import ├── MediumShadow.png ├── MediumShadow.png.import ├── SmallShadow.png └── SmallShadow.png.import ├── Stats ├── Stats.gdns └── Stats.tscn ├── UI ├── HealthUI.gdns ├── HealthUI.tscn ├── HeartUIEmpty.png ├── HeartUIEmpty.png.import ├── HeartUIFull.png └── HeartUIFull.png.import ├── World ├── Bush.png ├── Bush.png.import ├── Bush.tscn ├── CliffTileset.png ├── CliffTileset.png.import ├── DirtTileset.png ├── DirtTileset.png.import ├── Grass.gdns ├── Grass.png ├── Grass.png.import ├── Grass.tscn ├── GrassBackground.png ├── GrassBackground.png.import ├── Tree.png ├── Tree.png.import └── Tree.tscn ├── action_rpg_tutorial_library.gdnlib ├── default_env.tres ├── icon.png ├── icon.png.import ├── project.godot └── world.tscn /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.dll 3 | *.so 4 | *.dylib 5 | .godot/ 6 | 7 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/action-rpg-tutorial-rs.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Godot Action RPG w/Rust 2 | A [GDNative](https://docs.godotengine.org/en/stable/tutorials/plugins/gdnative/index.html) implementation in [Rust ](https://www.rust-lang.org/) of youtuber [HeartBeast ](https://www.youtube.com/c/uheartbeast)'s great step-by-step turoial series creating a [Godot Action RPG](https://tinyurl.com/5t7rstyx), using the [godot-rust ](https://godot-rust.github.io/) 3 | 4 | ## Windows Build 5 | from within the ```action-rpg-rs``` folder 6 | 7 | _build release and copy dll to godot project_ 8 | 9 | ```shell 10 | ~\...\action-rpg-rs > .\build.ps1 release 11 | ``` 12 | 13 | _build debug and copy dll to godot project_ 14 | 15 | ```shell 16 | ~\...\action-rpg-rs > .\build.ps1 debug 17 | ``` 18 | 19 | ## MacOS / Linux Build 20 | from within the ```action-rpg-rs``` folder 21 | 22 | _build release and copy library so to godot project_ 23 | 24 | ```shell 25 | ~/.../action-rpg-rs> ./build.sh release 26 | ``` 27 | 28 | _build debug and copy library so to godot project_ 29 | 30 | ```shell 31 | ~/.../action-rpg-rs> ./build.sh debug 32 | ``` 33 | 34 | _* will require ```llvm``` tools, see_ [godot-rust](https://godot-rust.github.io/book/getting-started/setup.html) setup instructions 35 | 36 | _* this project is stuck on [Godot 3.4.n](https://godotengine.org/download/archive/#3.5-beta1) and [GDNative 3.9](https://crates.io/crates/gdnative/0.9.3), there are breaking changes beyond this point_ -------------------------------------------------------------------------------- /action-rpg-rs/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb -------------------------------------------------------------------------------- /action-rpg-rs/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /action-rpg-rs/.idea/action-rpg-rs.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /action-rpg-rs/.idea/deployment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /action-rpg-rs/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /action-rpg-rs/.idea/runConfigurations/Build_Debug.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | -------------------------------------------------------------------------------- /action-rpg-rs/.idea/runConfigurations/Build_Release.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | -------------------------------------------------------------------------------- /action-rpg-rs/.idea/runConfigurations/Build___Debug.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /action-rpg-rs/.idea/runConfigurations/Build___Debug2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | -------------------------------------------------------------------------------- /action-rpg-rs/.idea/runConfigurations/Build___Release.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /action-rpg-rs/.idea/runConfigurations/Build___Release2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | -------------------------------------------------------------------------------- /action-rpg-rs/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /action-rpg-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "action-rpg-rs" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | gdnative = "0.9.3" -------------------------------------------------------------------------------- /action-rpg-rs/build.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [Parameter(Mandatory=$true, Position=0)][ValidateSet('release', 'debug')] 3 | [string] $build 4 | ) 5 | 6 | function exit_on_fail() { 7 | if ($lastExitCode -ne 0) { 8 | exit 1 9 | } 10 | } 11 | 12 | Write-Output "" 13 | 14 | if ($build -eq "release") { 15 | Write-Output "building release ..." 16 | Write-Output "" 17 | cargo build --release 18 | exit_on_fail 19 | } elseif ($build -eq "debug") { 20 | Write-Output "building debug ..." 21 | Write-Output "" 22 | cargo build 23 | exit_on_fail 24 | } 25 | 26 | Write-Output "" 27 | Write-Output "copying $build action_rpg_rs.dll to ../action-rpg/action_rpg_rs.dll" 28 | Write-Output "" 29 | 30 | Copy-Item .\target\$build\action_rpg_rs.dll ..\action-rpg\action-rpg.dll 31 | exit_on_fail 32 | 33 | Write-Output "done" 34 | Write-Output "" 35 | -------------------------------------------------------------------------------- /action-rpg-rs/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ]; then 4 | build="release" 5 | else 6 | build=$1 7 | fi 8 | 9 | echo 10 | 11 | if [[ "$build" == "release" ]]; then 12 | echo building release ... 13 | echo 14 | cargo build --release || { exit 1; } 15 | elif [[ "$build" == "debug" ]]; then 16 | echo building debug ... 17 | echo 18 | cargo build || { exit 1; } 19 | else 20 | echo "$1 is an invalid argument" 21 | echo 22 | echo "usage: ./build.sh [debug|release]" 23 | echo 24 | exit 1 25 | fi 26 | 27 | library="libaction_rpg_rs" 28 | 29 | case "$(uname -sr)" in 30 | Darwin*) 31 | library="$library.dynlib" 32 | ;; 33 | 34 | Linux*|Linux*Microsoft*|CYGWIN*|MINGW*|MINGW32*|MSYS*) 35 | library="$library.so" 36 | ;; 37 | 38 | *) 39 | library="$library.so" 40 | ;; 41 | esac 42 | 43 | echo 44 | echo "copying $build $library to ../action-rpg/$library" 45 | echo 46 | 47 | cp "./target/$build/$library" ../action-rpg/$library || { exit 1; } 48 | 49 | echo "done" 50 | echo 51 | -------------------------------------------------------------------------------- /action-rpg-rs/src/bat.rs: -------------------------------------------------------------------------------- 1 | use std::f64::consts::FRAC_PI_4; 2 | 3 | use gdnative::api::*; 4 | use gdnative::prelude::*; 5 | 6 | use crate::{assume_safe, call, child_node, get_parameter, load_resource, set_parameter}; 7 | use crate::has_effect::HasEffect; 8 | use crate::hurt_box::{METHOD_PLAY_HIT_EFFECT, METHOD_START_INVINCIBILITY}; 9 | use crate::player_detection::{METHOD_CAN_SEE_PLAYER, METHOD_GET_PLAYER}; 10 | use crate::soft_collision::{METHOD_GET_PUSH_VECTOR, METHOD_IS_COLLIDING}; 11 | use crate::stats::PROPERTY_HEALTH; 12 | use crate::sword::{PROPERTY_DAMAGE, PROPERTY_KNOCK_BACK_VECTOR}; 13 | use crate::wander::{METHOD_IS_TIMER_COMPLETE, METHOD_START_TIMER, PROPERTY_TARGET_POSITION}; 14 | 15 | pub(crate) const PROPERTY_ACCELERATION: &str = "acceleration"; 16 | pub(crate) const PROPERTY_FRICTION: &str = "friction"; 17 | pub(crate) const PROPERTY_KNOCK_BACK_FORCE: &str = "knock_back_force"; 18 | pub(crate) const PROPERTY_MAX_SPEED: &str = "max_speed"; 19 | pub(crate) const PROPERTY_PUSH_VECTOR_FORCE: &str = "push_vector_force"; 20 | 21 | // i choose this ratio of max speed to buffer the bat's approach to it's target 22 | const WANDER_BUFFER_RATIO: f32 = 0.08; // this value might be frame rate dependent 23 | 24 | const DEFAULT_ACCELERATION: f32 = 300.0; 25 | const DEFAULT_FRICTION: f32 = 200.0; 26 | const DEFAULT_KNOCK_BACK_FORCE: f32 = 120.0; 27 | const DEFAULT_MAX_SPEED: f32 = 50.0; 28 | const DEFAULT_PUSH_VECTOR_FORCE: f32 = 400.0; 29 | const DEFAULT_WANDER_BUFFER_ZONE: f32 = DEFAULT_MAX_SPEED * WANDER_BUFFER_RATIO; 30 | 31 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] 32 | enum BatState { 33 | CHASE, 34 | IDLE, 35 | WANDER, 36 | } 37 | 38 | #[derive(NativeClass)] 39 | #[inherit(KinematicBody2D)] 40 | #[register_with(Self::register)] 41 | pub struct Bat { 42 | #[property] 43 | acceleration: f32, 44 | blink_animation: Option>, 45 | effect: Option>, 46 | #[property] 47 | friction: f32, 48 | hurt_box: Option>, 49 | knock_back: Vector2, 50 | #[property] 51 | knock_back_force: f32, 52 | #[property] 53 | max_speed: f32, 54 | player_detection: Option>, 55 | push_vector_force: f32, 56 | rand: Ref, 57 | soft_collision: Option>, 58 | sprite: Option>, 59 | state: BatState, 60 | stats: Option>, 61 | velocity: Vector2, 62 | wander_buffer_zone: f32, 63 | wander_controller: Option>, 64 | } 65 | 66 | impl HasEffect for Bat { 67 | fn effect_scene(&self) -> &Option> { 68 | &self.effect 69 | } 70 | } 71 | 72 | impl Bat { 73 | fn new(_owner: &KinematicBody2D) -> Self { 74 | Bat { 75 | acceleration: DEFAULT_ACCELERATION, 76 | blink_animation: None, 77 | effect: None, 78 | friction: DEFAULT_FRICTION, 79 | hurt_box: None, 80 | knock_back: Vector2::zero(), 81 | knock_back_force: DEFAULT_KNOCK_BACK_FORCE, 82 | max_speed: DEFAULT_MAX_SPEED, 83 | player_detection: None, 84 | push_vector_force: DEFAULT_PUSH_VECTOR_FORCE, 85 | rand: RandomNumberGenerator::new(), 86 | soft_collision: None, 87 | sprite: None, 88 | state: BatState::IDLE, 89 | stats: None, 90 | velocity: Vector2::zero(), 91 | wander_buffer_zone: DEFAULT_WANDER_BUFFER_ZONE, 92 | wander_controller: None, 93 | } 94 | } 95 | 96 | //noinspection DuplicatedCode 97 | fn register(builder: &ClassBuilder) { 98 | builder 99 | .add_property::(PROPERTY_ACCELERATION) 100 | .with_getter(|s: &Self, _| s.acceleration) 101 | .with_setter(|s: &mut Self, _, value: f32| s.acceleration = value) 102 | .with_default(DEFAULT_ACCELERATION) 103 | .done(); 104 | 105 | builder 106 | .add_property::(PROPERTY_FRICTION) 107 | .with_getter(|s: &Self, _| s.friction) 108 | .with_setter(|s: &mut Self, _, value: f32| s.friction = value) 109 | .with_default(DEFAULT_FRICTION) 110 | .done(); 111 | 112 | builder 113 | .add_property::(PROPERTY_KNOCK_BACK_FORCE) 114 | .with_getter(|s: &Self, _| s.knock_back_force) 115 | .with_setter(|s: &mut Self, _, value: f32| s.knock_back_force = value) 116 | .with_default(DEFAULT_KNOCK_BACK_FORCE) 117 | .done(); 118 | 119 | builder 120 | .add_property::(PROPERTY_MAX_SPEED) 121 | .with_getter(|s: &Self, _| s.max_speed) 122 | .with_setter(|s: &mut Self, _, value: f32| { 123 | s.max_speed = value; 124 | s.wander_buffer_zone = s.max_speed * WANDER_BUFFER_RATIO; 125 | }) 126 | .with_default(DEFAULT_MAX_SPEED) 127 | .done(); 128 | 129 | builder 130 | .add_property::(PROPERTY_PUSH_VECTOR_FORCE) 131 | .with_getter(|s: &Self, _| s.push_vector_force) 132 | .with_setter(|s: &mut Self, _, value: f32| s.push_vector_force = value) 133 | .with_default(DEFAULT_PUSH_VECTOR_FORCE) 134 | .done(); 135 | } 136 | } 137 | 138 | #[methods] 139 | impl Bat { 140 | #[export] 141 | fn _ready(&mut self, owner: &KinematicBody2D) { 142 | load_resource! { scene: PackedScene = "Effects/EnemyDeathEffect.tscn" { 143 | self.effect = Some(scene.claim()) 144 | } } 145 | 146 | self.blink_animation = Some(child_node!(claim owner["BlinkAnimationPlayer"]: AnimationPlayer)); 147 | self.hurt_box = Some(child_node!(claim owner["HurtBox"]: Node2D)); 148 | self.player_detection = Some(child_node!(claim owner["PlayerDetectionZone"]: Area2D)); 149 | self.soft_collision = Some(child_node!(claim owner["SoftCollision"]: Area2D)); 150 | self.sprite = Some(child_node!(claim owner["AnimatedSprite"]: AnimatedSprite)); 151 | self.stats = Some(child_node!(owner["Stats"])); 152 | self.wander_controller = Some(child_node!(claim owner["WanderController"]: Node2D)); 153 | 154 | self.state = self.pick_random_state(); 155 | } 156 | 157 | #[export] 158 | fn _physics_process(&mut self, owner: &KinematicBody2D, delta: f32) { 159 | self.knock_back = owner.move_and_slide( 160 | self.knock_back.move_towards(Vector2::zero(), self.friction * delta), 161 | Vector2::zero(), false, 4, FRAC_PI_4, true, 162 | ); 163 | 164 | match self.state { 165 | BatState::CHASE => { 166 | let player = call!(self.player_detection; METHOD_GET_PLAYER: KinematicBody2D); 167 | 168 | if let Some(player) = player { 169 | let direction = owner.global_position() 170 | .direction_to(unsafe { player.assume_safe() }.global_position()); 171 | 172 | self.accelerate_towards(direction, delta); 173 | } else { 174 | self.state = BatState::IDLE 175 | } 176 | } 177 | BatState::IDLE => { 178 | self.seek_player(owner); 179 | self.next_state_on_finish(3.0); 180 | 181 | self.velocity = self.velocity.move_towards(Vector2::zero(), self.friction * delta); 182 | } 183 | BatState::WANDER => { 184 | self.seek_player(owner); 185 | self.next_state_on_finish(3.0); 186 | 187 | let target_position = get_parameter!( 188 | self.wander_controller.unwrap(); PROPERTY_TARGET_POSITION 189 | ).to_vector2(); 190 | 191 | let direction = owner.global_position().direction_to(target_position); 192 | 193 | self.accelerate_towards(direction, delta); 194 | 195 | if owner.global_position().distance_to(target_position) <= self.wander_buffer_zone { 196 | self.next_state(3.0); 197 | } 198 | } 199 | } 200 | 201 | if call!(self.soft_collision; METHOD_IS_COLLIDING).to_bool() { 202 | self.velocity += call!(self.soft_collision; METHOD_GET_PUSH_VECTOR).to_vector2() 203 | * delta * self.push_vector_force; 204 | } 205 | 206 | // move flip logic here for all movement states 207 | // check for stopped bat to keep last direction 208 | if self.velocity != Vector2::zero() { 209 | assume_safe!(self.sprite).set_flip_h(self.velocity.x < 0.0); 210 | } 211 | 212 | owner.move_and_slide(self.velocity, Vector2::zero(), false, 4, FRAC_PI_4, true); 213 | } 214 | 215 | #[inline] 216 | fn accelerate_towards(&mut self, direction: Vector2, delta: f32) { 217 | self.velocity = self.velocity.move_towards( 218 | direction * self.max_speed, 219 | self.acceleration * delta, 220 | ); 221 | } 222 | 223 | #[inline] 224 | fn next_state(&mut self, max_secs: f64) { 225 | self.state = self.pick_random_state(); 226 | 227 | call!( 228 | self.wander_controller; 229 | METHOD_START_TIMER(self.rand.randf_range(1.0, max_secs).to_variant()) 230 | ); 231 | } 232 | 233 | #[inline] 234 | fn next_state_on_finish(&mut self, max_secs: f64) { 235 | let timer_complete = call!(self.wander_controller; METHOD_IS_TIMER_COMPLETE).to_bool(); 236 | 237 | if timer_complete { 238 | self.next_state(max_secs); 239 | } 240 | } 241 | 242 | #[inline] 243 | fn seek_player(&mut self, _owner: &KinematicBody2D) { 244 | let can_see_player = call!(self.player_detection; METHOD_CAN_SEE_PLAYER).to_bool(); 245 | if can_see_player { 246 | self.state = BatState::CHASE 247 | } 248 | } 249 | 250 | // this did not need the overhead of lists and list manipulation, 251 | // so this is my simplified solution 252 | #[inline] 253 | fn pick_random_state(&mut self) -> BatState { 254 | if self.rand.randi_range(1, 2) == 1 { 255 | BatState::IDLE 256 | } else { 257 | BatState::WANDER 258 | } 259 | } 260 | 261 | #[export] 262 | #[allow(non_snake_case)] 263 | fn _on_HurtBox_area_entered(&mut self, _owner: &KinematicBody2D, area: Ref) { 264 | let damage = get_parameter!(area[PROPERTY_DAMAGE]).to_i64(); 265 | let stats = self.stats.unwrap(); 266 | let health = get_parameter!(stats; PROPERTY_HEALTH).to_i64(); 267 | 268 | set_parameter!(stats; PROPERTY_HEALTH = health - damage); 269 | 270 | self.knock_back = get_parameter!(area[PROPERTY_KNOCK_BACK_VECTOR]).to_vector2() 271 | * self.knock_back_force; 272 | 273 | call!(self.hurt_box; METHOD_START_INVINCIBILITY(0.4.to_variant())); 274 | call!(self.hurt_box; METHOD_PLAY_HIT_EFFECT); 275 | } 276 | 277 | #[export] 278 | #[allow(non_snake_case)] 279 | fn _on_HurtBox_invincibility_ended(&self, _owner: &KinematicBody2D) { 280 | assume_safe!(self.blink_animation).play("Stop", -1.0, 1.0, false); 281 | } 282 | 283 | #[export] 284 | #[allow(non_snake_case)] 285 | fn _on_HurtBox_invincibility_started(&self, _owner: &KinematicBody2D) { 286 | assume_safe!(self.blink_animation).play("Start", -1.0, 1.0, false); 287 | } 288 | 289 | // when connecting signal in the godot editor, click the "advanced" switch 290 | // and select the "deferred" option, otherwise an exception occurs 291 | // todo: figure out why this is necessary 292 | #[export] 293 | #[allow(non_snake_case)] 294 | fn _on_Stats_no_health(&self, owner: &KinematicBody2D) { 295 | self.play_effect_parent(owner); 296 | owner.queue_free(); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /action-rpg-rs/src/effect.rs: -------------------------------------------------------------------------------- 1 | use gdnative::api::*; 2 | use gdnative::prelude::*; 3 | 4 | #[derive(NativeClass)] 5 | #[inherit(AnimatedSprite)] 6 | pub struct Effect; 7 | 8 | impl Effect { 9 | fn new(_owner: &AnimatedSprite) -> Self { 10 | Effect 11 | } 12 | } 13 | 14 | #[methods] 15 | impl Effect { 16 | #[export] 17 | fn _ready(&mut self, owner: TRef) { 18 | owner 19 | .connect("animation_finished", owner, "_on_animation_finished", VariantArray::new_shared(), 1) 20 | .expect("_on_animation_finished to connect to effect instance"); 21 | 22 | owner.set_frame(0); 23 | owner.play("Animate", false); 24 | } 25 | 26 | #[export] 27 | #[allow(non_snake_case)] 28 | fn _on_animation_finished(&mut self, owner: &AnimatedSprite) { 29 | owner.queue_free(); 30 | } 31 | } -------------------------------------------------------------------------------- /action-rpg-rs/src/grass.rs: -------------------------------------------------------------------------------- 1 | use gdnative::api::*; 2 | use gdnative::prelude::*; 3 | 4 | use crate::has_effect::HasEffect; 5 | use crate::load_resource; 6 | 7 | #[derive(NativeClass)] 8 | #[inherit(Node2D)] 9 | pub struct Grass { 10 | effect: Option>, 11 | } 12 | 13 | impl HasEffect for Grass { 14 | fn effect_scene(&self) -> &Option> { 15 | &self.effect 16 | } 17 | } 18 | 19 | impl Grass { 20 | fn new(_owner: &Node2D) -> Self { 21 | Grass { effect: None } 22 | } 23 | } 24 | 25 | #[methods] 26 | impl Grass { 27 | #[export] 28 | fn _ready(&mut self, _owner: &Node2D) { 29 | load_resource! { scene: PackedScene = "Effects/GrassEffect.tscn" { 30 | self.effect = Some(scene.claim()) 31 | } } 32 | } 33 | 34 | #[export] 35 | #[allow(non_snake_case)] 36 | fn _on_HurtBox_area_entered(&mut self, owner: &Node2D, _area: Ref) { 37 | self.play_effect_parent(owner); 38 | owner.queue_free(); 39 | } 40 | } -------------------------------------------------------------------------------- /action-rpg-rs/src/has_effect.rs: -------------------------------------------------------------------------------- 1 | use gdnative::api::*; 2 | use gdnative::prelude::*; 3 | 4 | use crate::assume_safe; 5 | 6 | pub(crate) trait HasEffect { 7 | fn effect_scene(&self) -> &Option>; 8 | 9 | #[inline] 10 | fn play_effect_parent(&self, owner: &Node2D) { 11 | let scene = assume_safe!(self.effect_scene()); 12 | 13 | assume_safe! { 14 | let instance: Node2D = scene.instance(PackedScene::GEN_EDIT_STATE_DISABLED), 15 | let parent: Node = Node::get_parent(owner) => { 16 | parent.add_child(instance, false); 17 | instance.set_global_position(owner.global_position()); 18 | } 19 | } 20 | } 21 | 22 | #[inline] 23 | fn play_effect_root(&self, owner: &Node2D) { 24 | let scene = assume_safe!(self.effect_scene()); 25 | 26 | assume_safe! { 27 | let instance: Node2D = scene.instance(PackedScene::GEN_EDIT_STATE_DISABLED), 28 | let root: SceneTree = Node::get_tree(owner), 29 | let scene: Node = root.current_scene() => { 30 | scene.add_child(instance, false); 31 | instance.set_global_position(owner.global_position()); 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /action-rpg-rs/src/health_ui.rs: -------------------------------------------------------------------------------- 1 | use gdnative::api::*; 2 | use gdnative::prelude::*; 3 | 4 | use crate::{assume_safe, auto_load, child_node}; 5 | use crate::stats::{PROPERTY_MAX_HEALTH, SIGNAL_HEALTH_CHANGED, SIGNAL_MAX_HEALTH_CHANGED}; 6 | 7 | type Hearts = i64; 8 | 9 | pub(crate) const PROPERTY_HEARTS: &str = "hearts"; 10 | pub(crate) const PROPERTY_MAX_HEARTS: &str = "max_hearts"; 11 | 12 | const DEFAULT_MAX_HEARTS: Hearts = 4; 13 | 14 | const MINIMUM_HEARTS: Hearts = 0; 15 | const MINIMUM_MAX_HEARTS: Hearts = 1; 16 | 17 | const HEART_HEIGHT: f32 = 11.0; 18 | const HEART_WIDTH: f32 = 15.0; 19 | 20 | #[derive(NativeClass)] 21 | #[inherit(Control)] 22 | #[register_with(Self::register)] 23 | pub struct HealthUI { 24 | #[property] 25 | hearts: Hearts, 26 | hearts_empty: Option>, 27 | hearts_full: Option>, 28 | player_stats: Option>, 29 | #[property] 30 | max_hearts: Hearts, 31 | } 32 | 33 | impl HealthUI { 34 | fn new(_owner: &Control) -> Self { 35 | HealthUI { 36 | hearts: DEFAULT_MAX_HEARTS, 37 | hearts_empty: None, 38 | hearts_full: None, 39 | player_stats: None, 40 | max_hearts: DEFAULT_MAX_HEARTS, 41 | } 42 | } 43 | 44 | fn register(builder: &ClassBuilder) { 45 | builder 46 | .add_property::(PROPERTY_HEARTS) 47 | .with_getter(|s: &Self, _| s.hearts) 48 | .with_setter(Self::set_hearts) 49 | .with_default(DEFAULT_MAX_HEARTS) 50 | .done(); 51 | 52 | builder 53 | .add_property::(PROPERTY_MAX_HEARTS) 54 | .with_getter(|s: &Self, _| s.max_hearts) 55 | .with_setter(Self::set_max_hearts) 56 | .with_default(DEFAULT_MAX_HEARTS) 57 | .done(); 58 | } 59 | 60 | #[inline] 61 | fn update_health_ui(&self) { 62 | assume_safe!(self.hearts_full) 63 | .set_size(Vector2::new(self.hearts as f32 * HEART_WIDTH, HEART_HEIGHT), false); 64 | } 65 | 66 | #[inline] 67 | fn update_max_health_ui(&self) { 68 | assume_safe!(self.hearts_empty) 69 | .set_size(Vector2::new(self.max_hearts as f32 * HEART_WIDTH, HEART_HEIGHT), false); 70 | } 71 | } 72 | 73 | #[methods] 74 | impl HealthUI { 75 | #[export] 76 | fn _ready(&mut self, owner: TRef) { 77 | let owner_ref = owner.as_ref(); 78 | 79 | self.hearts_empty = Some(child_node!(claim owner_ref["HeartUIEmpty"]: TextureRect)); 80 | self.hearts_full = Some(child_node!(claim owner_ref["HeartUIFull"]: TextureRect)); 81 | 82 | let player_stats = auto_load!("PlayerStats": Node); 83 | 84 | player_stats 85 | .connect(SIGNAL_HEALTH_CHANGED, owner.clone(), "set_hearts", VariantArray::new_shared(), 1) 86 | .expect("set_hearts to connect to player stats"); 87 | 88 | player_stats 89 | .connect(SIGNAL_MAX_HEALTH_CHANGED, owner.clone(), "set_max_hearts", VariantArray::new_shared(), 1) 90 | .expect("set_max_hearts to connect to player stats"); 91 | 92 | self.set_max_hearts(owner, player_stats.get(PROPERTY_MAX_HEALTH).to_i64()); 93 | 94 | self.player_stats = Some(player_stats.claim()); 95 | } 96 | 97 | #[export] 98 | fn set_hearts(&mut self, _owner: TRef, hearts: Hearts) { 99 | self.hearts = Hearts::clamp(hearts, MINIMUM_HEARTS, self.max_hearts); 100 | self.update_health_ui(); 101 | } 102 | 103 | #[export] 104 | fn set_max_hearts(&mut self, _owner: TRef, max_hearts: Hearts) { 105 | self.max_hearts = Hearts::max(max_hearts, MINIMUM_MAX_HEARTS); 106 | self.update_max_health_ui(); 107 | 108 | self.hearts = self.max_hearts; 109 | self.update_health_ui(); 110 | } 111 | } -------------------------------------------------------------------------------- /action-rpg-rs/src/hurt_box.rs: -------------------------------------------------------------------------------- 1 | use gdnative::api::*; 2 | use gdnative::prelude::*; 3 | 4 | use crate::{assume_safe, child_node, load_resource}; 5 | use crate::has_effect::HasEffect; 6 | 7 | type Duration = f64; 8 | 9 | pub(crate) const PROPERTY_INVINCIBLE: &str = "invincible"; 10 | 11 | const DEFAULT_INVINCIBLE: bool = false; 12 | 13 | pub(crate) const METHOD_START_INVINCIBILITY: &str = "start_invincibility"; 14 | pub(crate) const METHOD_PLAY_HIT_EFFECT: &str = "play_hit_effect"; 15 | 16 | pub(crate) const SIGNAL_INVINCIBILITY_ENDED: &str = "invincibility_ended"; 17 | pub(crate) const SIGNAL_INVINCIBILITY_STARTED: &str = "invincibility_started"; 18 | 19 | #[derive(NativeClass)] 20 | #[inherit(Node2D)] 21 | #[register_with(Self::register)] 22 | pub struct HurtBox { 23 | collision_shape: Option>, 24 | effect: Option>, 25 | invincible: bool, 26 | timer: Option>, 27 | } 28 | 29 | impl HasEffect for HurtBox { 30 | fn effect_scene(&self) -> &Option> { 31 | &self.effect 32 | } 33 | } 34 | 35 | impl HurtBox { 36 | fn new(_owner: &Node2D) -> Self { 37 | HurtBox { 38 | collision_shape: None, 39 | effect: None, 40 | invincible: DEFAULT_INVINCIBLE, 41 | timer: None, 42 | } 43 | } 44 | 45 | fn register(builder: &ClassBuilder) { 46 | builder 47 | .add_property::(PROPERTY_INVINCIBLE) 48 | .with_getter(|s: &Self, _| s.invincible) 49 | .with_setter(Self::set_invincible) 50 | .with_default(DEFAULT_INVINCIBLE) 51 | .done(); 52 | 53 | builder.add_signal(Signal { name: SIGNAL_INVINCIBILITY_ENDED, args: &[] }); 54 | 55 | builder.add_signal(Signal { name: SIGNAL_INVINCIBILITY_STARTED, args: &[] }); 56 | } 57 | 58 | fn set_invincible(&mut self, owner: TRef, invincible: bool) { 59 | self.invincible = invincible; 60 | 61 | if invincible { 62 | owner.emit_signal(SIGNAL_INVINCIBILITY_STARTED, &[]); 63 | } else { 64 | owner.emit_signal(SIGNAL_INVINCIBILITY_ENDED, &[]); 65 | } 66 | } 67 | } 68 | 69 | #[methods] 70 | impl HurtBox { 71 | #[export] 72 | fn _ready(&mut self, owner: &Node2D) { 73 | load_resource! { scene: PackedScene = "Effects/HitEffect.tscn" { 74 | self.effect = Some(scene.claim()) 75 | } } 76 | 77 | self.collision_shape = Some(child_node!(claim owner["CollisionShape2D"]: CollisionShape2D)); 78 | self.timer = Some(child_node!(claim owner["Timer"]: Timer)); 79 | } 80 | 81 | #[export] 82 | fn play_hit_effect(&mut self, owner: &Node2D) { 83 | self.play_effect_root(owner); 84 | } 85 | 86 | #[export] 87 | fn start_invincibility(&mut self, owner: TRef, duration: Duration) { 88 | self.set_invincible(owner, true); 89 | 90 | assume_safe!(self.timer).start(duration); 91 | } 92 | 93 | // rust required these two signals to be connected "deferred" in godot 94 | #[export] 95 | #[allow(non_snake_case)] 96 | fn _on_HurtBox_invincibility_ended(&mut self, owner: &Node2D) { 97 | assume_safe!(self.collision_shape).set_disabled(false); 98 | owner.set("monitorable", true); 99 | } 100 | 101 | #[export] 102 | #[allow(non_snake_case)] 103 | fn _on_HurtBox_invincibility_started(&mut self, owner: &Node2D) { 104 | assume_safe!(self.collision_shape).set_disabled(true); 105 | owner.set("monitorable", false); 106 | } 107 | 108 | #[export] 109 | #[allow(non_snake_case)] 110 | fn _on_Timer_timeout(&mut self, owner: TRef) { 111 | self.set_invincible(owner, false); 112 | } 113 | } -------------------------------------------------------------------------------- /action-rpg-rs/src/hurt_sound.rs: -------------------------------------------------------------------------------- 1 | use gdnative::api::*; 2 | use gdnative::prelude::*; 3 | 4 | #[derive(NativeClass)] 5 | #[inherit(AudioStreamPlayer)] 6 | pub struct PlayerHurtSound; 7 | 8 | impl PlayerHurtSound { 9 | fn new(_owner: &AudioStreamPlayer) -> Self { 10 | PlayerHurtSound 11 | } 12 | } 13 | 14 | #[methods] 15 | impl PlayerHurtSound { 16 | #[export] 17 | #[allow(non_snake_case)] 18 | fn _on_PlayerHurtSound_finished(&mut self, owner: &AudioStreamPlayer) { 19 | owner.queue_free(); 20 | } 21 | } -------------------------------------------------------------------------------- /action-rpg-rs/src/lib.rs: -------------------------------------------------------------------------------- 1 | use gdnative::prelude::*; 2 | 3 | mod bat; 4 | mod effect; 5 | mod grass; 6 | mod has_effect; 7 | mod health_ui; 8 | mod hurt_box; 9 | mod hurt_sound; 10 | mod player; 11 | mod player_camera; 12 | mod player_detection; 13 | mod soft_collision; 14 | mod stats; 15 | mod sword; 16 | mod wander; 17 | 18 | mod utils; 19 | 20 | fn init(handle: InitHandle) { 21 | handle.add_class::(); 22 | handle.add_class::(); 23 | handle.add_class::(); 24 | handle.add_class::(); 25 | handle.add_class::(); 26 | handle.add_class::(); 27 | handle.add_class::(); 28 | handle.add_class::(); 29 | handle.add_class::(); 30 | handle.add_class::(); 31 | handle.add_class::(); 32 | handle.add_class::(); 33 | handle.add_class::(); 34 | } 35 | 36 | godot_init!(init); -------------------------------------------------------------------------------- /action-rpg-rs/src/player.rs: -------------------------------------------------------------------------------- 1 | use std::f64::consts::FRAC_PI_4; 2 | 3 | use gdnative::api::*; 4 | use gdnative::prelude::*; 5 | 6 | use crate::{ 7 | assume_safe, auto_load, blend_position, call, 8 | child_node, get_parameter, load_resource, set_parameter, 9 | }; 10 | use crate::hurt_box::{METHOD_PLAY_HIT_EFFECT, METHOD_START_INVINCIBILITY}; 11 | use crate::stats::{PROPERTY_HEALTH, SIGNAL_NO_HEALTH}; 12 | use crate::sword::PROPERTY_KNOCK_BACK_VECTOR; 13 | 14 | type AnimationPlayback = AnimationNodeStateMachinePlayback; 15 | 16 | pub(crate) const PROPERTY_ACCELERATION: &str = "acceleration"; 17 | pub(crate) const PROPERTY_FRICTION: &str = "friction"; 18 | pub(crate) const PROPERTY_MAX_SPEED: &str = "max_speed"; 19 | pub(crate) const PROPERTY_ROLL_SPEED: &str = "roll_speed"; 20 | 21 | const DEFAULT_ACCELERATION: f32 = 500.0; 22 | const DEFAULT_FRICTION: f32 = 500.0; 23 | const DEFAULT_MAX_SPEED: f32 = 80.0; 24 | const DEFAULT_ROLL_SPEED: f32 = 120.0; 25 | 26 | const INPUT_ATTACK: &str = "ui_attack"; 27 | const INPUT_DOWN: &str = "ui_down"; 28 | const INPUT_LEFT: &str = "ui_left"; 29 | const INPUT_RIGHT: &str = "ui_right"; 30 | const INPUT_ROLL: &str = "ui_roll"; 31 | const INPUT_UP: &str = "ui_up"; 32 | 33 | const TRAVEL_ATTACK: &str = "Attack"; 34 | const TRAVEL_IDLE: &str = "Idle"; 35 | const TRAVEL_ROLL: &str = "Roll"; 36 | const TRAVEL_RUN: &str = "Run"; 37 | 38 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] 39 | enum PlayerState { 40 | Attack, 41 | Move, 42 | Roll, 43 | } 44 | 45 | #[derive(NativeClass)] 46 | #[inherit(KinematicBody2D)] 47 | #[register_with(Self::register)] 48 | pub struct Player { 49 | #[property] 50 | acceleration: f32, 51 | // todo: when using Ref, the placeholder values in "fn new" cause the following warning in godot 52 | // todo: "WARNING: cleanup: ObjectDB instances leaked at exit" 53 | animation_state: Option>, 54 | animation_tree: Option>, 55 | blink_animation: Option>, 56 | #[property] 57 | friction: f32, 58 | hurt_box: Option>, 59 | hurt_sound: Option>, 60 | #[property] 61 | max_speed: f32, 62 | player_stats: Option>, 63 | #[property] 64 | roll_speed: f32, 65 | roll_vector: Vector2, 66 | state: PlayerState, 67 | sword: Option>, 68 | velocity: Vector2, 69 | } 70 | 71 | impl Player { 72 | fn new(_owner: &KinematicBody2D) -> Self { 73 | Player { 74 | acceleration: DEFAULT_ACCELERATION, 75 | animation_state: None, 76 | animation_tree: None, 77 | blink_animation: None, 78 | friction: DEFAULT_FRICTION, 79 | hurt_box: None, 80 | hurt_sound: None, 81 | max_speed: DEFAULT_MAX_SPEED, 82 | roll_speed: DEFAULT_ROLL_SPEED, 83 | roll_vector: Vector2::new(0.0, 1.0), // DOWN 84 | player_stats: None, 85 | state: PlayerState::Move, 86 | sword: None, 87 | velocity: Vector2::zero(), 88 | } 89 | } 90 | 91 | //noinspection DuplicatedCode 92 | fn register(builder: &ClassBuilder) { 93 | builder 94 | .add_property::(PROPERTY_ACCELERATION) 95 | .with_getter(|s: &Self, _| s.acceleration) 96 | .with_setter(|s: &mut Self, _, value: f32| s.acceleration = value) 97 | .with_default(DEFAULT_ACCELERATION) 98 | .done(); 99 | 100 | builder 101 | .add_property::(PROPERTY_FRICTION) 102 | .with_getter(|s: &Self, _| s.friction) 103 | .with_setter(|s: &mut Self, _, value: f32| s.friction = value) 104 | .with_default(DEFAULT_FRICTION) 105 | .done(); 106 | 107 | builder 108 | .add_property::(PROPERTY_MAX_SPEED) 109 | .with_getter(|s: &Self, _| s.max_speed) 110 | .with_setter(|s: &mut Self, _, value: f32| s.max_speed = value) 111 | .with_default(DEFAULT_MAX_SPEED) 112 | .done(); 113 | 114 | builder 115 | .add_property::(PROPERTY_ROLL_SPEED) 116 | .with_getter(|s: &Self, _| s.roll_speed) 117 | .with_setter(|s: &mut Self, _, value: f32| s.roll_speed = value) 118 | .with_default(DEFAULT_ROLL_SPEED) 119 | .done(); 120 | } 121 | } 122 | 123 | // the additional values passed to godot functions, that are not mentioned in 124 | // the video, are listed in the api documentation and are defaults in gdscript 125 | 126 | #[methods] 127 | impl Player { 128 | #[export] 129 | fn _ready(&mut self, owner: TRef) { 130 | let owner_ref = owner.as_ref(); 131 | 132 | child_node! { animation_tree: AnimationTree = owner_ref["AnimationTree"] } 133 | get_parameter! { animation_state: AnimationPlayback = animation_tree[@"playback"] } 134 | 135 | animation_tree.set_active(true); 136 | 137 | self.animation_tree = Some(animation_tree.claim()); 138 | self.animation_state = Some(animation_state.claim()); 139 | self.blink_animation = Some(child_node!(claim owner_ref["BlinkAnimationPlayer"]: AnimationPlayer)); 140 | self.hurt_box = Some(child_node!(claim owner_ref["HurtBox"]: Node2D)); 141 | self.sword = Some(child_node!(claim owner_ref["HitboxPivot/SwordHitbox"]: Area2D)); 142 | 143 | load_resource! { scene: PackedScene = "Player/PlayerHurtSound.tscn" { 144 | self.hurt_sound = Some(scene.claim()) 145 | } } 146 | 147 | let player_stats = auto_load!("PlayerStats": Node); 148 | 149 | player_stats 150 | .connect(SIGNAL_NO_HEALTH, owner, "_on_Stats_no_health", VariantArray::new_shared(), 1) 151 | .expect("_on_Stats_no_health to connect to player stats"); 152 | 153 | self.player_stats = Some(player_stats.claim()); 154 | } 155 | 156 | #[export] 157 | fn _physics_process(&mut self, owner: &KinematicBody2D, delta: f32) { 158 | match self.state { 159 | PlayerState::Move => 160 | self.move_state(owner, delta), 161 | PlayerState::Attack => 162 | self.attack_state(owner), 163 | PlayerState::Roll => 164 | self.roll_state(owner) 165 | } 166 | } 167 | 168 | #[export] 169 | fn attack_animation_finished(&mut self, _owner: &KinematicBody2D) { 170 | self.state = PlayerState::Move 171 | } 172 | 173 | #[export] 174 | fn roll_animation_finished(&mut self, _owner: &KinematicBody2D) { 175 | self.velocity = self.velocity * 0.8; // ease sliding past roll animation 176 | self.state = PlayerState::Move 177 | } 178 | 179 | #[inline] 180 | fn attack_state(&mut self, _owner: &KinematicBody2D) { 181 | self.velocity = Vector2::zero(); 182 | 183 | assume_safe!(self.animation_state).travel(TRAVEL_ATTACK); 184 | } 185 | 186 | #[inline] 187 | fn move_state(&mut self, owner: &KinematicBody2D, delta: f32) { 188 | let input = Input::godot_singleton(); 189 | let mut input_vector = Vector2::zero(); 190 | 191 | input_vector.x = (input.get_action_strength(INPUT_RIGHT) - 192 | input.get_action_strength(INPUT_LEFT)) as f32; 193 | 194 | input_vector.y = (input.get_action_strength(INPUT_DOWN) - 195 | input.get_action_strength(INPUT_UP)) as f32; 196 | 197 | if input_vector != Vector2::zero() { 198 | // in the video, the function "normalized" is used, which handles zero condition. 199 | // godot-rust does not have that function, instead there is a try_normalize. 200 | // since we only use the input_vector when it's none zero, I opted to use the 201 | // "normalize" function after the check for zero. 202 | input_vector = input_vector.normalize(); 203 | 204 | self.roll_vector = input_vector; 205 | 206 | set_parameter! { ?self.sword; PROPERTY_KNOCK_BACK_VECTOR = input_vector } 207 | 208 | let animation_tree = assume_safe!(self.animation_tree); 209 | 210 | animation_tree.set(blend_position!("Idle"), input_vector); 211 | animation_tree.set(blend_position!("Run"), input_vector); 212 | animation_tree.set(blend_position!("Attack"), input_vector); 213 | animation_tree.set(blend_position!("Roll"), input_vector); 214 | 215 | assume_safe!(self.animation_state).travel(TRAVEL_RUN); 216 | 217 | self.velocity = self.velocity.move_towards(input_vector * self.max_speed, self.acceleration * delta); 218 | } else { 219 | assume_safe!(self.animation_state).travel(TRAVEL_IDLE); 220 | 221 | self.velocity = self.velocity.move_towards(Vector2::zero(), self.friction * delta); 222 | } 223 | 224 | self.move_player(owner); 225 | 226 | if input.is_action_just_pressed(INPUT_ROLL) { 227 | self.state = PlayerState::Roll 228 | } 229 | 230 | if input.is_action_just_pressed(INPUT_ATTACK) { 231 | self.state = PlayerState::Attack 232 | } 233 | } 234 | 235 | #[inline] 236 | fn roll_state(&mut self, owner: &KinematicBody2D) { 237 | self.velocity = self.roll_vector * self.roll_speed; 238 | 239 | assume_safe!(self.animation_state).travel(TRAVEL_ROLL); 240 | 241 | self.move_player(owner); 242 | } 243 | 244 | #[inline] 245 | fn move_player(&mut self, owner: &KinematicBody2D) { 246 | // FRAC_PI_4 was suggested by c-lion ide as an approximate constant of the 247 | // documented default value of 0.785398 for "floor_max_angle" 248 | 249 | self.velocity = owner.move_and_slide(self.velocity, Vector2::zero(), false, 4, FRAC_PI_4, true); 250 | } 251 | 252 | #[export] 253 | #[allow(non_snake_case)] 254 | fn _on_HurtBox_area_entered(&mut self, owner: &KinematicBody2D, _area: Ref) { 255 | // enemy hit box does not have damage, the video "fix" causes a bug 256 | // let damage = get_parameter!(area[PROPERTY_DAMAGE]).to_i64(); 257 | let player_stats = self.player_stats.unwrap(); 258 | let health = get_parameter!(player_stats; PROPERTY_HEALTH).to_i64(); 259 | 260 | set_parameter!(player_stats; PROPERTY_HEALTH = health - 1); 261 | 262 | call!(self.hurt_box; METHOD_START_INVINCIBILITY(0.5.to_variant())); 263 | call!(self.hurt_box; METHOD_PLAY_HIT_EFFECT); 264 | 265 | let scene = assume_safe!(self.hurt_sound); 266 | 267 | assume_safe! { 268 | let instance: Node = scene.instance(PackedScene::GEN_EDIT_STATE_DISABLED), 269 | let root: SceneTree = Node::get_tree(owner), 270 | let scene: Node = root.current_scene() => { 271 | scene.add_child(instance, false); 272 | } 273 | } 274 | } 275 | 276 | #[export] 277 | #[allow(non_snake_case)] 278 | fn _on_HurtBox_invincibility_ended(&self, _owner: &KinematicBody2D) { 279 | assume_safe!(self.blink_animation).play("Stop", -1.0, 1.0, false); 280 | } 281 | 282 | #[export] 283 | #[allow(non_snake_case)] 284 | fn _on_HurtBox_invincibility_started(&self, _owner: &KinematicBody2D) { 285 | assume_safe!(self.blink_animation).play("Start", -1.0, 1.0, false); 286 | } 287 | 288 | #[export] 289 | #[allow(non_snake_case)] 290 | fn _on_Stats_no_health(&self, owner: &KinematicBody2D) { 291 | owner.queue_free(); 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /action-rpg-rs/src/player_camera.rs: -------------------------------------------------------------------------------- 1 | use gdnative::api::*; 2 | use gdnative::prelude::*; 3 | 4 | use crate::child_node; 5 | 6 | #[derive(NativeClass)] 7 | #[inherit(Camera2D)] 8 | pub struct PlayerCamera; 9 | 10 | impl PlayerCamera { 11 | fn new(_owner: &Camera2D) -> Self { 12 | PlayerCamera 13 | } 14 | } 15 | 16 | #[methods] 17 | impl PlayerCamera { 18 | #[export] 19 | fn _ready(&mut self, owner: &Camera2D) { 20 | let top_left = child_node!(owner["Limits/TopLeft"]: Position2D).position(); 21 | 22 | owner.set("limit_top", top_left.y); 23 | owner.set("limit_left", top_left.x); 24 | 25 | let bottom_right = child_node!(owner["Limits/BottomRight"]: Position2D).position(); 26 | 27 | owner.set("limit_bottom", bottom_right.y); 28 | owner.set("limit_right", bottom_right.x); 29 | } 30 | } -------------------------------------------------------------------------------- /action-rpg-rs/src/player_detection.rs: -------------------------------------------------------------------------------- 1 | use gdnative::api::*; 2 | use gdnative::prelude::*; 3 | 4 | pub(crate) const METHOD_CAN_SEE_PLAYER: &str = "can_see_player"; 5 | pub(crate) const METHOD_GET_PLAYER: &str = "get_player"; 6 | 7 | pub(crate) const PROPERTY_PLAYER: &str = "player"; 8 | 9 | #[derive(NativeClass)] 10 | #[inherit(Area2D)] 11 | #[register_with(Self::register)] 12 | pub struct PlayerDetectionZone { 13 | #[property(no_editor)] 14 | player: Option>, 15 | } 16 | 17 | impl PlayerDetectionZone { 18 | fn new(_owner: &Area2D) -> Self { 19 | PlayerDetectionZone { 20 | player: None 21 | } 22 | } 23 | 24 | fn register(builder: &ClassBuilder) { 25 | builder 26 | .add_property::>>(PROPERTY_PLAYER) 27 | .with_getter(|s: &Self, _| s.player) 28 | .with_setter(|s: &mut Self, _, value: Option>| s.player = value) 29 | .with_usage(PropertyUsage::NOEDITOR) 30 | .done(); 31 | } 32 | } 33 | 34 | #[methods] 35 | impl PlayerDetectionZone { 36 | #[export] 37 | pub(crate) fn can_see_player(&self, _owner: &Area2D) -> bool { self.player.is_some() } 38 | 39 | #[export] 40 | pub(crate) fn get_player(&self, _owner: &Area2D) -> Variant { self.player.to_variant() } 41 | 42 | #[export] 43 | #[allow(non_snake_case)] 44 | fn _on_PlayerDetectionZone_body_entered(&mut self, _owner: &Area2D, body: Ref) { 45 | self.player = Some(body); 46 | } 47 | 48 | #[export] 49 | #[allow(non_snake_case)] 50 | fn _on_PlayerDetectionZone_body_exited(&mut self, _owner: &Area2D, _body: Ref) { 51 | self.player = None 52 | } 53 | } -------------------------------------------------------------------------------- /action-rpg-rs/src/soft_collision.rs: -------------------------------------------------------------------------------- 1 | use gdnative::api::*; 2 | use gdnative::prelude::*; 3 | 4 | use crate::array_item; 5 | 6 | pub(crate) const METHOD_IS_COLLIDING: &str = "is_colliding"; 7 | pub(crate) const METHOD_GET_PUSH_VECTOR: &str = "get_push_vector"; 8 | 9 | #[derive(NativeClass)] 10 | #[inherit(Area2D)] 11 | pub struct SoftCollision; 12 | 13 | impl SoftCollision { 14 | fn new(_owner: &Area2D) -> Self { SoftCollision } 15 | } 16 | 17 | #[methods] 18 | impl SoftCollision { 19 | #[export] 20 | fn get_push_vector (&self, owner: &Area2D) -> Vector2 { 21 | let areas = owner.get_overlapping_areas(); 22 | let mut push_vector = Vector2::zero(); 23 | 24 | if !areas.is_empty() { 25 | let area = array_item! { areas[0]: Node2D }; 26 | 27 | push_vector = area.global_position().direction_to(owner.global_position()); 28 | 29 | if push_vector != Vector2::zero() { 30 | push_vector = push_vector.normalize(); 31 | } 32 | } 33 | 34 | push_vector 35 | } 36 | 37 | #[export] 38 | fn is_colliding (&self, owner: &Area2D) -> bool { 39 | !owner.get_overlapping_areas().is_empty() 40 | } 41 | } -------------------------------------------------------------------------------- /action-rpg-rs/src/stats.rs: -------------------------------------------------------------------------------- 1 | use gdnative::api::*; 2 | use gdnative::prelude::*; 3 | 4 | pub(crate) const PROPERTY_HEALTH: &'static str = "health"; 5 | pub(crate) const PROPERTY_MAX_HEALTH: &'static str = "max_health"; 6 | 7 | const DEFAULT_HEALTH: Health = 1; 8 | 9 | const MINIMUM_HEALTH: Health = 0; 10 | const MINIMUM_MAX_HEALTH: Health = 1; 11 | 12 | pub(crate) const SIGNAL_HEALTH_CHANGED: &str = "health_changed"; 13 | pub(crate) const SIGNAL_MAX_HEALTH_CHANGED: &str = "max_health_changed"; 14 | pub(crate) const SIGNAL_NO_HEALTH: &str = "no_health"; 15 | 16 | type Health = i64; 17 | 18 | #[derive(NativeClass)] 19 | #[inherit(Node)] 20 | #[register_with(Self::register)] 21 | pub struct Stats { 22 | health: Health, 23 | max_health: Health, 24 | } 25 | 26 | impl Stats { 27 | fn new(_owner: &Node) -> Self { 28 | Stats { 29 | health: Health::default(), 30 | max_health: Health::default(), 31 | } 32 | } 33 | 34 | fn register(builder: &ClassBuilder) { 35 | builder 36 | .add_property::(PROPERTY_MAX_HEALTH) 37 | .with_getter(|s: &Self, _| s.max_health) 38 | .with_setter(Self::set_max_health) 39 | .with_default(DEFAULT_HEALTH) 40 | .done(); 41 | 42 | builder 43 | .add_property::(PROPERTY_HEALTH) 44 | .with_getter(|s: &Self, _| s.health) 45 | .with_setter(Self::set_health) 46 | .with_default(DEFAULT_HEALTH) 47 | .with_usage(PropertyUsage::NOEDITOR) 48 | .done(); 49 | 50 | let health_arg = SignalArgument { 51 | name: "health", 52 | default: DEFAULT_HEALTH.to_variant(), 53 | export_info: ExportInfo::new(VariantType::I64), 54 | usage: PropertyUsage::DEFAULT 55 | }; 56 | 57 | builder.add_signal(Signal { name: SIGNAL_HEALTH_CHANGED, args: &[health_arg] }); 58 | 59 | let max_health_arg = SignalArgument { 60 | name: "max_health", 61 | default: DEFAULT_HEALTH.to_variant(), 62 | export_info: ExportInfo::new(VariantType::I64), 63 | usage: PropertyUsage::DEFAULT 64 | }; 65 | 66 | builder.add_signal(Signal { name: SIGNAL_MAX_HEALTH_CHANGED, args: &[max_health_arg] }); 67 | builder.add_signal(Signal { name: SIGNAL_NO_HEALTH, args: &[] }); 68 | } 69 | } 70 | 71 | #[methods] 72 | impl Stats { 73 | fn set_max_health(&mut self, owner: TRef, max_health: Health) { 74 | self.max_health = Health::max(max_health, MINIMUM_MAX_HEALTH); 75 | 76 | owner.emit_signal(SIGNAL_MAX_HEALTH_CHANGED, &[self.max_health.to_variant()]); 77 | 78 | self.set_health(owner, self.max_health); 79 | } 80 | 81 | fn set_health(&mut self, owner: TRef, health: Health) { 82 | self.health = Health::clamp(health, MINIMUM_HEALTH, self.max_health); 83 | 84 | owner.emit_signal(SIGNAL_HEALTH_CHANGED, &[self.health.to_variant()]); 85 | 86 | if health <= MINIMUM_HEALTH { 87 | owner.emit_signal(SIGNAL_NO_HEALTH, &[]); 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /action-rpg-rs/src/sword.rs: -------------------------------------------------------------------------------- 1 | use gdnative::api::*; 2 | use gdnative::prelude::*; 3 | 4 | pub(crate) const PROPERTY_DAMAGE: &str = "damage"; 5 | pub(crate) const PROPERTY_KNOCK_BACK_VECTOR: &str = "knock_back_vector"; 6 | 7 | const DEFAULT_DAMAGE: i64 = 1; 8 | 9 | #[derive(NativeClass)] 10 | #[inherit(Area2D)] 11 | #[register_with(Self::register)] 12 | pub struct Sword { 13 | #[property] 14 | damage: i64, 15 | #[property] 16 | knock_back_vector: Vector2, 17 | } 18 | 19 | impl Sword { 20 | fn new(_owner: &Area2D) -> Self { 21 | Sword { 22 | damage: 1, 23 | knock_back_vector: Vector2::zero(), 24 | } 25 | } 26 | 27 | fn register(builder: &ClassBuilder) { 28 | builder 29 | .add_property::(PROPERTY_DAMAGE) 30 | .with_getter(|s: &Self, _| s.damage) 31 | .with_setter(|s: &mut Self, _, value: i64| s.damage = value) 32 | .with_default(DEFAULT_DAMAGE) 33 | .done(); 34 | 35 | builder 36 | .add_property::(PROPERTY_KNOCK_BACK_VECTOR) 37 | .with_getter(|s: &Self, _| s.knock_back_vector) 38 | .with_setter(|s: &mut Self, _, value: Vector2| s.knock_back_vector = value) 39 | .with_usage(PropertyUsage::NOEDITOR) 40 | .done(); 41 | } 42 | } 43 | 44 | #[methods] 45 | impl Sword {} 46 | -------------------------------------------------------------------------------- /action-rpg-rs/src/utils.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! array_item { 3 | ($ary:ident [ $index:literal ] : $type:ty) => { 4 | unsafe { 5 | $ary.get_ref($index) 6 | .try_to_object::<$type>() 7 | .expect(concat!(stringify!($type), " at ", stringify!($ary), "[", stringify!($index), "]")) 8 | .assume_safe() 9 | } 10 | }; 11 | ($ary:ident [ $index:ident ] : $type:ty) => { 12 | unsafe { 13 | $ary.get_ref($index) 14 | .try_to_object::<$type>() 15 | .expect(concat!(stringify!($type), " at ", stringify!($ary), "[", stringify!($index), "]")) 16 | .assume_safe() 17 | } 18 | }; 19 | } 20 | #[macro_export] 21 | macro_rules! assume_safe { 22 | ($var:expr) => { 23 | unsafe { $var.as_ref().unwrap().assume_safe() } 24 | }; 25 | ($(let $var:ident : $type:ty = $src:expr),* => $code:block) => { 26 | $( 27 | let $var: TRef<$type> = $src 28 | .and_then(|v| { unsafe { v.assume_safe().cast::<$type>() } }) 29 | .expect(concat!(stringify!($var), ":", stringify!($type), " = ", stringify!($src))); 30 | )* 31 | $code 32 | }; 33 | } 34 | 35 | #[macro_export] 36 | macro_rules! auto_load { 37 | (claim $node:literal : $type:ty) => { 38 | unsafe { autoload::<$type>($node).unwrap().claim() } 39 | }; 40 | ($node:literal : $type:ty) => { 41 | unsafe { autoload::<$type>($node).unwrap() } 42 | }; 43 | (claim $node:ident : $type:ty) => { 44 | unsafe { autoload::<$type>($node).unwrap().claim() } 45 | }; 46 | ($node:ident : $type:ty) => { 47 | unsafe { autoload::<$type>($node).unwrap() } 48 | }; 49 | } 50 | 51 | #[macro_export] 52 | macro_rules! blend_position { 53 | ($param:literal) => { 54 | concat!("parameters/", $param, "/blend_position") 55 | }; 56 | } 57 | 58 | #[macro_export] 59 | macro_rules! call { 60 | ($src:expr; $method:literal) => { 61 | unsafe { 62 | $src.as_ref().unwrap().assume_safe().call($method, &[]) 63 | } 64 | }; 65 | ($src:expr; $method:literal : $type:ty) => { 66 | unsafe { 67 | $src.as_ref().unwrap().assume_safe().call($method, &[]).try_to_object::<$type>() 68 | } 69 | }; 70 | ($src:expr; $method:ident) => { 71 | unsafe { 72 | $src.as_ref().unwrap().assume_safe().call($method, &[]) 73 | } 74 | }; 75 | ($src:expr; $method:ident : $type:ty) => { 76 | unsafe { 77 | $src.as_ref().unwrap().assume_safe().call($method, &[]).try_to_object::<$type>() 78 | } 79 | }; 80 | ($src:expr; $method:literal ( $($arg:expr),* )) => { 81 | unsafe { 82 | $src.as_ref().unwrap().assume_safe().call($method, &[$($arg),*]) 83 | } 84 | }; 85 | ($src:expr; $method:literal ( $($arg:expr),* ) : $type:ty) => { 86 | unsafe { 87 | $src.as_ref().unwrap().assume_safe().call($method, &[$($arg),*]).try_to_object::<$type>() 88 | } 89 | }; 90 | ($src:expr; $method:ident ( $($arg:expr),* )) => { 91 | unsafe { 92 | $src.as_ref().unwrap().assume_safe().call($method, &[$($arg),*]) 93 | } 94 | }; 95 | ($src:expr; $method:ident ( $($arg:expr),* ) : $type:ty) => { 96 | unsafe { 97 | $src.as_ref().unwrap().assume_safe().call($method, &[$($arg),*]).try_to_object::<$type>() 98 | } 99 | }; 100 | } 101 | 102 | #[macro_export] 103 | macro_rules! child_node { 104 | (claim $owner:ident [ $child:literal ] : $type:ty) => { 105 | unsafe { 106 | $owner.get_node_as::<$type>($child) 107 | .expect(concat!("\"", $child, "\" ", stringify!($type), " Child Node")) 108 | }.claim() 109 | }; 110 | ($owner:ident [ $child:literal ] ) => { 111 | $owner.get_node($child) 112 | .expect(concat!("\"", $child, "\" Child Node")) 113 | }; 114 | ($owner:ident [ $child:literal ] : $type:ty) => { unsafe { 115 | $owner.get_node_as::<$type>($child) 116 | .expect(concat!("\"", $child, "\" ", stringify!($type), " Child Node")) 117 | } }; 118 | ($var:ident: $type:ty = $owner:ident [ $child:literal ] ) => { 119 | let $var = unsafe { 120 | $owner.get_node_as::<$type>($child) 121 | .expect(concat!("\"", $child, "\" ", stringify!($type), " Child Node")) 122 | }; 123 | }; 124 | ($var:ident = $owner:ident [ $child:literal ] ) => { 125 | let $var = $owner.get_node($child) 126 | .expect(concat!("\"", $child, "\" Child Node")); 127 | }; 128 | } 129 | 130 | #[macro_export] 131 | macro_rules! get_parameter { 132 | ($var:ident : $type:ty = $source:ident [ @ $param:literal ]) => { 133 | let $var = $source.get(concat!("parameters/", $param)) 134 | .try_to_object::<$type>() 135 | .expect(concat!("\"", stringify!($var), ": ", stringify!($type), "\" getting parameter ", "\"parameters/", $param, "\"")); 136 | let $var = unsafe { $var.assume_safe() }; 137 | }; 138 | ($var:ident : $type:ty = $source:expr ; @ $param:literal) => { 139 | let $var = $source.get(concat!("parameters/", $param)) 140 | .try_to_object::<$type>() 141 | .expect(concat!("\"", stringify!($var), ": ", stringify!($type), "\" getting parameter ", "\"parameters/", $param, "\"")); 142 | let $var = unsafe { $var.assume_safe() }; 143 | }; 144 | ($var:ident : $type:ty = $source:ident [ @ $param:ident ]) => { 145 | let $var = $source.get(concat!("parameters/", $param)) 146 | .try_to_object::<$type>() 147 | .expect(concat!("\"", stringify!($var), ": ", stringify!($type), "\" getting parameter ", "\"parameters/", stringify!($param), "\"")); 148 | let $var = unsafe { $var.assume_safe() }; 149 | }; 150 | ($var:ident : $type:ty = $source:expr ; @ $param:ident) => { 151 | let $var = $source.get(concat!("parameters/", $param)) 152 | .try_to_object::<$type>() 153 | .expect(concat!("\"", stringify!($var), ": ", stringify!($type), "\" getting parameter ", "\"parameters/", stringify!($param), "\"")); 154 | let $var = unsafe { $var.assume_safe() }; 155 | }; 156 | ($var:ident : $type:ty = $source:ident [ $param:literal ]) => { 157 | let $var = $source.get($param) 158 | .try_to_object::<$type>() 159 | .expect(concat!("\"", stringify!($var), ": ", stringify!($type), "\" getting parameter ", "\"", $param, "\"")); 160 | let $var = unsafe { $var.assume_safe() }; 161 | }; 162 | ($var:ident : $type:ty = $source:expr; $param:literal) => { 163 | let $var = $source.get($param) 164 | .try_to_object::<$type>() 165 | .expect(concat!("\"", stringify!($var), ": ", stringify!($type), "\" getting parameter ", "\"", $param, "\"")); 166 | let $var = unsafe { $var.assume_safe() }; 167 | }; 168 | ($var:ident : $type:ty = $source:ident [ $param:ident ]) => { 169 | let $var = $source.get($param) 170 | .try_to_object::<$type>() 171 | .expect(concat!("\"", stringify!($var), ": ", stringify!($type), "\" getting parameter ", "\"", stringify!($param), "\"")); 172 | let $var = unsafe { $var.assume_safe() }; 173 | }; 174 | ($var:ident : $type:ty = $source:expr; $param:ident) => { 175 | let $var = $source.get($param) 176 | .try_to_object::<$type>() 177 | .expect(concat!("\"", stringify!($var), ": ", stringify!($type), "\" getting parameter ", "\"", stringify!($param), "\"")); 178 | let $var = unsafe { $var.assume_safe() }; 179 | }; 180 | ($source:ident [ $param:literal ]) => {{ 181 | unsafe { $source.assume_safe() }.get($param) 182 | }}; 183 | ($source:expr ; $param:literal) => {{ 184 | unsafe { $source.assume_safe() }.get($param) 185 | }}; 186 | ($source:ident [ $param:ident ]) => {{ 187 | unsafe { $source.assume_safe() }.get($param) 188 | }}; 189 | ($source:expr ; $param:ident) => {{ 190 | unsafe { $source.assume_safe() }.get($param) 191 | }}; 192 | } 193 | 194 | #[macro_export] 195 | macro_rules! load_resource { 196 | ($res:ident : $type:ty = $src:literal $code:block) => { 197 | if let Some($res) = ResourceLoader::godot_singleton() 198 | .load(concat!("res://", $src), stringify!($type), false) 199 | .and_then(|res| res.cast::<$type>()) { 200 | let $res = unsafe { $res.assume_safe() }; 201 | $code 202 | } 203 | }; 204 | ($res:ident : $type:ty = $src:expr => $code:block) => { 205 | if let Some($res) = ResourceLoader::godot_singleton() 206 | .load($src, stringify!($type), false) 207 | .and_then(|res| res.cast::<$type>()) { 208 | let $res = unsafe { $res.assume_safe() }; 209 | $code 210 | } 211 | }; 212 | ($src:literal: $type:ty) => { 213 | unsafe { 214 | ResourceLoader::godot_singleton() 215 | .load(concat!("res://", $src), stringify!($type), false) 216 | .and_then(|res| res.cast::<$type>()) 217 | .assume_safe() 218 | } 219 | }; 220 | ($src:expr; $type:ty) => { 221 | unsafe { 222 | ResourceLoader::godot_singleton() 223 | .load($src, stringify!($type), false) 224 | .and_then(|res| res.cast::<$type>()) 225 | .assume_safe() 226 | } 227 | }; 228 | } 229 | 230 | #[macro_export] 231 | macro_rules! set_parameter { 232 | (? $var:expr; $($param:literal = $value:expr),*) => {unsafe { 233 | let argument = $var.as_ref().unwrap().assume_safe(); 234 | $( 235 | argument.set($param, $value); 236 | )* 237 | }}; 238 | (? $var:expr; $($param:ident = $value:expr),*) => {unsafe { 239 | let argument = $var.as_ref().unwrap().assume_safe(); 240 | $( 241 | argument.set($param, $value); 242 | )* 243 | }}; 244 | (? $var:expr; @ $($param:literal = $value:expr),*) => {unsafe { 245 | let argument = $var.as_ref().unwrap().assume_safe(); 246 | $( 247 | argument.set(concat!("parameters/", $param), $value); 248 | )* 249 | }}; 250 | (? $var:expr; @ $($param:ident = $value:expr),*) => {unsafe { 251 | let argument = $var.as_ref().unwrap().assume_safe(); 252 | $( 253 | argument.set(concat!("parameters/", $param), $value); 254 | )* 255 | }}; 256 | ($var:expr; $($param:literal = $value:expr),*) => {unsafe { 257 | let argument = $var.assume_safe(); 258 | $( 259 | argument.set($param, $value); 260 | )* 261 | }}; 262 | ($var:expr; $($param:ident = $value:expr),*) => {unsafe { 263 | let argument = $var.assume_safe(); 264 | $( 265 | argument.set($param, $value); 266 | )* 267 | }}; 268 | ($var:expr; @ $($param:literal = $value:expr),*) => {unsafe { 269 | let argument = $var.assume_safe(); 270 | $( 271 | argument.set(concat!("parameters/", $param), $value); 272 | )* 273 | }}; 274 | ($var:expr; @ $($param:ident = $value:expr),*) => {unsafe { 275 | let argument = $var.assume_safe(); 276 | $( 277 | argument.set(concat!("parameters/", $param), $value); 278 | )* 279 | }}; 280 | } 281 | -------------------------------------------------------------------------------- /action-rpg-rs/src/wander.rs: -------------------------------------------------------------------------------- 1 | use gdnative::api::*; 2 | use gdnative::prelude::*; 3 | 4 | use crate::{assume_safe, child_node, get_parameter}; 5 | 6 | type Duration = f64; 7 | type WanderRange = i64; 8 | 9 | pub(crate) const METHOD_IS_TIMER_COMPLETE: &str = "is_timer_complete"; 10 | pub(crate) const METHOD_START_TIMER: &str = "start_timer"; 11 | 12 | pub(crate) const PROPERTY_TARGET_POSITION: &str = "target_position"; 13 | pub(crate) const PROPERTY_WANDER_RANGE: &str = "wander_ranger"; 14 | 15 | const DEFAULT_WANDER_RANGE: WanderRange = 32; 16 | 17 | #[derive(NativeClass)] 18 | #[inherit(Node2D)] 19 | #[register_with(Self::register)] 20 | pub struct WanderController { 21 | rand: Ref, 22 | start_position: Vector2, 23 | #[property] 24 | target_position: Vector2, 25 | timer: Option>, 26 | #[property] 27 | wander_ranger: WanderRange, 28 | } 29 | 30 | impl WanderController { 31 | fn new(_owner: &Node2D) -> Self { 32 | WanderController { 33 | rand: RandomNumberGenerator::new(), 34 | start_position: Vector2::zero(), 35 | target_position: Vector2::zero(), 36 | timer: None, 37 | wander_ranger: DEFAULT_WANDER_RANGE 38 | } 39 | } 40 | 41 | fn register(builder: &ClassBuilder) { 42 | builder 43 | .add_property::(PROPERTY_WANDER_RANGE) 44 | .with_getter(|s: &Self, _| s.wander_ranger) 45 | .with_setter(|s: &mut Self, _, value| s.wander_ranger = value) 46 | .with_default(DEFAULT_WANDER_RANGE) 47 | .done(); 48 | 49 | builder 50 | .add_property::(PROPERTY_TARGET_POSITION) 51 | .with_getter(|s: &Self, _| s.target_position) 52 | .with_setter(|s: &mut Self, _, value| s.target_position = value) 53 | .with_usage(PropertyUsage::NOEDITOR) 54 | .done(); 55 | } 56 | } 57 | 58 | #[methods] 59 | impl WanderController { 60 | #[export] 61 | fn _ready(&mut self, owner: &Node2D) { 62 | self.start_position = owner.global_position(); 63 | self.timer = Some(child_node!(claim owner["Timer"]: Timer)); 64 | 65 | // bats seemed to have to the same pattern if they 66 | // were not interrupted by a chase state, this fixed that 67 | self.rand.randomize(); 68 | 69 | self.update_target_position(); 70 | } 71 | 72 | #[export] 73 | fn is_timer_complete(&self, _owner: &Node2D) -> bool{ 74 | get_parameter!(self.timer.unwrap(); "time_left").to_f64() == 0.0 75 | } 76 | 77 | #[export] 78 | fn start_timer(&self, _owner: &Node2D, duration: Duration) { 79 | assume_safe!(self.timer).start(duration); 80 | } 81 | 82 | #[export] 83 | #[allow(non_snake_case)] 84 | fn _on_Timer_timeout(&mut self, _owner: &Node2D) { 85 | self.update_target_position() 86 | } 87 | 88 | #[inline] 89 | fn random_wander_range(&self) -> f32 { 90 | self.rand.randf_range(-self.wander_ranger as f64, self.wander_ranger as f64) as f32 91 | } 92 | 93 | #[inline] 94 | fn update_target_position(&mut self) { 95 | let target_vector = Vector2::new(self.random_wander_range(), self.random_wander_range()); 96 | 97 | self.target_position = self.start_position + target_vector 98 | } 99 | } -------------------------------------------------------------------------------- /action-rpg/.gitignore: -------------------------------------------------------------------------------- 1 | # Godot-specific ignores 2 | .import/ 3 | export.cfg 4 | export_presets.cfg 5 | 6 | # Imported translations (automatically generated from CSV files) 7 | *.translation 8 | 9 | # Mono-specific ignores 10 | .mono/ 11 | data_*/ -------------------------------------------------------------------------------- /action-rpg/Colliders/HitBox.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene format=2] 2 | 3 | [node name="HitBox" type="Area2D"] 4 | collision_layer = 0 5 | collision_mask = 0 6 | 7 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 8 | -------------------------------------------------------------------------------- /action-rpg/Colliders/HurtBox.gdns: -------------------------------------------------------------------------------- 1 | [gd_resource type="NativeScript" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://action_rpg_tutorial_library.gdnlib" type="GDNativeLibrary" id=1] 4 | 5 | [resource] 6 | resource_name = "HurtBox" 7 | class_name = "HurtBox" 8 | library = ExtResource( 1 ) 9 | -------------------------------------------------------------------------------- /action-rpg/Colliders/HurtBox.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://Colliders/HurtBox.gdns" type="Script" id=1] 4 | 5 | [node name="HurtBox" type="Area2D"] 6 | collision_layer = 0 7 | collision_mask = 0 8 | script = ExtResource( 1 ) 9 | 10 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 11 | 12 | [node name="Timer" type="Timer" parent="."] 13 | one_shot = true 14 | 15 | [connection signal="invincibility_ended" from="." to="." method="_on_HurtBox_invincibility_ended" flags=3] 16 | [connection signal="invincibility_started" from="." to="." method="_on_HurtBox_invincibility_started" flags=3] 17 | [connection signal="timeout" from="Timer" to="." method="_on_Timer_timeout"] 18 | -------------------------------------------------------------------------------- /action-rpg/Colliders/SoftCollision.gdns: -------------------------------------------------------------------------------- 1 | [gd_resource type="NativeScript" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://action_rpg_tutorial_library.gdnlib" type="GDNativeLibrary" id=1] 4 | 5 | [resource] 6 | resource_name = "SoftCollision" 7 | class_name = "SoftCollision" 8 | library = ExtResource( 1 ) 9 | -------------------------------------------------------------------------------- /action-rpg/Colliders/SoftCollision.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://Colliders/SoftCollision.gdns" type="Script" id=1] 4 | 5 | [node name="SoftCollision" type="Area2D"] 6 | collision_layer = 32 7 | collision_mask = 32 8 | script = ExtResource( 1 ) 9 | 10 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 11 | -------------------------------------------------------------------------------- /action-rpg/Effects/Effect.gdns: -------------------------------------------------------------------------------- 1 | [gd_resource type="NativeScript" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://action_rpg_tutorial_library.gdnlib" type="GDNativeLibrary" id=1] 4 | 5 | [resource] 6 | resource_name = "GrassEffect" 7 | class_name = "Effect" 8 | library = ExtResource( 1 ) 9 | -------------------------------------------------------------------------------- /action-rpg/Effects/EnemyDeathEffect.gdns: -------------------------------------------------------------------------------- 1 | [gd_resource type="NativeScript" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://action_rpg_tutorial_library.gdnlib" type="GDNativeLibrary" id=1] 4 | 5 | [resource] 6 | resource_name = "Effect" 7 | class_name = "Effect" 8 | library = ExtResource( 1 ) 9 | -------------------------------------------------------------------------------- /action-rpg/Effects/EnemyDeathEffect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/Effects/EnemyDeathEffect.png -------------------------------------------------------------------------------- /action-rpg/Effects/EnemyDeathEffect.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/EnemyDeathEffect.png-b5f2aceef00ffbc3c194806ee7af6666.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://Effects/EnemyDeathEffect.png" 13 | dest_files=[ "res://.import/EnemyDeathEffect.png-b5f2aceef00ffbc3c194806ee7af6666.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /action-rpg/Effects/EnemyDeathEffect.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=15 format=2] 2 | 3 | [ext_resource path="res://Effects/EnemyDeathEffect.png" type="Texture" id=1] 4 | [ext_resource path="res://Effects/EnemyDeathEffect.gdns" type="Script" id=2] 5 | [ext_resource path="res://Music and Sounds/EnemyDie.wav" type="AudioStream" id=3] 6 | 7 | [sub_resource type="AtlasTexture" id=1] 8 | atlas = ExtResource( 1 ) 9 | region = Rect2( 0, 0, 32, 32 ) 10 | 11 | [sub_resource type="AtlasTexture" id=2] 12 | atlas = ExtResource( 1 ) 13 | region = Rect2( 32, 0, 32, 32 ) 14 | 15 | [sub_resource type="AtlasTexture" id=3] 16 | atlas = ExtResource( 1 ) 17 | region = Rect2( 64, 0, 32, 32 ) 18 | 19 | [sub_resource type="AtlasTexture" id=4] 20 | atlas = ExtResource( 1 ) 21 | region = Rect2( 96, 0, 32, 32 ) 22 | 23 | [sub_resource type="AtlasTexture" id=5] 24 | atlas = ExtResource( 1 ) 25 | region = Rect2( 128, 0, 32, 32 ) 26 | 27 | [sub_resource type="AtlasTexture" id=6] 28 | atlas = ExtResource( 1 ) 29 | region = Rect2( 160, 0, 32, 32 ) 30 | 31 | [sub_resource type="AtlasTexture" id=7] 32 | atlas = ExtResource( 1 ) 33 | region = Rect2( 192, 0, 32, 32 ) 34 | 35 | [sub_resource type="AtlasTexture" id=8] 36 | atlas = ExtResource( 1 ) 37 | region = Rect2( 224, 0, 32, 32 ) 38 | 39 | [sub_resource type="AtlasTexture" id=9] 40 | atlas = ExtResource( 1 ) 41 | region = Rect2( 256, 0, 32, 32 ) 42 | 43 | [sub_resource type="AtlasTexture" id=10] 44 | atlas = ExtResource( 1 ) 45 | region = Rect2( 288, 0, 32, 32 ) 46 | 47 | [sub_resource type="SpriteFrames" id=11] 48 | animations = [ { 49 | "frames": [ SubResource( 1 ), SubResource( 2 ), SubResource( 3 ), SubResource( 4 ), SubResource( 5 ), SubResource( 6 ), SubResource( 7 ), SubResource( 8 ), SubResource( 9 ), SubResource( 10 ) ], 50 | "loop": true, 51 | "name": "Animate", 52 | "speed": 15.0 53 | } ] 54 | 55 | [node name="EnemyDeathEffect" type="AnimatedSprite"] 56 | frames = SubResource( 11 ) 57 | animation = "Animate" 58 | offset = Vector2( 0, -16 ) 59 | script = ExtResource( 2 ) 60 | 61 | [node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."] 62 | stream = ExtResource( 3 ) 63 | autoplay = true 64 | -------------------------------------------------------------------------------- /action-rpg/Effects/GrassEffect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/Effects/GrassEffect.png -------------------------------------------------------------------------------- /action-rpg/Effects/GrassEffect.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/GrassEffect.png-e13c4c94a67cb5ef9b389379ed46bf17.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://Effects/GrassEffect.png" 13 | dest_files=[ "res://.import/GrassEffect.png-e13c4c94a67cb5ef9b389379ed46bf17.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /action-rpg/Effects/GrassEffect.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=9 format=2] 2 | 3 | [ext_resource path="res://Effects/GrassEffect.png" type="Texture" id=1] 4 | [ext_resource path="res://Effects/Effect.gdns" type="Script" id=2] 5 | 6 | [sub_resource type="AtlasTexture" id=1] 7 | atlas = ExtResource( 1 ) 8 | region = Rect2( 0, 0, 32, 32 ) 9 | 10 | [sub_resource type="AtlasTexture" id=2] 11 | atlas = ExtResource( 1 ) 12 | region = Rect2( 32, 0, 32, 32 ) 13 | 14 | [sub_resource type="AtlasTexture" id=3] 15 | atlas = ExtResource( 1 ) 16 | region = Rect2( 64, 0, 32, 32 ) 17 | 18 | [sub_resource type="AtlasTexture" id=4] 19 | atlas = ExtResource( 1 ) 20 | region = Rect2( 96, 0, 32, 32 ) 21 | 22 | [sub_resource type="AtlasTexture" id=5] 23 | atlas = ExtResource( 1 ) 24 | region = Rect2( 128, 0, 32, 32 ) 25 | 26 | [sub_resource type="SpriteFrames" id=6] 27 | animations = [ { 28 | "frames": [ SubResource( 1 ), SubResource( 2 ), SubResource( 3 ), SubResource( 4 ), SubResource( 5 ) ], 29 | "loop": true, 30 | "name": "Animate", 31 | "speed": 15.0 32 | } ] 33 | 34 | [node name="GrassEfect" type="AnimatedSprite"] 35 | frames = SubResource( 6 ) 36 | animation = "Animate" 37 | centered = false 38 | offset = Vector2( -8, -8 ) 39 | script = ExtResource( 2 ) 40 | -------------------------------------------------------------------------------- /action-rpg/Effects/HitEffect.gdns: -------------------------------------------------------------------------------- 1 | [gd_resource type="NativeScript" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://action_rpg_tutorial_library.gdnlib" type="GDNativeLibrary" id=1] 4 | 5 | [resource] 6 | resource_name = "Effect" 7 | class_name = "Effect" 8 | library = ExtResource( 1 ) 9 | -------------------------------------------------------------------------------- /action-rpg/Effects/HitEffect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/Effects/HitEffect.png -------------------------------------------------------------------------------- /action-rpg/Effects/HitEffect.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/HitEffect.png-4a350a9d7635dd7543954c4499aead9c.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://Effects/HitEffect.png" 13 | dest_files=[ "res://.import/HitEffect.png-4a350a9d7635dd7543954c4499aead9c.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /action-rpg/Effects/HitEffect.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=2] 2 | 3 | [ext_resource path="res://Effects/HitEffect.gdns" type="Script" id=1] 4 | [ext_resource path="res://Effects/HitEffect.png" type="Texture" id=2] 5 | [ext_resource path="res://Music and Sounds/Hit.wav" type="AudioStream" id=3] 6 | 7 | [sub_resource type="AtlasTexture" id=1] 8 | atlas = ExtResource( 2 ) 9 | region = Rect2( 0, 0, 24, 24 ) 10 | 11 | [sub_resource type="AtlasTexture" id=2] 12 | atlas = ExtResource( 2 ) 13 | region = Rect2( 24, 0, 24, 24 ) 14 | 15 | [sub_resource type="SpriteFrames" id=3] 16 | animations = [ { 17 | "frames": [ SubResource( 1 ), SubResource( 2 ) ], 18 | "loop": true, 19 | "name": "Animate", 20 | "speed": 15.0 21 | } ] 22 | 23 | [node name="HitEffect" type="AnimatedSprite"] 24 | frames = SubResource( 3 ) 25 | animation = "Animate" 26 | offset = Vector2( 0, -16 ) 27 | script = ExtResource( 1 ) 28 | 29 | [node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."] 30 | stream = ExtResource( 3 ) 31 | autoplay = true 32 | -------------------------------------------------------------------------------- /action-rpg/Enemies/Bat.gdns: -------------------------------------------------------------------------------- 1 | [gd_resource type="NativeScript" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://action_rpg_tutorial_library.gdnlib" type="GDNativeLibrary" id=1] 4 | 5 | [resource] 6 | resource_name = "Bat" 7 | class_name = "Bat" 8 | library = ExtResource( 1 ) 9 | -------------------------------------------------------------------------------- /action-rpg/Enemies/Bat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/Enemies/Bat.png -------------------------------------------------------------------------------- /action-rpg/Enemies/Bat.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/Bat.png-38405154cd5b8ff1c5e2fcf1cef1c3e6.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://Enemies/Bat.png" 13 | dest_files=[ "res://.import/Bat.png-38405154cd5b8ff1c5e2fcf1cef1c3e6.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /action-rpg/Enemies/Bat.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=25 format=2] 2 | 3 | [ext_resource path="res://Enemies/Bat.png" type="Texture" id=1] 4 | [ext_resource path="res://Shadows/SmallShadow.png" type="Texture" id=2] 5 | [ext_resource path="res://Colliders/HurtBox.tscn" type="PackedScene" id=3] 6 | [ext_resource path="res://Enemies/Bat.gdns" type="Script" id=4] 7 | [ext_resource path="res://Stats/Stats.tscn" type="PackedScene" id=5] 8 | [ext_resource path="res://Enemies/PlayerDetectionZone.tscn" type="PackedScene" id=6] 9 | [ext_resource path="res://Colliders/HitBox.tscn" type="PackedScene" id=7] 10 | [ext_resource path="res://Colliders/SoftCollision.tscn" type="PackedScene" id=8] 11 | [ext_resource path="res://Enemies/WanderController.tscn" type="PackedScene" id=9] 12 | [ext_resource path="res://Shaders/WhiteColorShader.gdshader" type="Shader" id=10] 13 | 14 | [sub_resource type="ShaderMaterial" id=1] 15 | resource_local_to_scene = true 16 | shader = ExtResource( 10 ) 17 | shader_param/active = false 18 | 19 | [sub_resource type="AtlasTexture" id=2] 20 | atlas = ExtResource( 1 ) 21 | region = Rect2( 0, 0, 16, 24 ) 22 | 23 | [sub_resource type="AtlasTexture" id=3] 24 | atlas = ExtResource( 1 ) 25 | region = Rect2( 16, 0, 16, 24 ) 26 | 27 | [sub_resource type="AtlasTexture" id=4] 28 | atlas = ExtResource( 1 ) 29 | region = Rect2( 32, 0, 16, 24 ) 30 | 31 | [sub_resource type="AtlasTexture" id=5] 32 | atlas = ExtResource( 1 ) 33 | region = Rect2( 48, 0, 16, 24 ) 34 | 35 | [sub_resource type="AtlasTexture" id=6] 36 | atlas = ExtResource( 1 ) 37 | region = Rect2( 64, 0, 16, 24 ) 38 | 39 | [sub_resource type="SpriteFrames" id=7] 40 | animations = [ { 41 | "frames": [ SubResource( 2 ), SubResource( 3 ), SubResource( 4 ), SubResource( 5 ), SubResource( 6 ) ], 42 | "loop": true, 43 | "name": "fly", 44 | "speed": 8.0 45 | } ] 46 | 47 | [sub_resource type="CircleShape2D" id=8] 48 | radius = 4.0 49 | 50 | [sub_resource type="CapsuleShape2D" id=9] 51 | radius = 6.0 52 | height = 6.0 53 | 54 | [sub_resource type="CircleShape2D" id=10] 55 | radius = 60.0 56 | 57 | [sub_resource type="CircleShape2D" id=11] 58 | radius = 3.16228 59 | 60 | [sub_resource type="CircleShape2D" id=12] 61 | radius = 5.0 62 | 63 | [sub_resource type="Animation" id=13] 64 | length = 0.2 65 | loop = true 66 | tracks/0/type = "value" 67 | tracks/0/path = NodePath("AnimatedSprite:material:shader_param/active") 68 | tracks/0/interp = 1 69 | tracks/0/loop_wrap = true 70 | tracks/0/imported = false 71 | tracks/0/enabled = true 72 | tracks/0/keys = { 73 | "times": PoolRealArray( 0, 0.1 ), 74 | "transitions": PoolRealArray( 1, 1 ), 75 | "update": 1, 76 | "values": [ true, false ] 77 | } 78 | 79 | [sub_resource type="Animation" id=14] 80 | length = 0.1 81 | tracks/0/type = "value" 82 | tracks/0/path = NodePath("AnimatedSprite:material:shader_param/active") 83 | tracks/0/interp = 1 84 | tracks/0/loop_wrap = true 85 | tracks/0/imported = false 86 | tracks/0/enabled = true 87 | tracks/0/keys = { 88 | "times": PoolRealArray( 0 ), 89 | "transitions": PoolRealArray( 1 ), 90 | "update": 1, 91 | "values": [ false ] 92 | } 93 | 94 | [node name="Bat" type="KinematicBody2D"] 95 | collision_layer = 16 96 | script = ExtResource( 4 ) 97 | 98 | [node name="AnimatedSprite" type="AnimatedSprite" parent="."] 99 | material = SubResource( 1 ) 100 | frames = SubResource( 7 ) 101 | animation = "fly" 102 | frame = 4 103 | playing = true 104 | offset = Vector2( 0, -12 ) 105 | 106 | [node name="ShadowSprite" type="Sprite" parent="."] 107 | texture = ExtResource( 2 ) 108 | 109 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 110 | visible = false 111 | shape = SubResource( 8 ) 112 | 113 | [node name="HurtBox" parent="." instance=ExtResource( 3 )] 114 | collision_layer = 8 115 | 116 | [node name="CollisionShape2D" parent="HurtBox" index="0"] 117 | visible = false 118 | position = Vector2( 0, -13 ) 119 | shape = SubResource( 9 ) 120 | 121 | [node name="Stats" parent="." instance=ExtResource( 5 )] 122 | max_health = 2 123 | 124 | [node name="PlayerDetectionZone" parent="." instance=ExtResource( 6 )] 125 | 126 | [node name="CollisionShape2D" parent="PlayerDetectionZone" index="0"] 127 | visible = false 128 | modulate = Color( 1, 1, 1, 0.196078 ) 129 | shape = SubResource( 10 ) 130 | 131 | [node name="HitBox" parent="." instance=ExtResource( 7 )] 132 | collision_mask = 4 133 | 134 | [node name="CollisionShape2D" parent="HitBox" index="0"] 135 | visible = false 136 | position = Vector2( 0, -15 ) 137 | shape = SubResource( 11 ) 138 | 139 | [node name="SoftCollision" parent="." instance=ExtResource( 8 )] 140 | 141 | [node name="CollisionShape2D" parent="SoftCollision" index="0"] 142 | visible = false 143 | shape = SubResource( 12 ) 144 | 145 | [node name="WanderController" parent="." instance=ExtResource( 9 )] 146 | 147 | [node name="BlinkAnimationPlayer" type="AnimationPlayer" parent="."] 148 | anims/Start = SubResource( 13 ) 149 | anims/Stop = SubResource( 14 ) 150 | 151 | [connection signal="area_entered" from="HurtBox" to="." method="_on_HurtBox_area_entered"] 152 | [connection signal="invincibility_ended" from="HurtBox" to="." method="_on_HurtBox_invincibility_ended" flags=3] 153 | [connection signal="invincibility_started" from="HurtBox" to="." method="_on_HurtBox_invincibility_started" flags=3] 154 | [connection signal="no_health" from="Stats" to="." method="_on_Stats_no_health" flags=3] 155 | 156 | [editable path="HurtBox"] 157 | [editable path="PlayerDetectionZone"] 158 | [editable path="HitBox"] 159 | [editable path="SoftCollision"] 160 | -------------------------------------------------------------------------------- /action-rpg/Enemies/PlayerDetectionZone.gdns: -------------------------------------------------------------------------------- 1 | [gd_resource type="NativeScript" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://action_rpg_tutorial_library.gdnlib" type="GDNativeLibrary" id=1] 4 | 5 | [resource] 6 | resource_name = "PlayerDetectionZone" 7 | class_name = "PlayerDetectionZone" 8 | library = ExtResource( 1 ) 9 | -------------------------------------------------------------------------------- /action-rpg/Enemies/PlayerDetectionZone.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://Enemies/PlayerDetectionZone.gdns" type="Script" id=1] 4 | 5 | [node name="PlayerDetectionZone" type="Area2D"] 6 | collision_layer = 0 7 | collision_mask = 2 8 | script = ExtResource( 1 ) 9 | 10 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 11 | 12 | [connection signal="body_entered" from="." to="." method="_on_PlayerDetectionZone_body_entered"] 13 | [connection signal="body_exited" from="." to="." method="_on_PlayerDetectionZone_body_exited"] 14 | -------------------------------------------------------------------------------- /action-rpg/Enemies/WanderController.gdns: -------------------------------------------------------------------------------- 1 | [gd_resource type="NativeScript" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://action_rpg_tutorial_library.gdnlib" type="GDNativeLibrary" id=1] 4 | 5 | [resource] 6 | resource_name = "WanderController" 7 | class_name = "WanderController" 8 | library = ExtResource( 1 ) 9 | -------------------------------------------------------------------------------- /action-rpg/Enemies/WanderController.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://Enemies/WanderController.gdns" type="Script" id=1] 4 | 5 | [node name="WanderController" type="Node2D"] 6 | script = ExtResource( 1 ) 7 | 8 | [node name="Timer" type="Timer" parent="."] 9 | one_shot = true 10 | autostart = true 11 | 12 | [connection signal="timeout" from="Timer" to="." method="_on_Timer_timeout"] 13 | -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/EnemyDie.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/Music and Sounds/EnemyDie.wav -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/EnemyDie.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/EnemyDie.wav-a486c87c5ffce10facfdaf6e118ee22d.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://Music and Sounds/EnemyDie.wav" 10 | dest_files=[ "res://.import/EnemyDie.wav-a486c87c5ffce10facfdaf6e118ee22d.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop=false 21 | compress/mode=0 22 | -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/Evade.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/Music and Sounds/Evade.wav -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/Evade.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/Evade.wav-cd612618661f05b5e39dccebc8901a0b.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://Music and Sounds/Evade.wav" 10 | dest_files=[ "res://.import/Evade.wav-cd612618661f05b5e39dccebc8901a0b.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop=false 21 | compress/mode=0 22 | -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/Hit.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/Music and Sounds/Hit.wav -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/Hit.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/Hit.wav-0fa8d06aa64f0c3d5cf8be50a2076760.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://Music and Sounds/Hit.wav" 10 | dest_files=[ "res://.import/Hit.wav-0fa8d06aa64f0c3d5cf8be50a2076760.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop=false 21 | compress/mode=0 22 | -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/Hurt.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/Music and Sounds/Hurt.wav -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/Hurt.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/Hurt.wav-9a8feff0886efa16ed78b7703de45922.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://Music and Sounds/Hurt.wav" 10 | dest_files=[ "res://.import/Hurt.wav-9a8feff0886efa16ed78b7703de45922.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop=false 21 | compress/mode=0 22 | -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/Menu Move.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/Music and Sounds/Menu Move.wav -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/Menu Move.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/Menu Move.wav-d97a887eeb77e9f82d0179618aff26aa.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://Music and Sounds/Menu Move.wav" 10 | dest_files=[ "res://.import/Menu Move.wav-d97a887eeb77e9f82d0179618aff26aa.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop=false 21 | compress/mode=0 22 | -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/Menu Select.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/Music and Sounds/Menu Select.wav -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/Menu Select.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/Menu Select.wav-cf35ab6ee2afc13096e6bf584e78dcaf.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://Music and Sounds/Menu Select.wav" 10 | dest_files=[ "res://.import/Menu Select.wav-cf35ab6ee2afc13096e6bf584e78dcaf.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop=false 21 | compress/mode=0 22 | -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/Music.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/Music and Sounds/Music.mp3 -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/Music.mp3.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="mp3" 4 | type="AudioStreamMP3" 5 | path="res://.import/Music.mp3-fbb50ed95d66ff7af78edb9c3a8f7b4d.mp3str" 6 | 7 | [deps] 8 | 9 | source_file="res://Music and Sounds/Music.mp3" 10 | dest_files=[ "res://.import/Music.mp3-fbb50ed95d66ff7af78edb9c3a8f7b4d.mp3str" ] 11 | 12 | [params] 13 | 14 | loop=true 15 | loop_offset=0 16 | -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/Pause.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/Music and Sounds/Pause.wav -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/Pause.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/Pause.wav-2283faeb42b74e3f6e4809669cc9a8ab.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://Music and Sounds/Pause.wav" 10 | dest_files=[ "res://.import/Pause.wav-2283faeb42b74e3f6e4809669cc9a8ab.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop=false 21 | compress/mode=0 22 | -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/Swipe.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/Music and Sounds/Swipe.wav -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/Swipe.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/Swipe.wav-1b0f341b1727da219872e70c921da651.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://Music and Sounds/Swipe.wav" 10 | dest_files=[ "res://.import/Swipe.wav-1b0f341b1727da219872e70c921da651.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop=false 21 | compress/mode=0 22 | -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/Unpause.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/Music and Sounds/Unpause.wav -------------------------------------------------------------------------------- /action-rpg/Music and Sounds/Unpause.wav.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="wav" 4 | type="AudioStreamSample" 5 | path="res://.import/Unpause.wav-804d58e6ee88c8f6467ca3b5bea5857f.sample" 6 | 7 | [deps] 8 | 9 | source_file="res://Music and Sounds/Unpause.wav" 10 | dest_files=[ "res://.import/Unpause.wav-804d58e6ee88c8f6467ca3b5bea5857f.sample" ] 11 | 12 | [params] 13 | 14 | force/8_bit=false 15 | force/mono=false 16 | force/max_rate=false 17 | force/max_rate_hz=44100 18 | edit/trim=false 19 | edit/normalize=false 20 | edit/loop=false 21 | compress/mode=0 22 | -------------------------------------------------------------------------------- /action-rpg/Player/Player.gdns: -------------------------------------------------------------------------------- 1 | [gd_resource type="NativeScript" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://action_rpg_tutorial_library.gdnlib" type="GDNativeLibrary" id=1] 4 | 5 | [resource] 6 | resource_name = "Player" 7 | class_name = "Player" 8 | library = ExtResource( 1 ) 9 | -------------------------------------------------------------------------------- /action-rpg/Player/Player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/Player/Player.png -------------------------------------------------------------------------------- /action-rpg/Player/Player.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/Player.png-3d0801c65bdfc563657cfa304115f1c7.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://Player/Player.png" 13 | dest_files=[ "res://.import/Player.png-3d0801c65bdfc563657cfa304115f1c7.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /action-rpg/Player/Player.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=60 format=2] 2 | 3 | [ext_resource path="res://Player/Player.png" type="Texture" id=1] 4 | [ext_resource path="res://Player/Player.gdns" type="Script" id=2] 5 | [ext_resource path="res://Colliders/HitBox.tscn" type="PackedScene" id=3] 6 | [ext_resource path="res://Player/SwordHitBox.gdns" type="Script" id=4] 7 | [ext_resource path="res://Colliders/HurtBox.tscn" type="PackedScene" id=5] 8 | [ext_resource path="res://Shadows/MediumShadow.png" type="Texture" id=6] 9 | [ext_resource path="res://Music and Sounds/Swipe.wav" type="AudioStream" id=7] 10 | [ext_resource path="res://Music and Sounds/Evade.wav" type="AudioStream" id=8] 11 | [ext_resource path="res://Shaders/WhiteColorShader.gdshader" type="Shader" id=9] 12 | 13 | [sub_resource type="ShaderMaterial" id=1] 14 | shader = ExtResource( 9 ) 15 | shader_param/active = false 16 | 17 | [sub_resource type="CapsuleShape2D" id=2] 18 | radius = 4.0 19 | height = 4.0 20 | 21 | [sub_resource type="Animation" id=3] 22 | length = 0.4 23 | tracks/0/type = "value" 24 | tracks/0/path = NodePath("Sprite:frame") 25 | tracks/0/interp = 1 26 | tracks/0/loop_wrap = true 27 | tracks/0/imported = false 28 | tracks/0/enabled = true 29 | tracks/0/keys = { 30 | "times": PoolRealArray( 0, 0.1, 0.2, 0.3 ), 31 | "transitions": PoolRealArray( 1, 1, 1, 1 ), 32 | "update": 1, 33 | "values": [ 36, 37, 38, 39 ] 34 | } 35 | tracks/1/type = "method" 36 | tracks/1/path = NodePath(".") 37 | tracks/1/interp = 1 38 | tracks/1/loop_wrap = true 39 | tracks/1/imported = false 40 | tracks/1/enabled = true 41 | tracks/1/keys = { 42 | "times": PoolRealArray( 0.4 ), 43 | "transitions": PoolRealArray( 1 ), 44 | "values": [ { 45 | "args": [ ], 46 | "method": "attack_animation_finished" 47 | } ] 48 | } 49 | tracks/2/type = "value" 50 | tracks/2/path = NodePath("HitboxPivot:rotation_degrees") 51 | tracks/2/interp = 1 52 | tracks/2/loop_wrap = true 53 | tracks/2/imported = false 54 | tracks/2/enabled = true 55 | tracks/2/keys = { 56 | "times": PoolRealArray( 0 ), 57 | "transitions": PoolRealArray( 1 ), 58 | "update": 0, 59 | "values": [ 90.0 ] 60 | } 61 | tracks/3/type = "value" 62 | tracks/3/path = NodePath("HitboxPivot/SwordHitbox/CollisionShape2D:disabled") 63 | tracks/3/interp = 1 64 | tracks/3/loop_wrap = true 65 | tracks/3/imported = false 66 | tracks/3/enabled = true 67 | tracks/3/keys = { 68 | "times": PoolRealArray( 0.1, 0.3 ), 69 | "transitions": PoolRealArray( 1, 1 ), 70 | "update": 1, 71 | "values": [ false, true ] 72 | } 73 | tracks/4/type = "audio" 74 | tracks/4/path = NodePath("AudioStreamPlayer") 75 | tracks/4/interp = 1 76 | tracks/4/loop_wrap = true 77 | tracks/4/imported = false 78 | tracks/4/enabled = true 79 | tracks/4/keys = { 80 | "clips": [ { 81 | "end_offset": 0.0, 82 | "start_offset": 0.0, 83 | "stream": ExtResource( 7 ) 84 | } ], 85 | "times": PoolRealArray( 0.1 ) 86 | } 87 | 88 | [sub_resource type="Animation" id=4] 89 | length = 0.4 90 | tracks/0/type = "value" 91 | tracks/0/path = NodePath("Sprite:frame") 92 | tracks/0/interp = 1 93 | tracks/0/loop_wrap = true 94 | tracks/0/imported = false 95 | tracks/0/enabled = true 96 | tracks/0/keys = { 97 | "times": PoolRealArray( 0, 0.1, 0.2, 0.3 ), 98 | "transitions": PoolRealArray( 1, 1, 1, 1 ), 99 | "update": 1, 100 | "values": [ 32, 33, 34, 35 ] 101 | } 102 | tracks/1/type = "method" 103 | tracks/1/path = NodePath(".") 104 | tracks/1/interp = 1 105 | tracks/1/loop_wrap = true 106 | tracks/1/imported = false 107 | tracks/1/enabled = true 108 | tracks/1/keys = { 109 | "times": PoolRealArray( 0.4 ), 110 | "transitions": PoolRealArray( 1 ), 111 | "values": [ { 112 | "args": [ ], 113 | "method": "attack_animation_finished" 114 | } ] 115 | } 116 | tracks/2/type = "value" 117 | tracks/2/path = NodePath("HitboxPivot:rotation_degrees") 118 | tracks/2/interp = 1 119 | tracks/2/loop_wrap = true 120 | tracks/2/imported = false 121 | tracks/2/enabled = true 122 | tracks/2/keys = { 123 | "times": PoolRealArray( 0 ), 124 | "transitions": PoolRealArray( 1 ), 125 | "update": 0, 126 | "values": [ 180.0 ] 127 | } 128 | tracks/3/type = "value" 129 | tracks/3/path = NodePath("HitboxPivot/SwordHitbox/CollisionShape2D:disabled") 130 | tracks/3/interp = 1 131 | tracks/3/loop_wrap = true 132 | tracks/3/imported = false 133 | tracks/3/enabled = true 134 | tracks/3/keys = { 135 | "times": PoolRealArray( 0.1, 0.3 ), 136 | "transitions": PoolRealArray( 1, 1 ), 137 | "update": 1, 138 | "values": [ false, true ] 139 | } 140 | tracks/4/type = "audio" 141 | tracks/4/path = NodePath("AudioStreamPlayer") 142 | tracks/4/interp = 1 143 | tracks/4/loop_wrap = true 144 | tracks/4/imported = false 145 | tracks/4/enabled = true 146 | tracks/4/keys = { 147 | "clips": [ { 148 | "end_offset": 0.0, 149 | "start_offset": 0.0, 150 | "stream": ExtResource( 7 ) 151 | } ], 152 | "times": PoolRealArray( 0.1 ) 153 | } 154 | 155 | [sub_resource type="Animation" id=5] 156 | length = 0.4 157 | tracks/0/type = "value" 158 | tracks/0/path = NodePath("Sprite:frame") 159 | tracks/0/interp = 1 160 | tracks/0/loop_wrap = true 161 | tracks/0/imported = false 162 | tracks/0/enabled = true 163 | tracks/0/keys = { 164 | "times": PoolRealArray( 0, 0.1, 0.2, 0.3 ), 165 | "transitions": PoolRealArray( 1, 1, 1, 1 ), 166 | "update": 1, 167 | "values": [ 24, 25, 26, 27 ] 168 | } 169 | tracks/1/type = "method" 170 | tracks/1/path = NodePath(".") 171 | tracks/1/interp = 1 172 | tracks/1/loop_wrap = true 173 | tracks/1/imported = false 174 | tracks/1/enabled = true 175 | tracks/1/keys = { 176 | "times": PoolRealArray( 0.4 ), 177 | "transitions": PoolRealArray( 1 ), 178 | "values": [ { 179 | "args": [ ], 180 | "method": "attack_animation_finished" 181 | } ] 182 | } 183 | tracks/2/type = "value" 184 | tracks/2/path = NodePath("HitboxPivot:rotation_degrees") 185 | tracks/2/interp = 1 186 | tracks/2/loop_wrap = true 187 | tracks/2/imported = false 188 | tracks/2/enabled = true 189 | tracks/2/keys = { 190 | "times": PoolRealArray( 0 ), 191 | "transitions": PoolRealArray( 1 ), 192 | "update": 0, 193 | "values": [ 0.0 ] 194 | } 195 | tracks/3/type = "value" 196 | tracks/3/path = NodePath("HitboxPivot/SwordHitbox/CollisionShape2D:disabled") 197 | tracks/3/interp = 1 198 | tracks/3/loop_wrap = true 199 | tracks/3/imported = false 200 | tracks/3/enabled = true 201 | tracks/3/keys = { 202 | "times": PoolRealArray( 0.1, 0.3 ), 203 | "transitions": PoolRealArray( 1, 1 ), 204 | "update": 1, 205 | "values": [ false, true ] 206 | } 207 | tracks/4/type = "audio" 208 | tracks/4/path = NodePath("AudioStreamPlayer") 209 | tracks/4/interp = 1 210 | tracks/4/loop_wrap = true 211 | tracks/4/imported = false 212 | tracks/4/enabled = true 213 | tracks/4/keys = { 214 | "clips": [ { 215 | "end_offset": 0.0, 216 | "start_offset": 0.0, 217 | "stream": ExtResource( 7 ) 218 | } ], 219 | "times": PoolRealArray( 0.1 ) 220 | } 221 | 222 | [sub_resource type="Animation" id=6] 223 | length = 0.4 224 | tracks/0/type = "value" 225 | tracks/0/path = NodePath("Sprite:frame") 226 | tracks/0/interp = 1 227 | tracks/0/loop_wrap = true 228 | tracks/0/imported = false 229 | tracks/0/enabled = true 230 | tracks/0/keys = { 231 | "times": PoolRealArray( 0, 0.1, 0.2, 0.3 ), 232 | "transitions": PoolRealArray( 1, 1, 1, 1 ), 233 | "update": 1, 234 | "values": [ 28, 29, 30, 31 ] 235 | } 236 | tracks/1/type = "method" 237 | tracks/1/path = NodePath(".") 238 | tracks/1/interp = 1 239 | tracks/1/loop_wrap = true 240 | tracks/1/imported = false 241 | tracks/1/enabled = true 242 | tracks/1/keys = { 243 | "times": PoolRealArray( 0.4 ), 244 | "transitions": PoolRealArray( 1 ), 245 | "values": [ { 246 | "args": [ ], 247 | "method": "attack_animation_finished" 248 | } ] 249 | } 250 | tracks/2/type = "value" 251 | tracks/2/path = NodePath("HitboxPivot:rotation_degrees") 252 | tracks/2/interp = 1 253 | tracks/2/loop_wrap = true 254 | tracks/2/imported = false 255 | tracks/2/enabled = true 256 | tracks/2/keys = { 257 | "times": PoolRealArray( 0 ), 258 | "transitions": PoolRealArray( 1 ), 259 | "update": 0, 260 | "values": [ 270.0 ] 261 | } 262 | tracks/3/type = "value" 263 | tracks/3/path = NodePath("HitboxPivot/SwordHitbox/CollisionShape2D:disabled") 264 | tracks/3/interp = 1 265 | tracks/3/loop_wrap = true 266 | tracks/3/imported = false 267 | tracks/3/enabled = true 268 | tracks/3/keys = { 269 | "times": PoolRealArray( 0.1, 0.3 ), 270 | "transitions": PoolRealArray( 1, 1 ), 271 | "update": 1, 272 | "values": [ false, true ] 273 | } 274 | tracks/4/type = "audio" 275 | tracks/4/path = NodePath("AudioStreamPlayer") 276 | tracks/4/interp = 1 277 | tracks/4/loop_wrap = true 278 | tracks/4/imported = false 279 | tracks/4/enabled = true 280 | tracks/4/keys = { 281 | "clips": [ { 282 | "end_offset": 0.0, 283 | "start_offset": 0.0, 284 | "stream": ExtResource( 7 ) 285 | } ], 286 | "times": PoolRealArray( 0.1 ) 287 | } 288 | 289 | [sub_resource type="Animation" id=7] 290 | length = 0.1 291 | loop = true 292 | tracks/0/type = "value" 293 | tracks/0/path = NodePath("Sprite:frame") 294 | tracks/0/interp = 1 295 | tracks/0/loop_wrap = true 296 | tracks/0/imported = false 297 | tracks/0/enabled = true 298 | tracks/0/keys = { 299 | "times": PoolRealArray( 0 ), 300 | "transitions": PoolRealArray( 1 ), 301 | "update": 1, 302 | "values": [ 18 ] 303 | } 304 | 305 | [sub_resource type="Animation" id=8] 306 | length = 0.1 307 | loop = true 308 | tracks/0/type = "value" 309 | tracks/0/path = NodePath("Sprite:frame") 310 | tracks/0/interp = 1 311 | tracks/0/loop_wrap = true 312 | tracks/0/imported = false 313 | tracks/0/enabled = true 314 | tracks/0/keys = { 315 | "times": PoolRealArray( 0.00560983 ), 316 | "transitions": PoolRealArray( 1 ), 317 | "update": 1, 318 | "values": [ 12 ] 319 | } 320 | 321 | [sub_resource type="Animation" id=9] 322 | length = 0.1 323 | loop = true 324 | tracks/0/type = "value" 325 | tracks/0/path = NodePath("Sprite:frame") 326 | tracks/0/interp = 1 327 | tracks/0/loop_wrap = true 328 | tracks/0/imported = false 329 | tracks/0/enabled = true 330 | tracks/0/keys = { 331 | "times": PoolRealArray( 0 ), 332 | "transitions": PoolRealArray( 1 ), 333 | "update": 1, 334 | "values": [ 0 ] 335 | } 336 | 337 | [sub_resource type="Animation" id=10] 338 | length = 0.1 339 | loop = true 340 | tracks/0/type = "value" 341 | tracks/0/path = NodePath("Sprite:frame") 342 | tracks/0/interp = 1 343 | tracks/0/loop_wrap = true 344 | tracks/0/imported = false 345 | tracks/0/enabled = true 346 | tracks/0/keys = { 347 | "times": PoolRealArray( 0 ), 348 | "transitions": PoolRealArray( 1 ), 349 | "update": 1, 350 | "values": [ 6 ] 351 | } 352 | 353 | [sub_resource type="Animation" id=11] 354 | length = 0.5 355 | tracks/0/type = "value" 356 | tracks/0/path = NodePath("Sprite:frame_coords") 357 | tracks/0/interp = 1 358 | tracks/0/loop_wrap = true 359 | tracks/0/imported = false 360 | tracks/0/enabled = true 361 | tracks/0/keys = { 362 | "times": PoolRealArray( ), 363 | "transitions": PoolRealArray( ), 364 | "update": 0, 365 | "values": [ ] 366 | } 367 | tracks/1/type = "value" 368 | tracks/1/path = NodePath("Sprite:frame") 369 | tracks/1/interp = 1 370 | tracks/1/loop_wrap = true 371 | tracks/1/imported = false 372 | tracks/1/enabled = true 373 | tracks/1/keys = { 374 | "times": PoolRealArray( 0, 0.1, 0.2, 0.3, 0.4 ), 375 | "transitions": PoolRealArray( 1, 1, 1, 1, 1 ), 376 | "update": 1, 377 | "values": [ 55, 55, 56, 57, 58 ] 378 | } 379 | tracks/2/type = "method" 380 | tracks/2/path = NodePath(".") 381 | tracks/2/interp = 1 382 | tracks/2/loop_wrap = true 383 | tracks/2/imported = false 384 | tracks/2/enabled = true 385 | tracks/2/keys = { 386 | "times": PoolRealArray( 0.5 ), 387 | "transitions": PoolRealArray( 1 ), 388 | "values": [ { 389 | "args": [ ], 390 | "method": "roll_animation_finished" 391 | } ] 392 | } 393 | tracks/3/type = "audio" 394 | tracks/3/path = NodePath("AudioStreamPlayer") 395 | tracks/3/interp = 1 396 | tracks/3/loop_wrap = true 397 | tracks/3/imported = false 398 | tracks/3/enabled = true 399 | tracks/3/keys = { 400 | "clips": [ { 401 | "end_offset": 0.0, 402 | "start_offset": 0.0, 403 | "stream": ExtResource( 8 ) 404 | } ], 405 | "times": PoolRealArray( 0.1 ) 406 | } 407 | 408 | [sub_resource type="Animation" id=12] 409 | length = 0.5 410 | tracks/0/type = "value" 411 | tracks/0/path = NodePath("Sprite:frame") 412 | tracks/0/interp = 1 413 | tracks/0/loop_wrap = true 414 | tracks/0/imported = false 415 | tracks/0/enabled = true 416 | tracks/0/keys = { 417 | "times": PoolRealArray( 0, 0.1, 0.2, 0.3, 0.4 ), 418 | "transitions": PoolRealArray( 1, 1, 1, 1, 1 ), 419 | "update": 1, 420 | "values": [ 50, 50, 51, 52, 53 ] 421 | } 422 | tracks/1/type = "method" 423 | tracks/1/path = NodePath(".") 424 | tracks/1/interp = 1 425 | tracks/1/loop_wrap = true 426 | tracks/1/imported = false 427 | tracks/1/enabled = true 428 | tracks/1/keys = { 429 | "times": PoolRealArray( 0.5 ), 430 | "transitions": PoolRealArray( 1 ), 431 | "values": [ { 432 | "args": [ ], 433 | "method": "roll_animation_finished" 434 | } ] 435 | } 436 | tracks/2/type = "audio" 437 | tracks/2/path = NodePath("AudioStreamPlayer") 438 | tracks/2/interp = 1 439 | tracks/2/loop_wrap = true 440 | tracks/2/imported = false 441 | tracks/2/enabled = true 442 | tracks/2/keys = { 443 | "clips": [ { 444 | "end_offset": 0.0, 445 | "start_offset": 0.0, 446 | "stream": ExtResource( 8 ) 447 | } ], 448 | "times": PoolRealArray( 0.1 ) 449 | } 450 | 451 | [sub_resource type="Animation" id=13] 452 | length = 0.5 453 | tracks/0/type = "value" 454 | tracks/0/path = NodePath("Sprite:frame") 455 | tracks/0/interp = 1 456 | tracks/0/loop_wrap = true 457 | tracks/0/imported = false 458 | tracks/0/enabled = true 459 | tracks/0/keys = { 460 | "times": PoolRealArray( 0, 0.1, 0.2, 0.3, 0.4 ), 461 | "transitions": PoolRealArray( 1, 1, 1, 1, 1 ), 462 | "update": 1, 463 | "values": [ 40, 41, 42, 43, 44 ] 464 | } 465 | tracks/1/type = "method" 466 | tracks/1/path = NodePath(".") 467 | tracks/1/interp = 1 468 | tracks/1/loop_wrap = true 469 | tracks/1/imported = false 470 | tracks/1/enabled = true 471 | tracks/1/keys = { 472 | "times": PoolRealArray( 0.5 ), 473 | "transitions": PoolRealArray( 1 ), 474 | "values": [ { 475 | "args": [ ], 476 | "method": "roll_animation_finished" 477 | } ] 478 | } 479 | tracks/2/type = "audio" 480 | tracks/2/path = NodePath("AudioStreamPlayer") 481 | tracks/2/interp = 1 482 | tracks/2/loop_wrap = true 483 | tracks/2/imported = false 484 | tracks/2/enabled = true 485 | tracks/2/keys = { 486 | "clips": [ { 487 | "end_offset": 0.0, 488 | "start_offset": 0.0, 489 | "stream": ExtResource( 8 ) 490 | } ], 491 | "times": PoolRealArray( 0.1 ) 492 | } 493 | 494 | [sub_resource type="Animation" id=14] 495 | length = 0.5 496 | tracks/0/type = "value" 497 | tracks/0/path = NodePath("Sprite:frame") 498 | tracks/0/interp = 1 499 | tracks/0/loop_wrap = true 500 | tracks/0/imported = false 501 | tracks/0/enabled = true 502 | tracks/0/keys = { 503 | "times": PoolRealArray( 0, 0.1, 0.2, 0.3, 0.4 ), 504 | "transitions": PoolRealArray( 1, 1, 1, 1, 1 ), 505 | "update": 1, 506 | "values": [ 45, 45, 46, 47, 48 ] 507 | } 508 | tracks/1/type = "method" 509 | tracks/1/path = NodePath(".") 510 | tracks/1/interp = 1 511 | tracks/1/loop_wrap = true 512 | tracks/1/imported = false 513 | tracks/1/enabled = true 514 | tracks/1/keys = { 515 | "times": PoolRealArray( 0.5 ), 516 | "transitions": PoolRealArray( 1 ), 517 | "values": [ { 518 | "args": [ ], 519 | "method": "roll_animation_finished" 520 | } ] 521 | } 522 | tracks/2/type = "audio" 523 | tracks/2/path = NodePath("AudioStreamPlayer") 524 | tracks/2/interp = 1 525 | tracks/2/loop_wrap = true 526 | tracks/2/imported = false 527 | tracks/2/enabled = true 528 | tracks/2/keys = { 529 | "clips": [ { 530 | "end_offset": 0.0, 531 | "start_offset": 0.0, 532 | "stream": ExtResource( 8 ) 533 | } ], 534 | "times": PoolRealArray( 0.1 ) 535 | } 536 | 537 | [sub_resource type="Animation" id=15] 538 | length = 0.6 539 | loop = true 540 | tracks/0/type = "value" 541 | tracks/0/path = NodePath("Sprite:frame") 542 | tracks/0/interp = 1 543 | tracks/0/loop_wrap = true 544 | tracks/0/imported = false 545 | tracks/0/enabled = true 546 | tracks/0/keys = { 547 | "times": PoolRealArray( 0, 0.1, 0.2, 0.3, 0.4, 0.5 ), 548 | "transitions": PoolRealArray( 1, 1, 1, 1, 1, 1 ), 549 | "update": 1, 550 | "values": [ 19, 20, 21, 22, 23, 18 ] 551 | } 552 | 553 | [sub_resource type="Animation" id=16] 554 | length = 0.6 555 | loop = true 556 | tracks/0/type = "value" 557 | tracks/0/path = NodePath("Sprite:frame") 558 | tracks/0/interp = 1 559 | tracks/0/loop_wrap = true 560 | tracks/0/imported = false 561 | tracks/0/enabled = true 562 | tracks/0/keys = { 563 | "times": PoolRealArray( 0, 0.1, 0.2, 0.3, 0.4, 0.5 ), 564 | "transitions": PoolRealArray( 1, 1, 1, 1, 1, 1 ), 565 | "update": 1, 566 | "values": [ 13, 14, 15, 16, 17, 12 ] 567 | } 568 | 569 | [sub_resource type="Animation" id=17] 570 | length = 0.6 571 | loop = true 572 | tracks/0/type = "value" 573 | tracks/0/path = NodePath("Sprite:frame") 574 | tracks/0/interp = 1 575 | tracks/0/loop_wrap = true 576 | tracks/0/imported = false 577 | tracks/0/enabled = true 578 | tracks/0/keys = { 579 | "times": PoolRealArray( 0, 0.1, 0.2, 0.3, 0.4, 0.5 ), 580 | "transitions": PoolRealArray( 1, 1, 1, 1, 1, 1 ), 581 | "update": 1, 582 | "values": [ 1, 2, 3, 4, 5, 0 ] 583 | } 584 | 585 | [sub_resource type="Animation" id=18] 586 | length = 0.6 587 | loop = true 588 | tracks/0/type = "value" 589 | tracks/0/path = NodePath("Sprite:frame") 590 | tracks/0/interp = 1 591 | tracks/0/loop_wrap = true 592 | tracks/0/imported = false 593 | tracks/0/enabled = true 594 | tracks/0/keys = { 595 | "times": PoolRealArray( 0, 0.1, 0.2, 0.3, 0.4, 0.5 ), 596 | "transitions": PoolRealArray( 1, 1, 1, 1, 1, 1 ), 597 | "update": 1, 598 | "values": [ 7, 8, 9, 10, 11, 6 ] 599 | } 600 | 601 | [sub_resource type="AnimationNodeAnimation" id=19] 602 | animation = "AttackLeft" 603 | 604 | [sub_resource type="AnimationNodeAnimation" id=20] 605 | animation = "AttackDown" 606 | 607 | [sub_resource type="AnimationNodeAnimation" id=21] 608 | animation = "AttackRight" 609 | 610 | [sub_resource type="AnimationNodeAnimation" id=22] 611 | animation = "AttackUp" 612 | 613 | [sub_resource type="AnimationNodeBlendSpace2D" id=23] 614 | blend_point_0/node = SubResource( 19 ) 615 | blend_point_0/pos = Vector2( -1, 0 ) 616 | blend_point_1/node = SubResource( 20 ) 617 | blend_point_1/pos = Vector2( 0, 1 ) 618 | blend_point_2/node = SubResource( 21 ) 619 | blend_point_2/pos = Vector2( 1, 0 ) 620 | blend_point_3/node = SubResource( 22 ) 621 | blend_point_3/pos = Vector2( 0, -1 ) 622 | blend_mode = 1 623 | 624 | [sub_resource type="AnimationNodeAnimation" id=24] 625 | animation = "IdleLeft" 626 | 627 | [sub_resource type="AnimationNodeAnimation" id=25] 628 | animation = "IdleDown" 629 | 630 | [sub_resource type="AnimationNodeAnimation" id=26] 631 | animation = "IdleRight" 632 | 633 | [sub_resource type="AnimationNodeAnimation" id=27] 634 | animation = "IdleUp" 635 | 636 | [sub_resource type="AnimationNodeBlendSpace2D" id=28] 637 | blend_point_0/node = SubResource( 24 ) 638 | blend_point_0/pos = Vector2( -1, 0 ) 639 | blend_point_1/node = SubResource( 25 ) 640 | blend_point_1/pos = Vector2( 0, 1.1 ) 641 | blend_point_2/node = SubResource( 26 ) 642 | blend_point_2/pos = Vector2( 1, 0 ) 643 | blend_point_3/node = SubResource( 27 ) 644 | blend_point_3/pos = Vector2( 0, -1.1 ) 645 | min_space = Vector2( -1, -1.1 ) 646 | max_space = Vector2( 1, 1.1 ) 647 | blend_mode = 1 648 | 649 | [sub_resource type="AnimationNodeAnimation" id=29] 650 | animation = "RollLeft" 651 | 652 | [sub_resource type="AnimationNodeAnimation" id=30] 653 | animation = "RollDown" 654 | 655 | [sub_resource type="AnimationNodeAnimation" id=31] 656 | animation = "RollRight" 657 | 658 | [sub_resource type="AnimationNodeAnimation" id=32] 659 | animation = "RollUp" 660 | 661 | [sub_resource type="AnimationNodeBlendSpace2D" id=33] 662 | blend_point_0/node = SubResource( 29 ) 663 | blend_point_0/pos = Vector2( -1, 0 ) 664 | blend_point_1/node = SubResource( 30 ) 665 | blend_point_1/pos = Vector2( 0, 1.1 ) 666 | blend_point_2/node = SubResource( 31 ) 667 | blend_point_2/pos = Vector2( 1, 0 ) 668 | blend_point_3/node = SubResource( 32 ) 669 | blend_point_3/pos = Vector2( 0, -1.1 ) 670 | min_space = Vector2( -1, -1.1 ) 671 | max_space = Vector2( 1, 1.1 ) 672 | blend_mode = 1 673 | 674 | [sub_resource type="AnimationNodeAnimation" id=34] 675 | animation = "RunLeft" 676 | 677 | [sub_resource type="AnimationNodeAnimation" id=35] 678 | animation = "RunDown" 679 | 680 | [sub_resource type="AnimationNodeAnimation" id=36] 681 | animation = "RunRight" 682 | 683 | [sub_resource type="AnimationNodeAnimation" id=37] 684 | animation = "RunUp" 685 | 686 | [sub_resource type="AnimationNodeBlendSpace2D" id=38] 687 | blend_point_0/node = SubResource( 34 ) 688 | blend_point_0/pos = Vector2( -1, 0 ) 689 | blend_point_1/node = SubResource( 35 ) 690 | blend_point_1/pos = Vector2( 0, 1.1 ) 691 | blend_point_2/node = SubResource( 36 ) 692 | blend_point_2/pos = Vector2( 1, 0 ) 693 | blend_point_3/node = SubResource( 37 ) 694 | blend_point_3/pos = Vector2( 0, -1.1 ) 695 | min_space = Vector2( -1, -1.1 ) 696 | max_space = Vector2( 1, 1.1 ) 697 | blend_mode = 1 698 | 699 | [sub_resource type="AnimationNodeStateMachineTransition" id=39] 700 | 701 | [sub_resource type="AnimationNodeStateMachineTransition" id=40] 702 | 703 | [sub_resource type="AnimationNodeStateMachineTransition" id=41] 704 | 705 | [sub_resource type="AnimationNodeStateMachineTransition" id=42] 706 | 707 | [sub_resource type="AnimationNodeStateMachineTransition" id=43] 708 | 709 | [sub_resource type="AnimationNodeStateMachineTransition" id=44] 710 | 711 | [sub_resource type="AnimationNodeStateMachine" id=45] 712 | states/Attack/node = SubResource( 23 ) 713 | states/Attack/position = Vector2( 306.158, 285.205 ) 714 | states/Idle/node = SubResource( 28 ) 715 | states/Idle/position = Vector2( 306.2, 169.4 ) 716 | states/Roll/node = SubResource( 33 ) 717 | states/Roll/position = Vector2( 305.908, 60.6566 ) 718 | states/Run/node = SubResource( 38 ) 719 | states/Run/position = Vector2( 477.4, 169.4 ) 720 | transitions = [ "Idle", "Run", SubResource( 39 ), "Run", "Idle", SubResource( 40 ), "Idle", "Attack", SubResource( 41 ), "Attack", "Idle", SubResource( 42 ), "Idle", "Roll", SubResource( 43 ), "Roll", "Idle", SubResource( 44 ) ] 721 | start_node = "Idle" 722 | graph_offset = Vector2( 0, 113.162 ) 723 | 724 | [sub_resource type="AnimationNodeStateMachinePlayback" id=46] 725 | 726 | [sub_resource type="CapsuleShape2D" id=47] 727 | height = 12.0 728 | 729 | [sub_resource type="CapsuleShape2D" id=48] 730 | radius = 5.0 731 | height = 6.0 732 | 733 | [sub_resource type="Animation" id=49] 734 | resource_name = "Start" 735 | length = 0.2 736 | loop = true 737 | tracks/0/type = "value" 738 | tracks/0/path = NodePath("Sprite:material:shader_param/active") 739 | tracks/0/interp = 1 740 | tracks/0/loop_wrap = true 741 | tracks/0/imported = false 742 | tracks/0/enabled = true 743 | tracks/0/keys = { 744 | "times": PoolRealArray( 0, 0.1 ), 745 | "transitions": PoolRealArray( 1, 1 ), 746 | "update": 1, 747 | "values": [ true, false ] 748 | } 749 | 750 | [sub_resource type="Animation" id=50] 751 | resource_name = "Stop" 752 | length = 0.1 753 | tracks/0/type = "value" 754 | tracks/0/path = NodePath("Sprite:material:shader_param/active") 755 | tracks/0/interp = 1 756 | tracks/0/loop_wrap = true 757 | tracks/0/imported = false 758 | tracks/0/enabled = true 759 | tracks/0/keys = { 760 | "times": PoolRealArray( 0 ), 761 | "transitions": PoolRealArray( 1 ), 762 | "update": 1, 763 | "values": [ false ] 764 | } 765 | 766 | [node name="Player" type="KinematicBody2D"] 767 | collision_layer = 2 768 | script = ExtResource( 2 ) 769 | 770 | [node name="ShadowSprite" type="Sprite" parent="."] 771 | position = Vector2( 0, 2 ) 772 | texture = ExtResource( 6 ) 773 | 774 | [node name="Sprite" type="Sprite" parent="."] 775 | material = SubResource( 1 ) 776 | position = Vector2( 0, -9 ) 777 | texture = ExtResource( 1 ) 778 | hframes = 60 779 | frame = 18 780 | 781 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 782 | visible = false 783 | position = Vector2( 0, 7.62939e-06 ) 784 | rotation = -1.5708 785 | shape = SubResource( 2 ) 786 | 787 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 788 | anims/AttackDown = SubResource( 3 ) 789 | anims/AttackLeft = SubResource( 4 ) 790 | anims/AttackRight = SubResource( 5 ) 791 | anims/AttackUp = SubResource( 6 ) 792 | anims/IdleDown = SubResource( 7 ) 793 | anims/IdleLeft = SubResource( 8 ) 794 | anims/IdleRight = SubResource( 9 ) 795 | anims/IdleUp = SubResource( 10 ) 796 | anims/RollDown = SubResource( 11 ) 797 | anims/RollLeft = SubResource( 12 ) 798 | anims/RollRight = SubResource( 13 ) 799 | anims/RollUp = SubResource( 14 ) 800 | anims/RunDown = SubResource( 15 ) 801 | anims/RunLeft = SubResource( 16 ) 802 | anims/RunRight = SubResource( 17 ) 803 | anims/RunUp = SubResource( 18 ) 804 | 805 | [node name="AnimationTree" type="AnimationTree" parent="."] 806 | tree_root = SubResource( 45 ) 807 | anim_player = NodePath("../AnimationPlayer") 808 | parameters/playback = SubResource( 46 ) 809 | parameters/Attack/blend_position = Vector2( 0, 1 ) 810 | parameters/Idle/blend_position = Vector2( 0, 1 ) 811 | parameters/Roll/blend_position = Vector2( 0, 1 ) 812 | parameters/Run/blend_position = Vector2( 0, 1 ) 813 | 814 | [node name="HitboxPivot" type="Position2D" parent="."] 815 | position = Vector2( 0, -4 ) 816 | rotation = 1.5708 817 | 818 | [node name="SwordHitbox" parent="HitboxPivot" instance=ExtResource( 3 )] 819 | position = Vector2( 15, 0 ) 820 | collision_mask = 8 821 | script = ExtResource( 4 ) 822 | 823 | [node name="CollisionShape2D" parent="HitboxPivot/SwordHitbox" index="0"] 824 | visible = false 825 | shape = SubResource( 47 ) 826 | disabled = true 827 | 828 | [node name="HurtBox" parent="." instance=ExtResource( 5 )] 829 | collision_layer = 4 830 | 831 | [node name="CollisionShape2D" parent="HurtBox" index="0"] 832 | visible = false 833 | position = Vector2( 0, -4 ) 834 | shape = SubResource( 48 ) 835 | 836 | [node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."] 837 | stream = ExtResource( 8 ) 838 | 839 | [node name="BlinkAnimationPlayer" type="AnimationPlayer" parent="."] 840 | anims/Start = SubResource( 49 ) 841 | anims/Stop = SubResource( 50 ) 842 | 843 | [connection signal="area_entered" from="HurtBox" to="." method="_on_HurtBox_area_entered"] 844 | [connection signal="invincibility_ended" from="HurtBox" to="." method="_on_HurtBox_invincibility_ended" flags=3] 845 | [connection signal="invincibility_started" from="HurtBox" to="." method="_on_HurtBox_invincibility_started" flags=3] 846 | 847 | [editable path="HitboxPivot/SwordHitbox"] 848 | [editable path="HurtBox"] 849 | -------------------------------------------------------------------------------- /action-rpg/Player/PlayerHurtSound.gdns: -------------------------------------------------------------------------------- 1 | [gd_resource type="NativeScript" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://action_rpg_tutorial_library.gdnlib" type="GDNativeLibrary" id=1] 4 | 5 | [resource] 6 | resource_name = "PlayerHurtSound" 7 | class_name = "PlayerHurtSound" 8 | library = ExtResource( 1 ) 9 | -------------------------------------------------------------------------------- /action-rpg/Player/PlayerHurtSound.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://Music and Sounds/Hurt.wav" type="AudioStream" id=1] 4 | [ext_resource path="res://Player/PlayerHurtSound.gdns" type="Script" id=2] 5 | 6 | [node name="PlayerHurtSound" type="AudioStreamPlayer"] 7 | stream = ExtResource( 1 ) 8 | autoplay = true 9 | script = ExtResource( 2 ) 10 | 11 | [connection signal="finished" from="." to="." method="_on_PlayerHurtSound_finished"] 12 | -------------------------------------------------------------------------------- /action-rpg/Player/PlayerStats.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://Stats/Stats.tscn" type="PackedScene" id=1] 4 | 5 | [node name="PlayerStats" instance=ExtResource( 1 )] 6 | max_health = 5 7 | -------------------------------------------------------------------------------- /action-rpg/Player/SwordHitBox.gdns: -------------------------------------------------------------------------------- 1 | [gd_resource type="NativeScript" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://action_rpg_tutorial_library.gdnlib" type="GDNativeLibrary" id=1] 4 | 5 | [resource] 6 | resource_name = "Sword" 7 | class_name = "Sword" 8 | library = ExtResource( 1 ) 9 | -------------------------------------------------------------------------------- /action-rpg/PlayerCamera.gdns: -------------------------------------------------------------------------------- 1 | [gd_resource type="NativeScript" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://action_rpg_tutorial_library.gdnlib" type="GDNativeLibrary" id=1] 4 | 5 | [resource] 6 | resource_name = "PlayerCamera" 7 | class_name = "PlayerCamera" 8 | library = ExtResource( 1 ) 9 | -------------------------------------------------------------------------------- /action-rpg/PlayerCamera.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://PlayerCamera.gdns" type="Script" id=1] 4 | 5 | [node name="PlayerCamera" type="Camera2D"] 6 | position = Vector2( 160, 90 ) 7 | current = true 8 | process_mode = 0 9 | smoothing_enabled = true 10 | script = ExtResource( 1 ) 11 | 12 | [node name="Limits" type="Node" parent="."] 13 | 14 | [node name="TopLeft" type="Position2D" parent="Limits"] 15 | 16 | [node name="BottomRight" type="Position2D" parent="Limits"] 17 | position = Vector2( 320, 180 ) 18 | -------------------------------------------------------------------------------- /action-rpg/Shaders/WhiteColorShader.gdshader: -------------------------------------------------------------------------------- 1 | shader_type canvas_item; 2 | 3 | uniform bool active = false; 4 | 5 | void fragment() { 6 | COLOR = active ? vec4(1.0, 1.0, 1.0, texture(TEXTURE, UV).a) : texture(TEXTURE, UV); 7 | } -------------------------------------------------------------------------------- /action-rpg/Shadows/LargeShadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/Shadows/LargeShadow.png -------------------------------------------------------------------------------- /action-rpg/Shadows/LargeShadow.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/LargeShadow.png-f9f465d3bd6a830d0053dcccc744b5ab.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://Shadows/LargeShadow.png" 13 | dest_files=[ "res://.import/LargeShadow.png-f9f465d3bd6a830d0053dcccc744b5ab.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /action-rpg/Shadows/MediumShadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/Shadows/MediumShadow.png -------------------------------------------------------------------------------- /action-rpg/Shadows/MediumShadow.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/MediumShadow.png-a006169ce9c485f0f94bc7dc3bc8473f.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://Shadows/MediumShadow.png" 13 | dest_files=[ "res://.import/MediumShadow.png-a006169ce9c485f0f94bc7dc3bc8473f.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /action-rpg/Shadows/SmallShadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/Shadows/SmallShadow.png -------------------------------------------------------------------------------- /action-rpg/Shadows/SmallShadow.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/SmallShadow.png-5e32714e38d94da2c1b597c947bda29e.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://Shadows/SmallShadow.png" 13 | dest_files=[ "res://.import/SmallShadow.png-5e32714e38d94da2c1b597c947bda29e.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /action-rpg/Stats/Stats.gdns: -------------------------------------------------------------------------------- 1 | [gd_resource type="NativeScript" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://action_rpg_tutorial_library.gdnlib" type="GDNativeLibrary" id=1] 4 | 5 | [resource] 6 | resource_name = "Stats" 7 | class_name = "Stats" 8 | library = ExtResource( 1 ) 9 | -------------------------------------------------------------------------------- /action-rpg/Stats/Stats.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://Stats/Stats.gdns" type="Script" id=1] 4 | 5 | [node name="Stats" type="Node"] 6 | script = ExtResource( 1 ) 7 | -------------------------------------------------------------------------------- /action-rpg/UI/HealthUI.gdns: -------------------------------------------------------------------------------- 1 | [gd_resource type="NativeScript" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://action_rpg_tutorial_library.gdnlib" type="GDNativeLibrary" id=1] 4 | 5 | [resource] 6 | resource_name = "HealthUI" 7 | class_name = "HealthUI" 8 | library = ExtResource( 1 ) 9 | -------------------------------------------------------------------------------- /action-rpg/UI/HealthUI.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://UI/HealthUI.gdns" type="Script" id=1] 4 | [ext_resource path="res://UI/HeartUIEmpty.png" type="Texture" id=2] 5 | [ext_resource path="res://UI/HeartUIFull.png" type="Texture" id=3] 6 | 7 | [node name="HealthUI" type="Control"] 8 | margin_right = 60.0 9 | margin_bottom = 11.0 10 | script = ExtResource( 1 ) 11 | __meta__ = { 12 | "_edit_use_anchors_": false 13 | } 14 | 15 | [node name="HeartUIEmpty" type="TextureRect" parent="."] 16 | margin_right = 60.0 17 | margin_bottom = 11.0 18 | texture = ExtResource( 2 ) 19 | stretch_mode = 2 20 | __meta__ = { 21 | "_edit_use_anchors_": false 22 | } 23 | 24 | [node name="HeartUIFull" type="TextureRect" parent="."] 25 | margin_right = 60.0 26 | margin_bottom = 11.0 27 | texture = ExtResource( 3 ) 28 | expand = true 29 | stretch_mode = 2 30 | -------------------------------------------------------------------------------- /action-rpg/UI/HeartUIEmpty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/UI/HeartUIEmpty.png -------------------------------------------------------------------------------- /action-rpg/UI/HeartUIEmpty.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/HeartUIEmpty.png-417b0a6fe975644d9dba3671413f10e5.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://UI/HeartUIEmpty.png" 13 | dest_files=[ "res://.import/HeartUIEmpty.png-417b0a6fe975644d9dba3671413f10e5.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /action-rpg/UI/HeartUIFull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/UI/HeartUIFull.png -------------------------------------------------------------------------------- /action-rpg/UI/HeartUIFull.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/HeartUIFull.png-e9d9ebbeee52aaa00895457d920c0f53.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://UI/HeartUIFull.png" 13 | dest_files=[ "res://.import/HeartUIFull.png-e9d9ebbeee52aaa00895457d920c0f53.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /action-rpg/World/Bush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/World/Bush.png -------------------------------------------------------------------------------- /action-rpg/World/Bush.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/Bush.png-d93cca107fd3c67cf3a69a32232496ae.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://World/Bush.png" 13 | dest_files=[ "res://.import/Bush.png-d93cca107fd3c67cf3a69a32232496ae.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /action-rpg/World/Bush.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://World/Bush.png" type="Texture" id=1] 4 | [ext_resource path="res://Shadows/LargeShadow.png" type="Texture" id=2] 5 | 6 | [sub_resource type="CapsuleShape2D" id=1] 7 | radius = 8.0 8 | height = 12.0 9 | 10 | [node name="Bush" type="StaticBody2D"] 11 | 12 | [node name="ShadowSprite" type="Sprite" parent="."] 13 | position = Vector2( 0, 2 ) 14 | texture = ExtResource( 2 ) 15 | 16 | [node name="Sprite" type="Sprite" parent="."] 17 | position = Vector2( 0, -4 ) 18 | texture = ExtResource( 1 ) 19 | 20 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 21 | visible = false 22 | rotation = -1.5708 23 | shape = SubResource( 1 ) 24 | -------------------------------------------------------------------------------- /action-rpg/World/CliffTileset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/World/CliffTileset.png -------------------------------------------------------------------------------- /action-rpg/World/CliffTileset.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/CliffTileset.png-beb1369a7815e4e7ee0afe974b2636cf.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://World/CliffTileset.png" 13 | dest_files=[ "res://.import/CliffTileset.png-beb1369a7815e4e7ee0afe974b2636cf.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /action-rpg/World/DirtTileset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/World/DirtTileset.png -------------------------------------------------------------------------------- /action-rpg/World/DirtTileset.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/DirtTileset.png-4e52e2a032d2a3147ded644a2054b3ec.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://World/DirtTileset.png" 13 | dest_files=[ "res://.import/DirtTileset.png-4e52e2a032d2a3147ded644a2054b3ec.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /action-rpg/World/Grass.gdns: -------------------------------------------------------------------------------- 1 | [gd_resource type="NativeScript" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://action_rpg_tutorial_library.gdnlib" type="GDNativeLibrary" id=1] 4 | 5 | [resource] 6 | resource_name = "Grass" 7 | class_name = "Grass" 8 | library = ExtResource( 1 ) 9 | -------------------------------------------------------------------------------- /action-rpg/World/Grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/World/Grass.png -------------------------------------------------------------------------------- /action-rpg/World/Grass.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/Grass.png-c8db04a845a94a62395b8fc264a498a7.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://World/Grass.png" 13 | dest_files=[ "res://.import/Grass.png-c8db04a845a94a62395b8fc264a498a7.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /action-rpg/World/Grass.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://World/Grass.png" type="Texture" id=1] 4 | [ext_resource path="res://World/Grass.gdns" type="Script" id=2] 5 | [ext_resource path="res://Colliders/HurtBox.tscn" type="PackedScene" id=3] 6 | 7 | [sub_resource type="RectangleShape2D" id=1] 8 | extents = Vector2( 7, 7 ) 9 | 10 | [node name="Grass" type="Node2D"] 11 | script = ExtResource( 2 ) 12 | 13 | [node name="Sprite" type="Sprite" parent="."] 14 | texture = ExtResource( 1 ) 15 | centered = false 16 | offset = Vector2( -8, -8 ) 17 | 18 | [node name="HurtBox" parent="." instance=ExtResource( 3 )] 19 | collision_layer = 8 20 | 21 | [node name="CollisionShape2D" parent="HurtBox" index="0"] 22 | visible = false 23 | position = Vector2( 8, 8 ) 24 | shape = SubResource( 1 ) 25 | 26 | [connection signal="area_entered" from="HurtBox" to="." method="_on_HurtBox_area_entered"] 27 | 28 | [editable path="HurtBox"] 29 | -------------------------------------------------------------------------------- /action-rpg/World/GrassBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/World/GrassBackground.png -------------------------------------------------------------------------------- /action-rpg/World/GrassBackground.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/GrassBackground.png-97f32375e80ccfc9d4addd18e4430882.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://World/GrassBackground.png" 13 | dest_files=[ "res://.import/GrassBackground.png-97f32375e80ccfc9d4addd18e4430882.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=1 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /action-rpg/World/Tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/World/Tree.png -------------------------------------------------------------------------------- /action-rpg/World/Tree.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/Tree.png-14ac646b55f931abf82a3d71f0acdb7a.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://World/Tree.png" 13 | dest_files=[ "res://.import/Tree.png-14ac646b55f931abf82a3d71f0acdb7a.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /action-rpg/World/Tree.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://World/Tree.png" type="Texture" id=1] 4 | [ext_resource path="res://Shadows/LargeShadow.png" type="Texture" id=2] 5 | 6 | [sub_resource type="CapsuleShape2D" id=1] 7 | height = 6.0 8 | 9 | [node name="Tree" type="StaticBody2D"] 10 | 11 | [node name="ShadowSprite" type="Sprite" parent="."] 12 | texture = ExtResource( 2 ) 13 | 14 | [node name="Sprite" type="Sprite" parent="."] 15 | position = Vector2( 0, -16 ) 16 | texture = ExtResource( 1 ) 17 | 18 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 19 | visible = false 20 | position = Vector2( 0, -2 ) 21 | rotation = 1.5708 22 | shape = SubResource( 1 ) 23 | -------------------------------------------------------------------------------- /action-rpg/action_rpg_tutorial_library.gdnlib: -------------------------------------------------------------------------------- 1 | [general] 2 | 3 | singleton=false 4 | load_once=true 5 | symbol_prefix="godot_" 6 | reloadable=true 7 | 8 | [entry] 9 | 10 | Windows.64="res://action-rpg.dll" 11 | X11.64="res://libaction_rpg_rs.so" 12 | OSX.64="res://libaction_rpg_rs.dynlib" 13 | 14 | [dependencies] 15 | 16 | Windows.64=[ ] 17 | X11.64=[ ] 18 | OSX.64=[ ] -------------------------------------------------------------------------------- /action-rpg/default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | [resource] 6 | background_mode = 2 7 | background_sky = SubResource( 1 ) 8 | -------------------------------------------------------------------------------- /action-rpg/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nejat/godot-action-rpg-tutorial-rs/171a8f4b190749092f4ce66cae44b71904299091/action-rpg/icon.png -------------------------------------------------------------------------------- /action-rpg/icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://icon.png" 13 | dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=false 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /action-rpg/project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=4 10 | 11 | [application] 12 | 13 | config/name="action-rpg-tutorial-rs" 14 | run/main_scene="res://world.tscn" 15 | config/icon="res://icon.png" 16 | 17 | [autoload] 18 | 19 | PlayerStats="*res://Player/PlayerStats.tscn" 20 | 21 | [display] 22 | 23 | window/size/width=320 24 | window/size/height=180 25 | window/size/resizable=false 26 | window/size/test_width=1280 27 | window/size/test_height=720 28 | window/stretch/mode="2d" 29 | window/stretch/aspect="keep" 30 | 31 | [importer_defaults] 32 | 33 | texture={ 34 | "compress/bptc_ldr": 0, 35 | "compress/hdr_mode": 0, 36 | "compress/lossy_quality": 0.7, 37 | "compress/mode": 0, 38 | "compress/normal_map": 0, 39 | "detect_3d": false, 40 | "flags/anisotropic": false, 41 | "flags/filter": false, 42 | "flags/mipmaps": false, 43 | "flags/repeat": 0, 44 | "flags/srgb": 2, 45 | "process/HDR_as_SRGB": false, 46 | "process/fix_alpha_border": true, 47 | "process/invert_color": false, 48 | "process/premult_alpha": false, 49 | "size_limit": 0, 50 | "stream": false, 51 | "svg/scale": 1.0 52 | } 53 | 54 | [input] 55 | 56 | ui_left={ 57 | "deadzone": 0.5, 58 | "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777231,"physical_scancode":0,"unicode":0,"echo":false,"script":null) 59 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":14,"pressure":0.0,"pressed":false,"script":null) 60 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":65,"physical_scancode":0,"unicode":0,"echo":false,"script":null) 61 | ] 62 | } 63 | ui_right={ 64 | "deadzone": 0.5, 65 | "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777233,"physical_scancode":0,"unicode":0,"echo":false,"script":null) 66 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":15,"pressure":0.0,"pressed":false,"script":null) 67 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":68,"physical_scancode":0,"unicode":0,"echo":false,"script":null) 68 | ] 69 | } 70 | ui_up={ 71 | "deadzone": 0.5, 72 | "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777232,"physical_scancode":0,"unicode":0,"echo":false,"script":null) 73 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null) 74 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":87,"physical_scancode":0,"unicode":0,"echo":false,"script":null) 75 | ] 76 | } 77 | ui_down={ 78 | "deadzone": 0.5, 79 | "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777234,"physical_scancode":0,"unicode":0,"echo":false,"script":null) 80 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null) 81 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":83,"physical_scancode":0,"unicode":0,"echo":false,"script":null) 82 | ] 83 | } 84 | ui_roll={ 85 | "deadzone": 0.5, 86 | "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":75,"physical_scancode":0,"unicode":0,"echo":false,"script":null) 87 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":0,"pressure":0.0,"pressed":false,"script":null) 88 | , Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":2,"pressed":false,"doubleclick":false,"script":null) 89 | , Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":4,"pressed":false,"doubleclick":false,"script":null) 90 | , Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":5,"pressed":false,"doubleclick":false,"script":null) 91 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":90,"physical_scancode":0,"unicode":0,"echo":false,"script":null) 92 | ] 93 | } 94 | ui_attack={ 95 | "deadzone": 0.5, 96 | "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":74,"physical_scancode":0,"unicode":0,"echo":false,"script":null) 97 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":2,"pressure":0.0,"pressed":false,"script":null) 98 | , Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":1,"pressed":false,"doubleclick":false,"script":null) 99 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":88,"physical_scancode":0,"unicode":0,"echo":false,"script":null) 100 | ] 101 | } 102 | 103 | [layer_names] 104 | 105 | 2d_physics/layer_1="World" 106 | 2d_physics/layer_2="Player" 107 | 2d_physics/layer_3="PlayerHurbox" 108 | 2d_physics/layer_4="EnemyHurtbox" 109 | 2d_physics/layer_5="Enemy" 110 | 2d_physics/layer_6="SoftCollisions" 111 | 112 | [physics] 113 | 114 | common/enable_pause_aware_picking=true 115 | 116 | [rendering] 117 | 118 | 2d/snapping/use_gpu_pixel_snap=true 119 | environment/default_environment="res://default_env.tres" 120 | -------------------------------------------------------------------------------- /action-rpg/world.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=60 format=2] 2 | 3 | [ext_resource path="res://World/Bush.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://Player/Player.tscn" type="PackedScene" id=2] 5 | [ext_resource path="res://World/GrassBackground.png" type="Texture" id=3] 6 | [ext_resource path="res://World/DirtTileset.png" type="Texture" id=4] 7 | [ext_resource path="res://World/CliffTileset.png" type="Texture" id=5] 8 | [ext_resource path="res://World/Grass.tscn" type="PackedScene" id=6] 9 | [ext_resource path="res://Enemies/Bat.tscn" type="PackedScene" id=7] 10 | [ext_resource path="res://UI/HealthUI.tscn" type="PackedScene" id=8] 11 | [ext_resource path="res://World/Tree.tscn" type="PackedScene" id=9] 12 | [ext_resource path="res://PlayerCamera.tscn" type="PackedScene" id=10] 13 | 14 | [sub_resource type="TileSet" id=1] 15 | 0/name = "DirtTileset.png 0" 16 | 0/texture = ExtResource( 4 ) 17 | 0/tex_offset = Vector2( 0, 0 ) 18 | 0/modulate = Color( 1, 1, 1, 1 ) 19 | 0/region = Rect2( 0, 0, 176, 80 ) 20 | 0/tile_mode = 1 21 | 0/autotile/bitmask_mode = 1 22 | 0/autotile/bitmask_flags = [ Vector2( 0, 0 ), 432, Vector2( 0, 1 ), 438, Vector2( 0, 2 ), 54, Vector2( 0, 3 ), 48, Vector2( 1, 0 ), 504, Vector2( 1, 1 ), 511, Vector2( 1, 2 ), 63, Vector2( 1, 3 ), 56, Vector2( 2, 0 ), 216, Vector2( 2, 1 ), 219, Vector2( 2, 2 ), 27, Vector2( 2, 3 ), 24, Vector2( 3, 0 ), 144, Vector2( 3, 1 ), 146, Vector2( 3, 2 ), 18, Vector2( 3, 3 ), 16, Vector2( 4, 0 ), 176, Vector2( 4, 1 ), 182, Vector2( 4, 2 ), 434, Vector2( 4, 3 ), 50, Vector2( 4, 4 ), 178, Vector2( 5, 0 ), 248, Vector2( 5, 1 ), 255, Vector2( 5, 2 ), 507, Vector2( 5, 3 ), 59, Vector2( 5, 4 ), 251, Vector2( 6, 0 ), 440, Vector2( 6, 1 ), 447, Vector2( 6, 2 ), 510, Vector2( 6, 3 ), 62, Vector2( 6, 4 ), 446, Vector2( 7, 0 ), 152, Vector2( 7, 1 ), 155, Vector2( 7, 2 ), 218, Vector2( 7, 3 ), 26, Vector2( 7, 4 ), 154, Vector2( 8, 0 ), 184, Vector2( 8, 1 ), 191, Vector2( 8, 2 ), 506, Vector2( 8, 3 ), 58, Vector2( 8, 4 ), 186, Vector2( 9, 0 ), 443, Vector2( 9, 1 ), 254, Vector2( 9, 2 ), 442, Vector2( 9, 3 ), 190, Vector2( 10, 2 ), 250, Vector2( 10, 3 ), 187 ] 23 | 0/autotile/icon_coordinate = Vector2( 3, 3 ) 24 | 0/autotile/tile_size = Vector2( 16, 16 ) 25 | 0/autotile/spacing = 0 26 | 0/autotile/occluder_map = [ ] 27 | 0/autotile/navpoly_map = [ ] 28 | 0/autotile/priority_map = [ ] 29 | 0/autotile/z_index_map = [ ] 30 | 0/occluder_offset = Vector2( 0, 0 ) 31 | 0/navigation_offset = Vector2( 0, 0 ) 32 | 0/shape_offset = Vector2( 0, 0 ) 33 | 0/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 ) 34 | 0/shape_one_way = false 35 | 0/shape_one_way_margin = 0.0 36 | 0/shapes = [ ] 37 | 0/z_index = 0 38 | 39 | [sub_resource type="ConvexPolygonShape2D" id=2] 40 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 41 | 42 | [sub_resource type="ConvexPolygonShape2D" id=3] 43 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 44 | 45 | [sub_resource type="ConvexPolygonShape2D" id=4] 46 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 47 | 48 | [sub_resource type="ConvexPolygonShape2D" id=5] 49 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 50 | 51 | [sub_resource type="ConvexPolygonShape2D" id=6] 52 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 53 | 54 | [sub_resource type="ConvexPolygonShape2D" id=7] 55 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 56 | 57 | [sub_resource type="ConvexPolygonShape2D" id=8] 58 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 59 | 60 | [sub_resource type="ConvexPolygonShape2D" id=9] 61 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 62 | 63 | [sub_resource type="ConvexPolygonShape2D" id=10] 64 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 65 | 66 | [sub_resource type="ConvexPolygonShape2D" id=11] 67 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 68 | 69 | [sub_resource type="ConvexPolygonShape2D" id=12] 70 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 71 | 72 | [sub_resource type="ConvexPolygonShape2D" id=13] 73 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 74 | 75 | [sub_resource type="ConvexPolygonShape2D" id=14] 76 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 77 | 78 | [sub_resource type="ConvexPolygonShape2D" id=15] 79 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 80 | 81 | [sub_resource type="ConvexPolygonShape2D" id=16] 82 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 83 | 84 | [sub_resource type="ConvexPolygonShape2D" id=17] 85 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 86 | 87 | [sub_resource type="ConvexPolygonShape2D" id=18] 88 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 89 | 90 | [sub_resource type="ConvexPolygonShape2D" id=19] 91 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 92 | 93 | [sub_resource type="ConvexPolygonShape2D" id=20] 94 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 95 | 96 | [sub_resource type="ConvexPolygonShape2D" id=21] 97 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 98 | 99 | [sub_resource type="ConvexPolygonShape2D" id=22] 100 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 101 | 102 | [sub_resource type="ConvexPolygonShape2D" id=23] 103 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 104 | 105 | [sub_resource type="ConvexPolygonShape2D" id=24] 106 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 107 | 108 | [sub_resource type="ConvexPolygonShape2D" id=25] 109 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 110 | 111 | [sub_resource type="ConvexPolygonShape2D" id=26] 112 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 113 | 114 | [sub_resource type="ConvexPolygonShape2D" id=27] 115 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 116 | 117 | [sub_resource type="ConvexPolygonShape2D" id=28] 118 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 119 | 120 | [sub_resource type="ConvexPolygonShape2D" id=29] 121 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 122 | 123 | [sub_resource type="ConvexPolygonShape2D" id=30] 124 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 125 | 126 | [sub_resource type="ConvexPolygonShape2D" id=31] 127 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 128 | 129 | [sub_resource type="ConvexPolygonShape2D" id=32] 130 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 131 | 132 | [sub_resource type="ConvexPolygonShape2D" id=33] 133 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 134 | 135 | [sub_resource type="ConvexPolygonShape2D" id=34] 136 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 137 | 138 | [sub_resource type="ConvexPolygonShape2D" id=35] 139 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 140 | 141 | [sub_resource type="ConvexPolygonShape2D" id=36] 142 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 143 | 144 | [sub_resource type="ConvexPolygonShape2D" id=37] 145 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 146 | 147 | [sub_resource type="ConvexPolygonShape2D" id=38] 148 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 149 | 150 | [sub_resource type="ConvexPolygonShape2D" id=39] 151 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 152 | 153 | [sub_resource type="ConvexPolygonShape2D" id=40] 154 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 155 | 156 | [sub_resource type="ConvexPolygonShape2D" id=41] 157 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 158 | 159 | [sub_resource type="ConvexPolygonShape2D" id=42] 160 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 161 | 162 | [sub_resource type="ConvexPolygonShape2D" id=43] 163 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 164 | 165 | [sub_resource type="ConvexPolygonShape2D" id=44] 166 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 167 | 168 | [sub_resource type="ConvexPolygonShape2D" id=45] 169 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 170 | 171 | [sub_resource type="ConvexPolygonShape2D" id=46] 172 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 173 | 174 | [sub_resource type="ConvexPolygonShape2D" id=47] 175 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 176 | 177 | [sub_resource type="ConvexPolygonShape2D" id=48] 178 | points = PoolVector2Array( 0, 0, 32, 0, 32, 32, 0, 32 ) 179 | 180 | [sub_resource type="TileSet" id=49] 181 | 0/name = "CliffTileset.png 0" 182 | 0/texture = ExtResource( 5 ) 183 | 0/tex_offset = Vector2( 0, 0 ) 184 | 0/modulate = Color( 1, 1, 1, 1 ) 185 | 0/region = Rect2( 0, 0, 352, 160 ) 186 | 0/tile_mode = 1 187 | 0/autotile/bitmask_mode = 1 188 | 0/autotile/bitmask_flags = [ Vector2( 0, 0 ), 432, Vector2( 0, 1 ), 438, Vector2( 0, 2 ), 54, Vector2( 0, 3 ), 48, Vector2( 1, 0 ), 504, Vector2( 1, 1 ), 511, Vector2( 1, 2 ), 63, Vector2( 1, 3 ), 56, Vector2( 2, 0 ), 216, Vector2( 2, 1 ), 219, Vector2( 2, 2 ), 27, Vector2( 2, 3 ), 24, Vector2( 3, 0 ), 144, Vector2( 3, 1 ), 146, Vector2( 3, 2 ), 18, Vector2( 3, 3 ), 16, Vector2( 4, 0 ), 176, Vector2( 4, 1 ), 182, Vector2( 4, 2 ), 434, Vector2( 4, 3 ), 50, Vector2( 4, 4 ), 178, Vector2( 5, 0 ), 248, Vector2( 5, 1 ), 255, Vector2( 5, 2 ), 507, Vector2( 5, 3 ), 59, Vector2( 5, 4 ), 251, Vector2( 6, 0 ), 440, Vector2( 6, 1 ), 447, Vector2( 6, 2 ), 510, Vector2( 6, 3 ), 62, Vector2( 6, 4 ), 446, Vector2( 7, 0 ), 152, Vector2( 7, 1 ), 155, Vector2( 7, 2 ), 218, Vector2( 7, 3 ), 26, Vector2( 7, 4 ), 154, Vector2( 8, 0 ), 184, Vector2( 8, 1 ), 191, Vector2( 8, 2 ), 506, Vector2( 8, 3 ), 58, Vector2( 8, 4 ), 186, Vector2( 9, 0 ), 443, Vector2( 9, 1 ), 254, Vector2( 9, 2 ), 442, Vector2( 9, 3 ), 190, Vector2( 10, 2 ), 250, Vector2( 10, 3 ), 187 ] 189 | 0/autotile/icon_coordinate = Vector2( 3, 3 ) 190 | 0/autotile/tile_size = Vector2( 32, 32 ) 191 | 0/autotile/spacing = 0 192 | 0/autotile/occluder_map = [ ] 193 | 0/autotile/navpoly_map = [ ] 194 | 0/autotile/priority_map = [ ] 195 | 0/autotile/z_index_map = [ ] 196 | 0/occluder_offset = Vector2( 0, 0 ) 197 | 0/navigation_offset = Vector2( 0, 0 ) 198 | 0/shape_offset = Vector2( 0, 0 ) 199 | 0/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 ) 200 | 0/shape = SubResource( 2 ) 201 | 0/shape_one_way = false 202 | 0/shape_one_way_margin = 1.0 203 | 0/shapes = [ { 204 | "autotile_coord": Vector2( 0, 0 ), 205 | "one_way": false, 206 | "one_way_margin": 1.0, 207 | "shape": SubResource( 2 ), 208 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 209 | }, { 210 | "autotile_coord": Vector2( 1, 0 ), 211 | "one_way": false, 212 | "one_way_margin": 1.0, 213 | "shape": SubResource( 3 ), 214 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 215 | }, { 216 | "autotile_coord": Vector2( 2, 0 ), 217 | "one_way": false, 218 | "one_way_margin": 1.0, 219 | "shape": SubResource( 4 ), 220 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 221 | }, { 222 | "autotile_coord": Vector2( 2, 1 ), 223 | "one_way": false, 224 | "one_way_margin": 1.0, 225 | "shape": SubResource( 5 ), 226 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 227 | }, { 228 | "autotile_coord": Vector2( 1, 1 ), 229 | "one_way": false, 230 | "one_way_margin": 1.0, 231 | "shape": SubResource( 6 ), 232 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 233 | }, { 234 | "autotile_coord": Vector2( 0, 1 ), 235 | "one_way": false, 236 | "one_way_margin": 1.0, 237 | "shape": SubResource( 7 ), 238 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 239 | }, { 240 | "autotile_coord": Vector2( 0, 2 ), 241 | "one_way": false, 242 | "one_way_margin": 1.0, 243 | "shape": SubResource( 8 ), 244 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 245 | }, { 246 | "autotile_coord": Vector2( 1, 2 ), 247 | "one_way": false, 248 | "one_way_margin": 1.0, 249 | "shape": SubResource( 9 ), 250 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 251 | }, { 252 | "autotile_coord": Vector2( 2, 2 ), 253 | "one_way": false, 254 | "one_way_margin": 1.0, 255 | "shape": SubResource( 10 ), 256 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 257 | }, { 258 | "autotile_coord": Vector2( 0, 3 ), 259 | "one_way": false, 260 | "one_way_margin": 1.0, 261 | "shape": SubResource( 11 ), 262 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 263 | }, { 264 | "autotile_coord": Vector2( 1, 3 ), 265 | "one_way": false, 266 | "one_way_margin": 1.0, 267 | "shape": SubResource( 12 ), 268 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 269 | }, { 270 | "autotile_coord": Vector2( 2, 3 ), 271 | "one_way": false, 272 | "one_way_margin": 1.0, 273 | "shape": SubResource( 13 ), 274 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 275 | }, { 276 | "autotile_coord": Vector2( 3, 3 ), 277 | "one_way": false, 278 | "one_way_margin": 1.0, 279 | "shape": SubResource( 14 ), 280 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 281 | }, { 282 | "autotile_coord": Vector2( 3, 2 ), 283 | "one_way": false, 284 | "one_way_margin": 1.0, 285 | "shape": SubResource( 15 ), 286 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 287 | }, { 288 | "autotile_coord": Vector2( 3, 1 ), 289 | "one_way": false, 290 | "one_way_margin": 1.0, 291 | "shape": SubResource( 16 ), 292 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 293 | }, { 294 | "autotile_coord": Vector2( 3, 0 ), 295 | "one_way": false, 296 | "one_way_margin": 1.0, 297 | "shape": SubResource( 17 ), 298 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 299 | }, { 300 | "autotile_coord": Vector2( 4, 0 ), 301 | "one_way": false, 302 | "one_way_margin": 1.0, 303 | "shape": SubResource( 18 ), 304 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 305 | }, { 306 | "autotile_coord": Vector2( 4, 1 ), 307 | "one_way": false, 308 | "one_way_margin": 1.0, 309 | "shape": SubResource( 19 ), 310 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 311 | }, { 312 | "autotile_coord": Vector2( 4, 2 ), 313 | "one_way": false, 314 | "one_way_margin": 1.0, 315 | "shape": SubResource( 20 ), 316 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 317 | }, { 318 | "autotile_coord": Vector2( 4, 3 ), 319 | "one_way": false, 320 | "one_way_margin": 1.0, 321 | "shape": SubResource( 21 ), 322 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 323 | }, { 324 | "autotile_coord": Vector2( 5, 3 ), 325 | "one_way": false, 326 | "one_way_margin": 1.0, 327 | "shape": SubResource( 22 ), 328 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 329 | }, { 330 | "autotile_coord": Vector2( 5, 2 ), 331 | "one_way": false, 332 | "one_way_margin": 1.0, 333 | "shape": SubResource( 23 ), 334 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 335 | }, { 336 | "autotile_coord": Vector2( 5, 1 ), 337 | "one_way": false, 338 | "one_way_margin": 1.0, 339 | "shape": SubResource( 24 ), 340 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 341 | }, { 342 | "autotile_coord": Vector2( 5, 0 ), 343 | "one_way": false, 344 | "one_way_margin": 1.0, 345 | "shape": SubResource( 25 ), 346 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 347 | }, { 348 | "autotile_coord": Vector2( 6, 0 ), 349 | "one_way": false, 350 | "one_way_margin": 1.0, 351 | "shape": SubResource( 26 ), 352 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 353 | }, { 354 | "autotile_coord": Vector2( 7, 0 ), 355 | "one_way": false, 356 | "one_way_margin": 1.0, 357 | "shape": SubResource( 27 ), 358 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 359 | }, { 360 | "autotile_coord": Vector2( 7, 1 ), 361 | "one_way": false, 362 | "one_way_margin": 1.0, 363 | "shape": SubResource( 28 ), 364 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 365 | }, { 366 | "autotile_coord": Vector2( 6, 1 ), 367 | "one_way": false, 368 | "one_way_margin": 1.0, 369 | "shape": SubResource( 29 ), 370 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 371 | }, { 372 | "autotile_coord": Vector2( 6, 2 ), 373 | "one_way": false, 374 | "one_way_margin": 1.0, 375 | "shape": SubResource( 30 ), 376 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 377 | }, { 378 | "autotile_coord": Vector2( 7, 2 ), 379 | "one_way": false, 380 | "one_way_margin": 1.0, 381 | "shape": SubResource( 31 ), 382 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 383 | }, { 384 | "autotile_coord": Vector2( 7, 3 ), 385 | "one_way": false, 386 | "one_way_margin": 1.0, 387 | "shape": SubResource( 32 ), 388 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 389 | }, { 390 | "autotile_coord": Vector2( 6, 3 ), 391 | "one_way": false, 392 | "one_way_margin": 1.0, 393 | "shape": SubResource( 33 ), 394 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 395 | }, { 396 | "autotile_coord": Vector2( 4, 4 ), 397 | "one_way": false, 398 | "one_way_margin": 1.0, 399 | "shape": SubResource( 34 ), 400 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 401 | }, { 402 | "autotile_coord": Vector2( 5, 4 ), 403 | "one_way": false, 404 | "one_way_margin": 1.0, 405 | "shape": SubResource( 35 ), 406 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 407 | }, { 408 | "autotile_coord": Vector2( 6, 4 ), 409 | "one_way": false, 410 | "one_way_margin": 1.0, 411 | "shape": SubResource( 36 ), 412 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 413 | }, { 414 | "autotile_coord": Vector2( 7, 4 ), 415 | "one_way": false, 416 | "one_way_margin": 1.0, 417 | "shape": SubResource( 37 ), 418 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 419 | }, { 420 | "autotile_coord": Vector2( 8, 4 ), 421 | "one_way": false, 422 | "one_way_margin": 1.0, 423 | "shape": SubResource( 38 ), 424 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 425 | }, { 426 | "autotile_coord": Vector2( 8, 3 ), 427 | "one_way": false, 428 | "one_way_margin": 1.0, 429 | "shape": SubResource( 39 ), 430 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 431 | }, { 432 | "autotile_coord": Vector2( 8, 2 ), 433 | "one_way": false, 434 | "one_way_margin": 1.0, 435 | "shape": SubResource( 40 ), 436 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 437 | }, { 438 | "autotile_coord": Vector2( 8, 1 ), 439 | "one_way": false, 440 | "one_way_margin": 1.0, 441 | "shape": SubResource( 41 ), 442 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 443 | }, { 444 | "autotile_coord": Vector2( 8, 0 ), 445 | "one_way": false, 446 | "one_way_margin": 1.0, 447 | "shape": SubResource( 42 ), 448 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 449 | }, { 450 | "autotile_coord": Vector2( 9, 0 ), 451 | "one_way": false, 452 | "one_way_margin": 1.0, 453 | "shape": SubResource( 43 ), 454 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 455 | }, { 456 | "autotile_coord": Vector2( 9, 1 ), 457 | "one_way": false, 458 | "one_way_margin": 1.0, 459 | "shape": SubResource( 44 ), 460 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 461 | }, { 462 | "autotile_coord": Vector2( 9, 2 ), 463 | "one_way": false, 464 | "one_way_margin": 1.0, 465 | "shape": SubResource( 45 ), 466 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 467 | }, { 468 | "autotile_coord": Vector2( 9, 3 ), 469 | "one_way": false, 470 | "one_way_margin": 1.0, 471 | "shape": SubResource( 46 ), 472 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 473 | }, { 474 | "autotile_coord": Vector2( 10, 3 ), 475 | "one_way": false, 476 | "one_way_margin": 1.0, 477 | "shape": SubResource( 47 ), 478 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 479 | }, { 480 | "autotile_coord": Vector2( 10, 2 ), 481 | "one_way": false, 482 | "one_way_margin": 1.0, 483 | "shape": SubResource( 48 ), 484 | "shape_transform": Transform2D( 1, 0, 0, 1, 0, 0 ) 485 | } ] 486 | 0/z_index = 0 487 | 488 | [node name="World" type="Node2D"] 489 | 490 | [node name="BackgroundGrass" type="Sprite" parent="."] 491 | position = Vector2( 160, 90 ) 492 | texture = ExtResource( 3 ) 493 | region_enabled = true 494 | region_rect = Rect2( 0, 0, 688, 464 ) 495 | 496 | [node name="DirtPath" type="TileMap" parent="."] 497 | tile_set = SubResource( 1 ) 498 | cell_size = Vector2( 16, 16 ) 499 | format = 1 500 | tile_data = PoolIntArray( -65528, 0, 0, -65527, 0, 2, 8, 0, 65536, 9, 0, 65538, 65542, 0, 196608, 65543, 0, 196609, 65544, 0, 196614, 65545, 0, 9, 65546, 0, 1, 65547, 0, 1, 65548, 0, 1, 65549, 0, 2, 131081, 0, 65536, 131082, 0, 65537, 131083, 0, 65537, 131084, 0, 65537, 131085, 0, 65538, 196614, 0, 0, 196615, 0, 1, 196616, 0, 1, 196617, 0, 131078, 196618, 0, 65537, 196619, 0, 65537, 196620, 0, 65537, 196621, 0, 65538, 327679, 0, 0, 262144, 0, 1, 262145, 0, 1, 262146, 0, 1, 262147, 0, 1, 262148, 0, 5, 262149, 0, 196609, 262150, 0, 196614, 262151, 0, 131073, 262152, 0, 131073, 262153, 0, 131073, 262154, 0, 131073, 262155, 0, 65542, 262156, 0, 65537, 262157, 0, 131077, 262158, 0, 1, 262159, 0, 1, 262160, 0, 1, 262161, 0, 1, 262162, 0, 1, 262163, 0, 1, 262164, 0, 2, 393215, 0, 65536, 327680, 0, 65537, 327681, 0, 65537, 327682, 0, 65537, 327683, 0, 65541, 327684, 0, 131074, 327691, 0, 131072, 327692, 0, 131073, 327693, 0, 131073, 327694, 0, 131073, 327695, 0, 131073, 327696, 0, 131073, 327697, 0, 131073, 327698, 0, 131073, 327699, 0, 131073, 327700, 0, 131074, 458751, 0, 131072, 393216, 0, 131073, 393217, 0, 131073, 393218, 0, 131073, 393219, 0, 131074, 655374, 0, 0, 655375, 0, 1, 655376, 0, 1, 655377, 0, 2, 720908, 0, 196608, 720909, 0, 196609, 720910, 0, 196614, 720911, 0, 131073, 720912, 0, 131073, 720913, 0, 131074 ) 501 | 502 | [node name="DirtCliff" type="TileMap" parent="."] 503 | tile_set = SubResource( 49 ) 504 | cell_size = Vector2( 32, 32 ) 505 | collision_mask = 0 506 | format = 1 507 | tile_data = PoolIntArray( -196612, 0, 4, -196611, 0, 196609, -196610, 0, 196609, -196609, 0, 196609, -262144, 0, 196609, -262143, 0, 196609, -262142, 0, 196609, -262141, 0, 196609, -262140, 0, 196609, -262139, 0, 196609, -262138, 0, 196609, -262137, 0, 196609, -262136, 0, 196609, -262135, 0, 196609, -262134, 0, 196609, -262133, 0, 196609, -262132, 0, 196609, -262131, 0, 196609, -262130, 0, 7, -131076, 0, 65539, -196594, 0, 65539, -65540, 0, 65539, -131058, 0, 65539, -4, 0, 65539, -1, 0, 0, -65536, 0, 1, -65535, 0, 1, -65534, 0, 1, -65533, 0, 2, -65528, 0, 0, -65527, 0, 1, -65526, 0, 2, -65522, 0, 65539, 65532, 0, 65539, 65535, 0, 65536, 0, 0, 65541, 1, 0, 131073, 2, 0, 131073, 3, 0, 131074, 8, 0, 131072, 9, 0, 65544, 10, 0, 131074, 14, 0, 65539, 131068, 0, 65539, 131071, 0, 131072, 65536, 0, 131074, 65545, 0, 131075, 65550, 0, 65539, 196604, 0, 65539, 131086, 0, 65539, 262140, 0, 65539, 196612, 0, 3, 196617, 0, 0, 196618, 0, 5, 196619, 0, 196610, 196622, 0, 65539, 327676, 0, 65539, 262146, 0, 0, 262147, 0, 1, 262148, 0, 131079, 262152, 0, 196608, 262153, 0, 196617, 262154, 0, 131074, 262158, 0, 65539, 393212, 0, 65539, 327680, 0, 196608, 327681, 0, 196609, 327682, 0, 196614, 327683, 0, 131073, 327684, 0, 131074, 327689, 0, 131075, 327694, 0, 65539, 458748, 0, 65539, 393230, 0, 65539, 524284, 0, 65539, 458766, 0, 65539, 589820, 0, 196612, 589821, 0, 196609, 589822, 0, 196609, 589823, 0, 196609, 524288, 0, 196609, 524289, 0, 196609, 524290, 0, 196609, 524291, 0, 196609, 524292, 0, 196609, 524293, 0, 196609, 524294, 0, 196609, 524295, 0, 196609, 524296, 0, 196609, 524297, 0, 196609, 524298, 0, 196609, 524299, 0, 196609, 524300, 0, 196609, 524301, 0, 196609, 524302, 0, 196615 ) 508 | 509 | [node name="PlayerCamera" parent="." instance=ExtResource( 10 )] 510 | position = Vector2( 152, 59 ) 511 | 512 | [node name="TopLeft" parent="PlayerCamera/Limits" index="0"] 513 | position = Vector2( -96, -96 ) 514 | 515 | [node name="BottomRight" parent="PlayerCamera/Limits" index="1"] 516 | position = Vector2( 448, 256 ) 517 | 518 | [node name="Playground" type="YSort" parent="."] 519 | 520 | [node name="Player" parent="Playground" instance=ExtResource( 2 )] 521 | position = Vector2( 152, 59 ) 522 | 523 | [node name="RemoteTransform2D" type="RemoteTransform2D" parent="Playground/Player"] 524 | remote_path = NodePath("../../../PlayerCamera") 525 | 526 | [node name="Bushes" type="YSort" parent="Playground"] 527 | 528 | [node name="Bush" parent="Playground/Bushes" instance=ExtResource( 1 )] 529 | position = Vector2( 72, 56 ) 530 | 531 | [node name="Bush2" parent="Playground/Bushes" instance=ExtResource( 1 )] 532 | position = Vector2( 176, 160 ) 533 | 534 | [node name="Bush3" parent="Playground/Bushes" instance=ExtResource( 1 )] 535 | position = Vector2( 96, 104 ) 536 | 537 | [node name="Bush4" parent="Playground/Bushes" instance=ExtResource( 1 )] 538 | position = Vector2( 240, 136 ) 539 | 540 | [node name="Bush5" parent="Playground/Bushes" instance=ExtResource( 1 )] 541 | position = Vector2( 256, 48 ) 542 | 543 | [node name="Bush6" parent="Playground/Bushes" instance=ExtResource( 1 )] 544 | position = Vector2( 216, 112 ) 545 | 546 | [node name="Grass" type="YSort" parent="Playground"] 547 | 548 | [node name="Grass" parent="Playground/Grass" instance=ExtResource( 6 )] 549 | position = Vector2( 32, 144 ) 550 | 551 | [node name="Grass2" parent="Playground/Grass" instance=ExtResource( 6 )] 552 | position = Vector2( 16, 144 ) 553 | 554 | [node name="Grass3" parent="Playground/Grass" instance=ExtResource( 6 )] 555 | position = Vector2( 48, 128 ) 556 | 557 | [node name="Grass4" parent="Playground/Grass" instance=ExtResource( 6 )] 558 | position = Vector2( 32, 32 ) 559 | 560 | [node name="Grass5" parent="Playground/Grass" instance=ExtResource( 6 )] 561 | position = Vector2( 48, 144 ) 562 | 563 | [node name="Grass6" parent="Playground/Grass" instance=ExtResource( 6 )] 564 | position = Vector2( 240, 0 ) 565 | 566 | [node name="Grass7" parent="Playground/Grass" instance=ExtResource( 6 )] 567 | position = Vector2( 160, 112 ) 568 | 569 | [node name="Grass8" parent="Playground/Grass" instance=ExtResource( 6 )] 570 | position = Vector2( 272, 112 ) 571 | 572 | [node name="Grass9" parent="Playground/Grass" instance=ExtResource( 6 )] 573 | position = Vector2( 272, 96 ) 574 | 575 | [node name="Grass10" parent="Playground/Grass" instance=ExtResource( 6 )] 576 | position = Vector2( 256, 112 ) 577 | 578 | [node name="Trees" type="YSort" parent="Playground"] 579 | 580 | [node name="Tree" parent="Playground/Trees" instance=ExtResource( 9 )] 581 | position = Vector2( 112, 152 ) 582 | 583 | [node name="Tree2" parent="Playground/Trees" instance=ExtResource( 9 )] 584 | position = Vector2( 224, 32 ) 585 | 586 | [node name="Bat" parent="Playground" instance=ExtResource( 7 )] 587 | position = Vector2( 24, 117 ) 588 | 589 | [node name="Bat2" parent="Playground" instance=ExtResource( 7 )] 590 | position = Vector2( 232, 3 ) 591 | 592 | [node name="Bat3" parent="Playground" instance=ExtResource( 7 )] 593 | position = Vector2( 222, 160 ) 594 | 595 | [node name="CanvasLayer" type="CanvasLayer" parent="."] 596 | 597 | [node name="HealthUI" parent="CanvasLayer" instance=ExtResource( 8 )] 598 | margin_left = 2.0 599 | margin_top = 2.0 600 | margin_right = 62.0 601 | margin_bottom = 13.0 602 | 603 | [editable path="PlayerCamera"] 604 | --------------------------------------------------------------------------------