feat: implemented bullet impacts

This commit is contained in:
Sara 2025-07-19 12:33:40 +02:00
parent 7a5c129bb9
commit a7b3f97b3b
14 changed files with 644 additions and 12 deletions

View file

@ -0,0 +1,398 @@
{
"connections": [
{
"from": "shape_3",
"from_port": 0,
"to": "rotate",
"to_port": 0
},
{
"from": "shape",
"from_port": 0,
"to": "colorize",
"to_port": 0
},
{
"from": "colorize",
"from_port": 0,
"to": "blend2",
"to_port": 1
},
{
"from": "uniform",
"from_port": 0,
"to": "blend2_2",
"to_port": 0
},
{
"from": "fbm2",
"from_port": 0,
"to": "blend2_2",
"to_port": 1
},
{
"from": "shape_2",
"from_port": 0,
"to": "rotate_2",
"to_port": 0
},
{
"from": "blend2_2",
"from_port": 0,
"to": "blend2",
"to_port": 0
},
{
"from": "rotate",
"from_port": 0,
"to": "math",
"to_port": 0
},
{
"from": "rotate_2",
"from_port": 0,
"to": "math",
"to_port": 1
},
{
"from": "math",
"from_port": 0,
"to": "blend2_2",
"to_port": 2
},
{
"from": "blend2",
"from_port": 0,
"to": "Material",
"to_port": 0
},
{
"from": "normal_map2",
"from_port": 0,
"to": "Material",
"to_port": 4
},
{
"from": "shape_4",
"from_port": 0,
"to": "math_2",
"to_port": 0
},
{
"from": "fbm2",
"from_port": 0,
"to": "math_3",
"to_port": 0
},
{
"from": "math_2",
"from_port": 0,
"to": "math_3",
"to_port": 1
},
{
"from": "math_3",
"from_port": 0,
"to": "normal_map2",
"to_port": 0
},
{
"from": "math",
"from_port": 0,
"to": "Material",
"to_port": 7
}
],
"label": "Graph",
"longdesc": "",
"name": "@@369",
"node_position": {
"x": 0,
"y": 0
},
"nodes": [
{
"export_last_target": "Blender",
"export_paths": {
"Blender": "/home/sara/Documents/gd-projects/wave-survival-fps/project/assets/materials/effects/bullet_impact",
"Godot/Godot 4 Standard": "/home/sara/Documents/gd-projects/wave-survival-fps/project/assets/materials/bullet_impact"
},
"name": "Material",
"node_position": {
"x": 35,
"y": 206
},
"parameters": {
"albedo_color": {
"a": 1,
"b": 1,
"g": 1,
"r": 1,
"type": "Color"
},
"ao": 1,
"depth_scale": 0.5,
"emission_energy": 1,
"flags_transparent": true,
"metallic": 0,
"normal": 1,
"roughness": 1,
"size": 11,
"sss": 1
},
"seed_int": 0,
"type": "material"
},
{
"name": "shape",
"node_position": {
"x": -884,
"y": 617
},
"parameters": {
"edge": 0.52,
"radius": 0.57,
"shape": 0,
"sides": 6
},
"seed_int": 0,
"type": "shape"
},
{
"name": "shape_3",
"node_position": {
"x": -1354,
"y": 458
},
"parameters": {
"edge": 0.13,
"radius": 0.49,
"shape": 2,
"sides": 7
},
"seed_int": 0,
"type": "shape"
},
{
"generic_size": 1,
"name": "rotate",
"node_position": {
"x": -1203,
"y": 461
},
"parameters": {
"cx": 0,
"cy": 0,
"rotate": -112.325
},
"seed_int": 0,
"type": "rotate"
},
{
"name": "fbm2",
"node_position": {
"x": -962,
"y": 231
},
"parameters": {
"folds": 0,
"iterations": 8,
"noise": 1,
"offset": 0,
"persistence": 1,
"scale_x": 1,
"scale_y": 1
},
"seed_int": 453903360,
"type": "fbm2"
},
{
"name": "colorize",
"node_position": {
"x": -638,
"y": 321
},
"parameters": {
"gradient": {
"interpolation": 1,
"points": [
{
"a": 0,
"b": 0,
"g": 0,
"pos": 0,
"r": 0
},
{
"a": 1,
"b": 0,
"g": 0,
"pos": 1,
"r": 0
}
],
"type": "Gradient"
}
},
"seed_int": 0,
"type": "colorize"
},
{
"generic_size": 1,
"name": "blend2",
"node_position": {
"x": -413,
"y": 205
},
"parameters": {
"amount1": 1,
"blend_type1": 0
},
"seed_int": 0,
"type": "blend2"
},
{
"generic_size": 1,
"name": "blend2_2",
"node_position": {
"x": -691,
"y": 205
},
"parameters": {
"amount1": 1,
"blend_type1": 0
},
"seed_int": 0,
"type": "blend2"
},
{
"name": "uniform",
"node_position": {
"x": -689,
"y": 139
},
"parameters": {
"color": {
"a": 0,
"b": 0,
"g": 0,
"r": 0,
"type": "Color"
}
},
"seed_int": 0,
"type": "uniform"
},
{
"name": "shape_2",
"node_position": {
"x": -1355.388916,
"y": 601.694458
},
"parameters": {
"edge": 0.12,
"radius": 0.49,
"shape": 2,
"sides": 8
},
"seed_int": 0,
"type": "shape"
},
{
"generic_size": 1,
"name": "rotate_2",
"node_position": {
"x": -1198.5,
"y": 580
},
"parameters": {
"cx": 0,
"cy": 0,
"rotate": -89.08
},
"seed_int": 0,
"type": "rotate"
},
{
"name": "math",
"node_position": {
"x": -939,
"y": 455
},
"parameters": {
"clamp": false,
"default_in1": 0,
"default_in2": 0,
"op": 2
},
"seed_int": 0,
"type": "math"
},
{
"name": "normal_map2",
"node_position": {
"x": -221.561737,
"y": 545.807861
},
"parameters": {
"buffer": 1,
"param2": 0,
"size": 10,
"strength": 1
},
"seed_int": 0,
"type": "normal_map2"
},
{
"name": "shape_4",
"node_position": {
"x": -881.154663,
"y": 762.272461
},
"parameters": {
"edge": 1.1,
"radius": 0.51,
"shape": 0,
"sides": 6
},
"seed_int": 0,
"type": "shape"
},
{
"name": "math_2",
"node_position": {
"x": -702.654663,
"y": 738.272461
},
"parameters": {
"clamp": false,
"default_in1": 0,
"default_in2": 0.71,
"op": 6
},
"seed_int": 0,
"type": "math"
},
{
"name": "math_3",
"node_position": {
"x": -447.654663,
"y": 549.272461
},
"parameters": {
"clamp": true,
"default_in1": 0,
"default_in2": 0,
"op": 1
},
"seed_int": 0,
"type": "math"
}
],
"parameters": {
},
"seed_int": 0,
"shortdesc": "",
"type": "graph"
}

View file

@ -0,0 +1,70 @@
#include "hitscan_muzzle.h"
#include "macros.h"
#include "scene/resources/packed_scene.h"
void HitscanMuzzle::_bind_methods() {
BIND_PROPERTY(Variant::FLOAT, spread);
BIND_PROPERTY(Variant::INT, damage);
BIND_PROPERTY(Variant::INT, ray_count);
}
void HitscanMuzzle::ready() {
this->home_transform = get_transform();
this->impact_effect = ResourceLoader::load("res://objects/effects/bullet_impact.tscn");
if (!this->impact_effect.is_valid()) {
print_error("HitscanMuzzle::ready: impact effect is invalid");
}
}
void HitscanMuzzle::_notification(int what) {
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
switch (what) {
default:
return;
case NOTIFICATION_READY:
ready();
return;
}
}
void HitscanMuzzle::shoot() {
set_transform(this->home_transform);
rotate_object_local(Vector3(0.f, 1.f, 0.f), Math::random(-Math::PI, Math::PI));
rotate_object_local(Vector3(1.f, 0.f, 0.f), Math::random(0.f, this->spread));
force_raycast_update();
Node *effect_as_node{ this->impact_effect->instantiate() };
if (Node3D * effect{ cast_to<Node3D>(effect_as_node) }) {
get_tree()->get_current_scene()->add_child(effect);
Vector3 const point{ get_collision_point() };
Vector3 const normal{ get_collision_normal() };
effect->look_at_from_position(point, point + normal);
} else if (effect_as_node) {
effect_as_node->queue_free();
}
}
void HitscanMuzzle::set_spread(float spread) {
this->spread = spread;
}
float HitscanMuzzle::get_spread() const {
return this->spread;
}
void HitscanMuzzle::set_damage(int damage) {
this->damage = damage;
}
int HitscanMuzzle::get_damage() const {
return this->damage;
}
void HitscanMuzzle::set_ray_count(int amount) {
this->ray_count = amount;
}
int HitscanMuzzle::get_ray_count() const {
return this->ray_count;
}

View file

@ -0,0 +1,32 @@
#ifndef HITSCAN_MUZZLE_H
#define HITSCAN_MUZZLE_H
#include "scene/3d/physics/ray_cast_3d.h"
class HitscanMuzzle : public RayCast3D {
GDCLASS(HitscanMuzzle, RayCast3D);
static void _bind_methods();
void ready();
public:
void _notification(int what);
void shoot();
void shoot_multiple(int count);
void set_spread(float spread);
float get_spread() const;
void set_damage(int damage);
int get_damage() const;
void set_ray_count(int amount);
int get_ray_count() const;
private:
float spread{ 0.01f };
int damage{ 1 };
int ray_count{ 1 };
Transform3D home_transform{};
Ref<PackedScene> impact_effect{};
};
#endif // !HITSCAN_MUZZLE_H

View file

@ -1,6 +1,7 @@
#include "register_types.h" #include "register_types.h"
#include "core/object/class_db.h" #include "core/object/class_db.h"
#include "wave_survival/hitscan_muzzle.h"
#include "wave_survival/player_body.h" #include "wave_survival/player_body.h"
#include "wave_survival/player_camera.h" #include "wave_survival/player_camera.h"
#include "wave_survival/player_input.h" #include "wave_survival/player_input.h"
@ -18,6 +19,7 @@ void initialize_wave_survival_module(ModuleInitializationLevel p_level) {
GDREGISTER_RUNTIME_CLASS(WeaponBase); GDREGISTER_RUNTIME_CLASS(WeaponBase);
GDREGISTER_CLASS(Rifle); GDREGISTER_CLASS(Rifle);
GDREGISTER_CLASS(WeaponInventory); GDREGISTER_CLASS(WeaponInventory);
GDREGISTER_CLASS(HitscanMuzzle);
} }
void uninitialize_wave_survival_module(ModuleInitializationLevel p_level) { void uninitialize_wave_survival_module(ModuleInitializationLevel p_level) {

View file

@ -1,5 +1,6 @@
#include "rifle.h" #include "rifle.h"
#include "scene/animation/animation_player.h" #include "scene/animation/animation_player.h"
#include "wave_survival/hitscan_muzzle.h"
#include "wave_survival/player_body.h" #include "wave_survival/player_body.h"
#include "wave_survival/player_input.h" #include "wave_survival/player_input.h"
@ -36,6 +37,7 @@ void Rifle::stop_run_anim() {
void Rifle::shoot() { void Rifle::shoot() {
if (!is_animating()) { if (!is_animating()) {
this->muzzle->shoot();
if (this->request_alt_mode) { if (this->request_alt_mode) {
get_anim()->queue("fire_aim"); get_anim()->queue("fire_aim");
} else { } else {
@ -76,6 +78,7 @@ void Rifle::on_animation_changed(String new_animation) {
} }
void Rifle::ready() { void Rifle::ready() {
this->muzzle = cast_to<HitscanMuzzle>(get_node(NodePath("%HitscanMuzzle")));
get_anim()->connect("current_animation_changed", callable_mp(this, &self_type::on_animation_changed)); get_anim()->connect("current_animation_changed", callable_mp(this, &self_type::on_animation_changed));
get_input()->connect(PlayerInput::sig_primary_fire, callable_mp(this, &self_type::on_primary_fire)); get_input()->connect(PlayerInput::sig_primary_fire, callable_mp(this, &self_type::on_primary_fire));
get_input()->connect(PlayerInput::sig_alt_mode, callable_mp(this, &self_type::on_alt_mode)); get_input()->connect(PlayerInput::sig_alt_mode, callable_mp(this, &self_type::on_alt_mode));

View file

@ -4,6 +4,7 @@
#include "wave_survival/player_camera.h" #include "wave_survival/player_camera.h"
#include "wave_survival/weapon_base.h" #include "wave_survival/weapon_base.h"
class PlayerBody; class PlayerBody;
class HitscanMuzzle;
class Rifle : public WeaponBase { class Rifle : public WeaponBase {
GDCLASS(Rifle, WeaponBase); GDCLASS(Rifle, WeaponBase);
@ -35,6 +36,8 @@ private:
bool request_alt_mode{ false }; bool request_alt_mode{ false };
bool in_alt_mode{ false }; bool in_alt_mode{ false };
bool running{ false }; bool running{ false };
HitscanMuzzle *muzzle{ nullptr };
}; };
#endif // !WEAPONS_RIFLE_H #endif // !WEAPONS_RIFLE_H

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 KiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://da2nhu26lshix"
path="res://.godot/imported/bullet_impact_albedo.png-a285c5d1917287b07f88b5818370b861.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/textures/effects/bullet_impact_albedo.png"
dest_files=["res://.godot/imported/bullet_impact_albedo.png-a285c5d1917287b07f88b5818370b861.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 MiB

View file

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://duj1snmy7lk1l"
path="res://.godot/imported/bullet_impact_normal.png-5bf99cdbe81296319f4892ecad612d8f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/textures/effects/bullet_impact_normal.png"
dest_files=["res://.godot/imported/bullet_impact_normal.png-5bf99cdbe81296319f4892ecad612d8f.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,13 @@
[gd_scene load_steps=3 format=3 uid="uid://me2saxnylpfa"]
[ext_resource type="Texture2D" uid="uid://da2nhu26lshix" path="res://assets/textures/effects/bullet_impact_albedo.png" id="1_dkdib"]
[ext_resource type="Texture2D" uid="uid://duj1snmy7lk1l" path="res://assets/textures/effects/bullet_impact_normal.png" id="2_drixv"]
[node name="BulletImpact" type="Node3D"]
[node name="Decal" type="Decal" parent="."]
transform = Transform3D(1, 0, 0, 0, -4.371139e-08, 1, 0, -1, -4.371139e-08, 0, 0, 0)
size = Vector3(0.1, 0.1, 0.1)
texture_albedo = ExtResource("1_dkdib")
texture_normal = ExtResource("2_drixv")
cull_mask = 1047553

View file

@ -7,13 +7,20 @@ anim = NodePath("rifle/AnimationPlayer")
[node name="rifle" parent="." instance=ExtResource("1_afgyw")] [node name="rifle" parent="." instance=ExtResource("1_afgyw")]
[node name="Body" parent="rifle/Character/Skeleton3D" index="0"]
layers = 2
[node name="mesh" parent="rifle/Character/Skeleton3D" index="1"]
layers = 2
[node name="BoneAttachment3D" type="BoneAttachment3D" parent="rifle/Character/Skeleton3D" index="2"] [node name="BoneAttachment3D" type="BoneAttachment3D" parent="rifle/Character/Skeleton3D" index="2"]
transform = Transform3D(1, -2.0932122e-15, 2.3524223e-18, 2.3525281e-18, 0.0022477505, 0.99999744, -2.0932126e-15, -0.9999974, 0.0022477508, 0.07988295, -0.13953947, -0.33976445) transform = Transform3D(1, -2.0932122e-15, 2.3524223e-18, 2.3525281e-18, 0.0022477505, 0.99999744, -2.0932126e-15, -0.9999974, 0.0022477508, 0.07988295, -0.13953947, -0.33976445)
bone_name = "root" bone_name = "root"
bone_idx = 39 bone_idx = 39
[node name="RayCast3D" type="RayCast3D" parent="rifle/Character/Skeleton3D/BoneAttachment3D"] [node name="HitscanMuzzle" type="HitscanMuzzle" parent="rifle/Character/Skeleton3D/BoneAttachment3D"]
transform = Transform3D(1, 0, -1.6543612e-24, 0, 1, 0, 4.135903e-25, 0, 1, 0, 0.00010576844, 0.047063388) unique_name_in_owner = true
transform = Transform3D(1, 0, 0, 0, 0.99999994, 0, 0, 0, 0.99999994, 2.746498e-26, 0, 0.059500493)
target_position = Vector3(0, 100, 0) target_position = Vector3(0, 100, 0)
[node name="AnimationPlayer" parent="rifle" index="1"] [node name="AnimationPlayer" parent="rifle" index="1"]

View file

@ -67,3 +67,8 @@ switch_weapon={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
] ]
} }
[layer_names]
3d_render/layer_1="Default"
3d_render/layer_2="FirstPerson"