feat: added interactions, demo packs and destructables

This commit is contained in:
Sara 2025-07-28 21:17:11 +02:00
parent d66c999039
commit 5a4ac26c72
12 changed files with 369 additions and 23 deletions

View file

@ -0,0 +1,33 @@
#include "interactable.h"
#include "macros.h"
void Interactable::_bind_methods() {
ClassDB::bind_method(D_METHOD("activate", "interactor"), &self_type::activate);
ClassDB::bind_method(D_METHOD("set_highlighted", "value"), &self_type::set_highlighted);
ClassDB::bind_method(D_METHOD("get_highlighted"), &self_type::get_highlighted);
GDVIRTUAL_BIND(_activated, "interactor");
GDVIRTUAL_BIND(_highlight_changed, "interactor", "value");
}
void Interactable::activate(PlayerInteractor *interactor) {
activated(interactor);
GDVIRTUAL_CALL(_activated, interactor);
}
void Interactable::set_highlighted(PlayerInteractor *interactor, bool value) {
this->highlighted = value;
highlight_changed(interactor, value);
GDVIRTUAL_CALL(_highlight_changed, interactor, value);
}
bool Interactable::get_highlighted() const {
return this->highlighted;
}
void Interactable::set_highlight_tooltip(String value) {
this->highlight_tooltip = value;
}
String Interactable::get_highlight_tooltip() const {
return this->highlight_tooltip;
}

View file

@ -0,0 +1,30 @@
#ifndef INTERACTABLE_H
#define INTERACTABLE_H
#include "player_interactor.h"
#include "scene/main/node.h"
class Interactable : public Node {
GDCLASS(Interactable, Node);
static void _bind_methods();
protected:
virtual void activated(PlayerInteractor *interactor) {}
GDVIRTUAL1_REQUIRED(_activated, PlayerInteractor *);
virtual void highlight_changed(PlayerInteractor *interactor, bool new_value) {}
GDVIRTUAL2(_highlight_changed, PlayerInteractor *, bool);
public:
void activate(PlayerInteractor *interactor);
void set_highlighted(PlayerInteractor *interactor, bool value);
bool get_highlighted() const;
void set_highlight_tooltip(String value);
String get_highlight_tooltip() const;
private:
bool highlighted{ false };
String highlight_tooltip{ "Activate" };
};
#endif // !INTERACTABLE_H

View file

@ -9,6 +9,7 @@ String PlayerInput::sig_switch_weapon{ "switch_weapon" };
String PlayerInput::sig_run{ "run" };
String PlayerInput::sig_jump{ "jump" };
String PlayerInput::sig_crouch{ "crouch" };
String PlayerInput::sig_activate{ "activate" };
void PlayerInput::_bind_methods() {
ADD_SIGNAL(MethodInfo(sig_movement_input, PropertyInfo(Variant::VECTOR2, "axes")));
@ -19,6 +20,7 @@ void PlayerInput::_bind_methods() {
ADD_SIGNAL(MethodInfo(sig_run, PropertyInfo(Variant::BOOL, "is_running")));
ADD_SIGNAL(MethodInfo(sig_jump));
ADD_SIGNAL(MethodInfo(sig_crouch, PropertyInfo(Variant::BOOL, "is_crouching")));
ADD_SIGNAL(MethodInfo(sig_activate));
}
void PlayerInput::normalize_input() {
@ -83,4 +85,7 @@ void PlayerInput::unhandled_input(Ref<InputEvent> const &event) {
if (event->is_action("switch_weapon") && event->is_pressed()) {
emit_signal(sig_switch_weapon);
}
if (event->is_action("activate") && event->is_pressed()) {
emit_signal(sig_activate);
}
}

View file

@ -19,6 +19,7 @@ public:
static String sig_run;
static String sig_jump;
static String sig_crouch;
static String sig_activate;
private:
bool was_paused{ false };

View file

@ -0,0 +1,87 @@
#include "player_interactor.h"
#include "interactable.h"
#include "player_body.h"
#include "player_input.h"
void PlayerInteractor::_bind_methods() {
ClassDB::bind_method(D_METHOD("pickup_demo_pack"), &self_type::pickup_demo_pack);
ClassDB::bind_method(D_METHOD("try_use_demo_pack"), &self_type::try_use_demo_pack);
}
void PlayerInteractor::highlight_removed() {
this->interactable->disconnect("tree_exiting", this->on_highlight_removed);
this->interactable->set_highlighted(this, false);
this->interactable = nullptr;
}
void PlayerInteractor::activate() {
if (interactable != nullptr) {
interactable->activate(this);
}
}
void PlayerInteractor::ready() {
PlayerInput *input{ cast_to<PlayerInput>(get_node(NodePath("%PlayerInput"))) };
input->connect(PlayerInput::sig_activate, callable_mp(this, &self_type::activate));
}
void PlayerInteractor::process(double delta) {
Interactable *new_interactable{ nullptr };
if (is_colliding()) {
if (Node * hit{ cast_to<Node>(this->get_collider(0)) }) {
TypedArray<Node> interactables = hit->find_children("*", Interactable::get_class_static());
new_interactable = interactables.size() > 0 ? cast_to<Interactable>(interactables[0]) : nullptr;
}
}
if (new_interactable != this->interactable) {
if (this->interactable != nullptr) {
this->interactable->set_highlighted(this, false);
this->interactable->disconnect("tree_exiting", this->on_highlight_removed);
}
this->interactable = new_interactable;
if (this->interactable != nullptr) {
this->interactable->set_highlighted(this, true);
this->interactable->connect("tree_exiting", this->on_highlight_removed);
}
}
}
void PlayerInteractor::_notification(int what) {
if (Engine::get_singleton()->is_editor_hint()) {
return;
}
switch (what) {
default:
return;
case NOTIFICATION_READY:
set_process(true);
ready();
return;
case NOTIFICATION_PROCESS:
process(get_process_delta_time());
return;
}
}
PackedStringArray PlayerInteractor::get_configuration_warnings() const {
PackedStringArray warnings{ super_type::get_configuration_warnings() };
if (cast_to<PlayerBody>(get_owner()) == nullptr) {
warnings.push_back("PlayerInteractor should be part of and owned by a PlayerBody node to work properly.");
}
if (get_node(NodePath("%PlayerInput")) == nullptr) {
warnings.push_back("PlayerInteractor should have a node named '%PlayerInput' in the same scene as it");
}
return warnings;
}
void PlayerInteractor::pickup_demo_pack() {
++this->num_demo_packs;
}
bool PlayerInteractor::try_use_demo_pack() {
if (this->num_demo_packs > 0) {
--this->num_demo_packs;
return true;
}
return false;
}

View file

@ -0,0 +1,30 @@
#ifndef PLAYER_INTERACTOR_H
#define PLAYER_INTERACTOR_H
#include "scene/3d/physics/shape_cast_3d.h"
class Interactable;
class PlayerInteractor : public ShapeCast3D {
GDCLASS(PlayerInteractor, ShapeCast3D);
static void _bind_methods();
void highlight_removed();
void activate();
void ready();
void process(double delta);
protected:
void _notification(int what);
public:
virtual PackedStringArray get_configuration_warnings() const override;
void pickup_demo_pack();
bool try_use_demo_pack();
private:
int num_demo_packs{ 0 };
Interactable *interactable{ nullptr };
Callable on_highlight_removed{ callable_mp(this, &self_type::highlight_removed) };
static String activate_method_name;
};
#endif // !PLAYER_INTERACTOR_H

View file

@ -7,12 +7,14 @@
#include "wave_survival/health_status.h"
#include "wave_survival/hitbox.h"
#include "wave_survival/hitscan_muzzle.h"
#include "wave_survival/interactable.h"
#include "wave_survival/npc_unit.h"
#include "wave_survival/patrol_path.h"
#include "wave_survival/player_body.h"
#include "wave_survival/player_camera.h"
#include "wave_survival/player_detector.h"
#include "wave_survival/player_input.h"
#include "wave_survival/player_interactor.h"
#include "wave_survival/state.h"
#include "wave_survival/state_machine.h"
#include "wave_survival/weapon_base.h"
@ -43,6 +45,8 @@ void initialize_wave_survival_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(PlayerDetector);
GDREGISTER_CLASS(Hitbox);
GDREGISTER_CLASS(DamageBox);
GDREGISTER_CLASS(PlayerInteractor);
GDREGISTER_CLASS(Interactable);
}
void uninitialize_wave_survival_module(ModuleInitializationLevel p_level) {

View file

@ -25,6 +25,9 @@ public:
void set_starting_weapon(Ref<PackedScene> weapon_scene);
Ref<PackedScene> get_starting_weapon() const;
void pickup_demo_pack();
bool try_use_demo_pack();
private:
Node3D *weapon_parent{ nullptr };
unsigned current{ 0 };

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,27 @@
[gd_scene load_steps=4 format=3 uid="uid://cclghy01gblif"]
[sub_resource type="SphereShape3D" id="SphereShape3D_kl827"]
radius = 0.1282442
[sub_resource type="GDScript" id="GDScript_kl827"]
script/source = "extends Interactable
func _activated(interactor: PlayerInteractor) -> void:
interactor.pickup_demo_pack()
get_owner().queue_free()
"
[sub_resource type="BoxMesh" id="BoxMesh_kl827"]
size = Vector3(0.2, 0.1, 0.2)
[node name="DemoPackPickup" type="StaticBody3D"]
collision_layer = 18
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
shape = SubResource("SphereShape3D_kl827")
[node name="Interactable" type="Interactable" parent="."]
script = SubResource("GDScript_kl827")
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
mesh = SubResource("BoxMesh_kl827")

View file

@ -1,7 +1,10 @@
[gd_scene load_steps=5 format=3 uid="uid://snjgu4yp5swd"]
[gd_scene load_steps=6 format=3 uid="uid://snjgu4yp5swd"]
[ext_resource type="PackedScene" uid="uid://ce40pq785yoyi" path="res://objects/weapons/rifle.tscn" id="1_eqqp1"]
[sub_resource type="SphereShape3D" id="SphereShape3D_eqqp1"]
radius = 0.2
[sub_resource type="GDScript" id="GDScript_eqqp1"]
script/source = "extends HealthStatus
@ -23,6 +26,12 @@ unique_name_in_owner = true
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 1.60811, 0)
fov = 60.0
[node name="PlayerInteractor" type="PlayerInteractor" parent="PlayerCamera"]
shape = SubResource("SphereShape3D_eqqp1")
target_position = Vector3(0, 0, -2)
collision_mask = 22
collide_with_areas = true
[node name="PlayerInput" type="PlayerInput" parent="."]
unique_name_in_owner = true
process_mode = 3

View file

@ -91,6 +91,7 @@ activate={
3d_physics/layer_2="Precision"
3d_physics/layer_3="Hitbox(Enemy)"
3d_physics/layer_4="Hitbox(Player)"
3d_physics/layer_5="Interactables"
[physics]