feat: implemented combat basics
This commit is contained in:
parent
fdea9b3fdc
commit
c322abdaa4
56 changed files with 2809 additions and 518 deletions
13
src/enemy.cpp
Normal file
13
src/enemy.cpp
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#include "enemy.hpp"
|
||||
|
||||
void Enemy::_bind_methods() {}
|
||||
|
||||
void Enemy::_ready() {
|
||||
this->anim_tree = this->get_node<PlayerAnimTree>("CharacterModel/AnimationTree");
|
||||
}
|
||||
|
||||
void Enemy::damage() {
|
||||
this->anim_tree->death_animation();
|
||||
this->set_collision_mask(0x0);
|
||||
this->set_collision_layer(0x0);
|
||||
}
|
||||
19
src/enemy.hpp
Normal file
19
src/enemy.hpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef ENEMY_HPP
|
||||
#define ENEMY_HPP
|
||||
|
||||
#include "damageable_entity.hpp"
|
||||
#include "player_anim_tree.hpp"
|
||||
#include <godot_cpp/classes/character_body3d.hpp>
|
||||
namespace gd = godot;
|
||||
|
||||
class Enemy : public gd::CharacterBody3D, public DamageableEntity {
|
||||
GDCLASS(Enemy, gd::CharacterBody3D);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
virtual void _ready() override;
|
||||
virtual void damage() override;
|
||||
private:
|
||||
PlayerAnimTree *anim_tree{nullptr};
|
||||
};
|
||||
|
||||
#endif // !ENEMY_HPP
|
||||
37
src/hitscan_muzzle.cpp
Normal file
37
src/hitscan_muzzle.cpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#include "hitscan_muzzle.hpp"
|
||||
#include "damageable_entity.hpp"
|
||||
#include "godot_cpp/variant/callable.hpp"
|
||||
#include "utils/godot_macros.hpp"
|
||||
#include <godot_cpp/classes/physics_direct_space_state3d.hpp>
|
||||
#include <godot_cpp/classes/physics_ray_query_parameters3d.hpp>
|
||||
#include <godot_cpp/classes/world3d.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
|
||||
void HitscanMuzzle::_bind_methods() {
|
||||
#define CLASSNAME HitscanMuzzle
|
||||
GDFUNCTION(fire); // allow fire() to be called from animation tracks
|
||||
}
|
||||
|
||||
void HitscanMuzzle::_ready() {
|
||||
this->set_enabled(false);
|
||||
this->set_physics_process(false);
|
||||
}
|
||||
|
||||
void HitscanMuzzle::_physics_process(double) {
|
||||
this->fire_physics_check();
|
||||
this->set_physics_process(false); // since _physics_process is only used for fire_physics_check, just disable it immediately
|
||||
}
|
||||
|
||||
void HitscanMuzzle::fire() {
|
||||
this->set_physics_process(true); // offload physics checks to physics process to avoid multithreading issues
|
||||
}
|
||||
|
||||
void HitscanMuzzle::fire_physics_check() {
|
||||
this->force_raycast_update(); // since we disabled automatic updating (set_enabled(false) in _ready), we'll have to force update here.
|
||||
gd::Object *hit{this->get_collider()};
|
||||
if(hit == nullptr) return; // nothing was hit
|
||||
// see if the hit object can be damaged, damage if so
|
||||
DamageableEntity *damage_iface{dynamic_cast<DamageableEntity*>(hit)};
|
||||
if(damage_iface == nullptr) return; // hit object can't be damaged.
|
||||
damage_iface->damage();
|
||||
}
|
||||
22
src/hitscan_muzzle.hpp
Normal file
22
src/hitscan_muzzle.hpp
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef HITSCAN_MUZZLE_HPP
|
||||
#define HITSCAN_MUZZLE_HPP
|
||||
|
||||
#include <godot_cpp/classes/node3d.hpp>
|
||||
#include <godot_cpp/classes/physics_body3d.hpp>
|
||||
#include <godot_cpp/classes/ray_cast3d.hpp>
|
||||
#include <godot_cpp/templates/vector.hpp>
|
||||
namespace gd = godot;
|
||||
|
||||
class HitscanMuzzle : public gd::RayCast3D {
|
||||
GDCLASS(HitscanMuzzle, gd::RayCast3D);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
virtual void _ready() override;
|
||||
virtual void _physics_process(double) override;
|
||||
|
||||
void fire(); // prep a deferred call to fire_physics_check
|
||||
private:
|
||||
void fire_physics_check();
|
||||
};
|
||||
|
||||
#endif // !HITSCAN_MUZZLE_HPP
|
||||
|
|
@ -11,13 +11,16 @@ void Player::_bind_methods() {
|
|||
void Player::_ready() {
|
||||
if(gd::Engine::get_singleton()->is_editor_hint())
|
||||
return;
|
||||
this->anim_tree = this->get_node<PlayerAnimTree>("%AnimationTree");
|
||||
// setup input callbacks
|
||||
this->input = this->get_node<utils::PlayerInput>("%PlayerInput");
|
||||
this->input->listen_to(utils::PlayerInput::Listener("dir_left", "dir_right", callable_mp(this, &Player::_on_dir_horizontal)));
|
||||
this->input->listen_to(utils::PlayerInput::Listener("dir_backward", "dir_forward", callable_mp(this, &Player::_on_dir_vertical)));
|
||||
this->input->listen_to(utils::PlayerInput::Listener("fire", callable_mp(this, &Player::_on_fire)));
|
||||
this->input->listen_to(utils::PlayerInput::Listener("run", callable_mp(this, &Player::_on_run)));
|
||||
// get components
|
||||
this->anim_tree = this->get_node<PlayerAnimTree>("CharacterModel/AnimationTree");
|
||||
this->model_node = this->get_node<gd::Node3D>("%CharacterModel");
|
||||
// setup camera
|
||||
this->camera_parent = this->get_node<gd::Node3D>("%CameraParent");
|
||||
this->camera_parent->set_global_rotation(this->get_global_rotation());
|
||||
}
|
||||
|
|
@ -25,22 +28,20 @@ void Player::_ready() {
|
|||
void Player::_process(double delta) {
|
||||
if(gd::Engine::get_singleton()->is_editor_hint())
|
||||
return;
|
||||
if(this->input_fire >= 0.0)
|
||||
this->input_fire -= delta;
|
||||
this->process_rotate(delta);
|
||||
this->process_transform_camera(delta);
|
||||
// calculate the motion based on model-space motion and global basis
|
||||
// process rotations
|
||||
this->process_rotate(delta); // global character rotation
|
||||
this->process_transform_camera(delta); // camera input rotation
|
||||
// set the global motion based on model-space motion vector
|
||||
gd::Basis const &model_basis{this->model_node->get_global_basis()};
|
||||
this->anim_tree->set_walk_speed(gd::Math::max(0.f, model_basis.get_column(2).dot(this->camera_parent->get_basis().get_column(2))));
|
||||
gd::Vector3 const local_motion{this->anim_tree->get_root_motion_position()};
|
||||
gd::Vector3 const motion {
|
||||
local_motion.x * model_basis.get_column(0) +
|
||||
local_motion.y * model_basis.get_column(1) +
|
||||
local_motion.z * model_basis.get_column(2) +
|
||||
(this->is_on_floor() ? gd::Vector3{} : gd::Vector3{0.f, -1.f, 0.f}) // add some gravity if required
|
||||
local_motion.z * model_basis.get_column(2)
|
||||
+ (this->is_on_floor() ? gd::Vector3{} : gd::Vector3{0.f, -0.05f, 0.f}) // add some gravity if required
|
||||
};
|
||||
// set velocity and move
|
||||
this->set_velocity(motion / delta);
|
||||
this->set_velocity(motion / delta); // velocity has to be in m/s, root motion is framerate-dependent. meters/second=distance/time.
|
||||
}
|
||||
|
||||
void Player::_physics_process(double delta [[maybe_unused]]) {
|
||||
|
|
@ -50,7 +51,7 @@ void Player::_physics_process(double delta [[maybe_unused]]) {
|
|||
}
|
||||
|
||||
void Player::damage() {
|
||||
|
||||
this->anim_tree->death_animation();
|
||||
}
|
||||
|
||||
void Player::process_transform_camera(double delta) {
|
||||
|
|
@ -79,8 +80,8 @@ void Player::_on_dir_horizontal(gd::Ref<gd::InputEvent>, float value) {
|
|||
|
||||
void Player::_on_dir_vertical(gd::Ref<gd::InputEvent>, float value) {
|
||||
this->input_directions.y = value;
|
||||
this->anim_tree->set_aim_weapon(value <= -0.9f);
|
||||
this->anim_tree->set_is_walking(value > 0.5f);
|
||||
this->anim_tree->set_aim_weapon(value <= AIM_INPUT_THRESHOLD);
|
||||
this->anim_tree->set_is_walking(value > WALK_INPUT_THRESHOLD);
|
||||
}
|
||||
|
||||
void Player::_on_fire(gd::Ref<gd::InputEvent> event, float) {
|
||||
|
|
|
|||
|
|
@ -32,11 +32,12 @@ private:
|
|||
utils::PlayerInput *input{nullptr};
|
||||
gd::Node3D *model_node{nullptr};
|
||||
gd::Vector2 input_directions{0.f, 0.f};
|
||||
double input_fire{0.0};
|
||||
|
||||
float const ROTATION_SPEED{1.8f};
|
||||
float const CAMERA_ROTATION_SPEED{2.f};
|
||||
float const AIMING_CAMERA_ROTATION_SPEED{1.f};
|
||||
float const AIM_INPUT_THRESHOLD{-0.9f};
|
||||
float const WALK_INPUT_THRESHOLD{0.5f};
|
||||
};
|
||||
|
||||
#endif // !TR_PLAYER_HPP
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ void PlayerAnimTree::_bind_methods() {
|
|||
}
|
||||
|
||||
void PlayerAnimTree::_ready() {
|
||||
this->parent_3d = gd::Object::cast_to<gd::Node3D>(this->get_parent());
|
||||
this->fsm = this->get("parameters/Actions/playback");
|
||||
}
|
||||
|
||||
|
|
@ -24,6 +25,12 @@ void PlayerAnimTree::_process(double delta) {
|
|||
this->update_tags(this->fsm->get_current_node());
|
||||
this->fire_weapon -= delta;
|
||||
this->running_time -= delta;
|
||||
if(this->is_dead && this->death_blend < 1.f) {
|
||||
this->death_blend = gd::Math::min(this->death_blend + float(delta * this->DEATH_BLEND_SPEED), 1.f);
|
||||
this->set("parameters/DeathBlend/blend_amount", this->death_blend);
|
||||
}
|
||||
this->parent_3d->set_quaternion(this->get_root_motion_rotation_accumulator());
|
||||
this->parent_3d->rotate_y(M_PIf);
|
||||
}
|
||||
|
||||
void PlayerAnimTree::set_target_turn_speed(float value) {
|
||||
|
|
@ -52,7 +59,7 @@ float PlayerAnimTree::get_walk_speed() const {
|
|||
}
|
||||
|
||||
void PlayerAnimTree::set_is_running() {
|
||||
this->running_time = 0.25;
|
||||
this->running_time = this->RUN_PARAM_DECAY;
|
||||
}
|
||||
|
||||
bool PlayerAnimTree::get_is_running() const {
|
||||
|
|
@ -68,7 +75,7 @@ bool PlayerAnimTree::get_aim_weapon() const {
|
|||
}
|
||||
|
||||
void PlayerAnimTree::set_fire_weapon() {
|
||||
this->fire_weapon = 0.5f;
|
||||
this->fire_weapon = this->FIRE_PARAM_DECAY;
|
||||
}
|
||||
|
||||
bool PlayerAnimTree::get_fire_weapon() {
|
||||
|
|
@ -77,6 +84,11 @@ bool PlayerAnimTree::get_fire_weapon() {
|
|||
return is_set;
|
||||
}
|
||||
|
||||
void PlayerAnimTree::death_animation() {
|
||||
this->set("parameters/DeathSeek/request", 0.f);
|
||||
this->is_dead = true;
|
||||
}
|
||||
|
||||
bool PlayerAnimTree::match_tags(Tags tags) const {
|
||||
return (this->current_tags & tags) != Tags::None;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
#define PLAYER_ANIM_TREE_HPP
|
||||
|
||||
#include "utils/godot_macros.hpp"
|
||||
#include <godot_cpp/classes/animation_tree.hpp>
|
||||
#include <godot_cpp/classes/animation_node_state_machine_playback.hpp>
|
||||
#include <godot_cpp/classes/animation_tree.hpp>
|
||||
#include <godot_cpp/classes/node3d.hpp>
|
||||
namespace gd = godot;
|
||||
|
||||
class PlayerAnimTree : public gd::AnimationTree {
|
||||
|
|
@ -32,11 +33,17 @@ public:
|
|||
void set_fire_weapon();
|
||||
bool get_fire_weapon();
|
||||
bool match_tags(Tags tags) const;
|
||||
void death_animation();
|
||||
private:
|
||||
void update_tags(gd::StringName const &anim);
|
||||
void commit_turn_speed();
|
||||
void commit_walk_speed();
|
||||
private:
|
||||
double const DEATH_BLEND_SPEED{1. / 0.3}; //!< multiplier for delta_time when blending from state machine to death animation
|
||||
double const FIRE_PARAM_DECAY{0.5}; //!< how many seconds it takes for a fire input to become invalid
|
||||
double const RUN_PARAM_DECAY{0.25}; //!< how many seconds to run every time set_is_running is called
|
||||
|
||||
gd::Node3D *parent_3d{nullptr};
|
||||
gd::Ref<gd::AnimationNodeStateMachinePlayback> fsm;
|
||||
float turn_speed{0.f};
|
||||
float target_turn_speed{0.f};
|
||||
|
|
@ -45,6 +52,8 @@ private:
|
|||
double running_time{0.0};
|
||||
bool aim_weapon{false};
|
||||
double fire_weapon{0.0};
|
||||
float death_blend{0.f};
|
||||
bool is_dead{false};
|
||||
Tags current_tags{Tags::None};
|
||||
gd::StringName last_known_anim{};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@
|
|||
#include <godot_cpp/godot.hpp>
|
||||
|
||||
#include "player.hpp"
|
||||
#include "player_anim_tree.hpp"
|
||||
#include "enemy.hpp"
|
||||
#include "hitscan_muzzle.hpp"
|
||||
|
||||
using namespace godot;
|
||||
|
||||
|
|
@ -17,6 +20,8 @@ void initialize_gdextension_types(ModuleInitializationLevel p_level)
|
|||
utils::godot_cpp_utils_register_types();
|
||||
GDREGISTER_CLASS(Player);
|
||||
GDREGISTER_CLASS(PlayerAnimTree);
|
||||
GDREGISTER_CLASS(Enemy);
|
||||
GDREGISTER_CLASS(HitscanMuzzle);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue