Merge branch 'feat/entities-shape-scale': collision_radius derivat del shape + scale al YAML
This commit is contained in:
@@ -3,13 +3,14 @@ ai_type: pentagon # Validat contra el directori; mapeja a EnemyType::PE
|
|||||||
|
|
||||||
shape:
|
shape:
|
||||||
path: enemy_pentagon.shp
|
path: enemy_pentagon.shp
|
||||||
|
scale: 1.0 # multiplicador visual + hitbox sobre la mida nativa del .shp
|
||||||
|
collision_factor: 1.0 # ajust opcional del hitbox (default 1.0)
|
||||||
|
|
||||||
physics:
|
physics:
|
||||||
mass: 5.0
|
mass: 5.0
|
||||||
speed: 35.0 # px/s (esquivador lent)
|
speed: 35.0 # px/s (esquivador lent)
|
||||||
rotation_delta_min: 0.75 # rad/s — rotació visual mínima
|
rotation_delta_min: 0.75 # rad/s — rotació visual mínima
|
||||||
rotation_delta_max: 3.75 # rad/s — rotació visual màxima
|
rotation_delta_max: 3.75 # rad/s — rotació visual màxima
|
||||||
collision_radius: 20.0
|
|
||||||
|
|
||||||
behavior:
|
behavior:
|
||||||
# Pentagon: zigzag esquivador (canvi de direcció probabilístic per segon).
|
# Pentagon: zigzag esquivador (canvi de direcció probabilístic per segon).
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ ai_type: pinwheel # Validat contra el directori; mapeja a EnemyType::PI
|
|||||||
|
|
||||||
shape:
|
shape:
|
||||||
path: enemy_pinwheel.shp
|
path: enemy_pinwheel.shp
|
||||||
|
scale: 1.0 # multiplicador visual + hitbox sobre la mida nativa del .shp
|
||||||
|
collision_factor: 1.0 # ajust opcional del hitbox (default 1.0)
|
||||||
|
|
||||||
physics:
|
physics:
|
||||||
mass: 4.0 # Més lleuger — àgil
|
mass: 4.0 # Més lleuger — àgil
|
||||||
speed: 50.0 # px/s (el més ràpid)
|
speed: 50.0 # px/s (el més ràpid)
|
||||||
rotation_delta_min: 3.0 # rad/s — rotació base elevada
|
rotation_delta_min: 3.0 # rad/s — rotació base elevada
|
||||||
rotation_delta_max: 6.0
|
rotation_delta_max: 6.0
|
||||||
collision_radius: 20.0
|
|
||||||
|
|
||||||
behavior:
|
behavior:
|
||||||
# Pinwheel: movement rectilíniauniforme + boost de rotació visual prop de la nau.
|
# Pinwheel: movement rectilíniauniforme + boost de rotació visual prop de la nau.
|
||||||
|
|||||||
@@ -4,15 +4,21 @@ name: player_ship
|
|||||||
# Nota: el segon jugador rep un override del shape ("ship2.shp") al ctor.
|
# Nota: el segon jugador rep un override del shape ("ship2.shp") al ctor.
|
||||||
# Quan s'introdueixin variants reals de nau, es crearà un YAML separat
|
# Quan s'introdueixin variants reals de nau, es crearà un YAML separat
|
||||||
# per cada model.
|
# per cada model.
|
||||||
|
#
|
||||||
|
# scale: multiplicador visual i de hitbox sobre la mida nativa del .shp (1.0 = mida del fitxer).
|
||||||
|
# collision_factor: ajust opcional del hitbox respecte el cercle circumscrit
|
||||||
|
# automàtic de la shape; tocar només si el feel del hitbox
|
||||||
|
# no quadra amb la silueta visual (default 1.0).
|
||||||
shape:
|
shape:
|
||||||
path: ship.shp
|
path: ship.shp
|
||||||
|
scale: 1.0
|
||||||
|
collision_factor: 1.0
|
||||||
|
|
||||||
physics:
|
physics:
|
||||||
mass: 10.0
|
mass: 10.0
|
||||||
restitution: 0.6
|
restitution: 0.6
|
||||||
linear_damping: 1.5
|
linear_damping: 1.5
|
||||||
angular_damping: 0.0
|
angular_damping: 0.0
|
||||||
collision_radius: 12.0
|
|
||||||
rotation_speed: 3.14 # rad/s (~180 deg/s, input-driven sense inercia)
|
rotation_speed: 3.14 # rad/s (~180 deg/s, input-driven sense inercia)
|
||||||
acceleration: 400.0 # px/s^2 multiplicat per la massa quan THRUST
|
acceleration: 400.0 # px/s^2 multiplicat per la massa quan THRUST
|
||||||
max_velocity: 180.0 # px/s (clamp post-integració per preservar feel arcade)
|
max_velocity: 180.0 # px/s (clamp post-integració per preservar feel arcade)
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ ai_type: square # Validat contra el directori; mapeja a EnemyType::SQ
|
|||||||
|
|
||||||
shape:
|
shape:
|
||||||
path: enemy_square.shp
|
path: enemy_square.shp
|
||||||
|
scale: 1.0 # multiplicador visual + hitbox sobre la mida nativa del .shp
|
||||||
|
collision_factor: 1.0 # ajust opcional del hitbox (default 1.0)
|
||||||
|
|
||||||
physics:
|
physics:
|
||||||
mass: 8.0 # Més pesat — "tanc"
|
mass: 8.0 # Més pesat — "tanc"
|
||||||
speed: 40.0 # px/s (velocitat mitjana)
|
speed: 40.0 # px/s (velocitat mitjana)
|
||||||
rotation_delta_min: 0.3 # rad/s — rotació lenta
|
rotation_delta_min: 0.3 # rad/s — rotació lenta
|
||||||
rotation_delta_max: 1.5
|
rotation_delta_max: 1.5
|
||||||
collision_radius: 20.0
|
|
||||||
|
|
||||||
behavior:
|
behavior:
|
||||||
# Square: tracking discret cap a la nau cada N segons.
|
# Square: tracking discret cap a la nau cada N segons.
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ namespace Graphics {
|
|||||||
|
|
||||||
// Cache and return
|
// Cache and return
|
||||||
std::cout << "[ShapeLoader] Carregat: " << normalized << " (" << shape->getName()
|
std::cout << "[ShapeLoader] Carregat: " << normalized << " (" << shape->getName()
|
||||||
<< ", " << shape->getNumPrimitives() << " primitives)" << '\n';
|
<< ", " << shape->getNumPrimitives() << " primitives, bounding_radius="
|
||||||
|
<< shape->getBoundingRadius() << ")" << '\n';
|
||||||
|
|
||||||
cache[filename] = shape;
|
cache[filename] = shape;
|
||||||
return shape;
|
return shape;
|
||||||
|
|||||||
@@ -55,22 +55,24 @@ void Enemy::init(EnemyType type, const Vec2* ship_pos) {
|
|||||||
config_ = &EnemyRegistry::get(type);
|
config_ = &EnemyRegistry::get(type);
|
||||||
const EnemyConfig& cfg = *config_;
|
const EnemyConfig& cfg = *config_;
|
||||||
|
|
||||||
collision_radius_ = cfg.physics.collision_radius;
|
|
||||||
|
|
||||||
// Cas Square: resetejar tracking timer al spawn.
|
// Cas Square: resetejar tracking timer al spawn.
|
||||||
if (type_ == EnemyType::SQUARE) {
|
if (type_ == EnemyType::SQUARE) {
|
||||||
tracking_timer_ = 0.0F;
|
tracking_timer_ = 0.0F;
|
||||||
tracking_strength_ = cfg.behavior.tracking_strength;
|
tracking_strength_ = cfg.behavior.tracking_strength;
|
||||||
}
|
}
|
||||||
|
|
||||||
body_.setMass(cfg.physics.mass);
|
|
||||||
body_.radius = collision_radius_;
|
|
||||||
|
|
||||||
shape_ = Graphics::ShapeLoader::load(cfg.shape.path);
|
shape_ = Graphics::ShapeLoader::load(cfg.shape.path);
|
||||||
if (!shape_ || !shape_->isValid()) {
|
if (!shape_ || !shape_->isValid()) {
|
||||||
std::cerr << "[Enemy] Error: no se ha podido cargar " << cfg.shape.path << '\n';
|
std::cerr << "[Enemy] Error: no se ha podido cargar " << cfg.shape.path << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Radi de col·lisió derivat del cercle circumscrit de la shape * scale * collision_factor.
|
||||||
|
const float BOUNDING = (shape_ != nullptr) ? shape_->getBoundingRadius() : 0.0F;
|
||||||
|
collision_radius_ = BOUNDING * cfg.shape.scale * cfg.shape.collision_factor;
|
||||||
|
|
||||||
|
body_.setMass(cfg.physics.mass);
|
||||||
|
body_.radius = collision_radius_;
|
||||||
|
|
||||||
// Posición aleatoria con comprobación de seguridad
|
// Posición aleatoria con comprobación de seguridad
|
||||||
float min_x;
|
float min_x;
|
||||||
float max_x;
|
float max_x;
|
||||||
@@ -188,7 +190,8 @@ void Enemy::draw() const {
|
|||||||
if (!is_active_ || !shape_) {
|
if (!is_active_ || !shape_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const float SCALE = computeCurrentScale();
|
// El SCALE final = escala base del YAML * modulador dinàmic (spawn/pulse).
|
||||||
|
const float SCALE = config_->shape.scale * computeCurrentScale();
|
||||||
SDL_Color color = config_->colors.normal;
|
SDL_Color color = config_->colors.normal;
|
||||||
|
|
||||||
// Parpadeo dorado mientras está herido.
|
// Parpadeo dorado mientras está herido.
|
||||||
|
|||||||
@@ -32,89 +32,110 @@ namespace {
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cada parseXxx valida + omple la sub-struct corresponent. Retornen false
|
||||||
|
// amb log si falta un camp requerit. Separar-los baixa la complexitat
|
||||||
|
// cognitiva del fromYaml() principal.
|
||||||
|
|
||||||
|
auto parseAiType(const fkyaml::node& node, EnemyType expected, const std::string& name, EnemyType& out) -> bool {
|
||||||
|
if (!node.contains("ai_type")) {
|
||||||
|
std::cerr << "[EnemyConfig] Error: falta 'ai_type' a " << name << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto AI_STR = node["ai_type"].get_value<std::string>();
|
||||||
|
const auto PARSED = aiTypeFromString(AI_STR);
|
||||||
|
if (!PARSED) {
|
||||||
|
std::cerr << "[EnemyConfig] Error: ai_type desconegut '" << AI_STR << "' a " << name << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (*PARSED != expected) {
|
||||||
|
std::cerr << "[EnemyConfig] Error: ai_type '" << AI_STR
|
||||||
|
<< "' no coincideix amb el tipus esperat (per directori) a " << name << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = *PARSED;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parseShape(const fkyaml::node& node, const std::string& name, EnemyConfig::ShapeCfg& out) -> bool {
|
||||||
|
if (!node.contains("shape") || !node["shape"].contains("path")) {
|
||||||
|
std::cerr << "[EnemyConfig] Error: falta 'shape.path' a " << name << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto& shape = node["shape"];
|
||||||
|
out.path = shape["path"].get_value<std::string>();
|
||||||
|
out.scale = shape.contains("scale") ? shape["scale"].get_value<float>() : 1.0F;
|
||||||
|
out.collision_factor = shape.contains("collision_factor")
|
||||||
|
? shape["collision_factor"].get_value<float>()
|
||||||
|
: 1.0F;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parsePhysics(const fkyaml::node& node, const std::string& name, EnemyConfig::PhysicsCfg& out) -> bool {
|
||||||
|
if (!node.contains("physics")) {
|
||||||
|
std::cerr << "[EnemyConfig] Error: falta 'physics' a " << name << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto& p = node["physics"];
|
||||||
|
out.mass = p["mass"].get_value<float>();
|
||||||
|
out.speed = p["speed"].get_value<float>();
|
||||||
|
out.rotation_delta_min = p["rotation_delta_min"].get_value<float>();
|
||||||
|
out.rotation_delta_max = p["rotation_delta_max"].get_value<float>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tots els camps de behavior són opcionals; només l'AI corresponent els consumeix.
|
||||||
|
void parseBehavior(const fkyaml::node& node, EnemyConfig::BehaviorCfg& out) {
|
||||||
|
if (!node.contains("behavior")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& b = node["behavior"];
|
||||||
|
const auto READ_OPT = [&b](const char* key, float& dst) {
|
||||||
|
if (b.contains(key)) {
|
||||||
|
dst = b[key].get_value<float>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
READ_OPT("zigzag_prob_per_second", out.zigzag_prob_per_second);
|
||||||
|
READ_OPT("angle_change_max", out.angle_change_max);
|
||||||
|
READ_OPT("tracking_strength", out.tracking_strength);
|
||||||
|
READ_OPT("tracking_interval", out.tracking_interval);
|
||||||
|
READ_OPT("rotation_proximity_multiplier", out.rotation_proximity_multiplier);
|
||||||
|
READ_OPT("proximity_distance", out.proximity_distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parseColors(const fkyaml::node& node, const std::string& name, EnemyConfig::ColorsCfg& out) -> bool {
|
||||||
|
if (!node.contains("colors") ||
|
||||||
|
!parseColor(node["colors"]["normal"], out.normal) ||
|
||||||
|
!parseColor(node["colors"]["wounded"], out.wounded)) {
|
||||||
|
std::cerr << "[EnemyConfig] Error: 'colors.normal' / 'colors.wounded' no són [r,g,b] a "
|
||||||
|
<< name << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parseScore(const fkyaml::node& node, const std::string& name, int& out) -> bool {
|
||||||
|
if (!node.contains("score")) {
|
||||||
|
std::cerr << "[EnemyConfig] Error: falta 'score' a " << name << '\n';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out = node["score"].get_value<int>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
auto EnemyConfig::fromYaml(const fkyaml::node& node, EnemyType expected_ai_type)
|
auto EnemyConfig::fromYaml(const fkyaml::node& node, EnemyType expected_ai_type)
|
||||||
-> std::optional<EnemyConfig> {
|
-> std::optional<EnemyConfig> {
|
||||||
try {
|
try {
|
||||||
EnemyConfig cfg;
|
EnemyConfig cfg;
|
||||||
|
|
||||||
cfg.name = node.contains("name") ? node["name"].get_value<std::string>() : "enemy";
|
cfg.name = node.contains("name") ? node["name"].get_value<std::string>() : "enemy";
|
||||||
|
|
||||||
// ai_type — validació estricta contra el tipus esperat
|
if (!parseAiType(node, expected_ai_type, cfg.name, cfg.ai_type)) { return std::nullopt; }
|
||||||
if (!node.contains("ai_type")) {
|
if (!parseShape(node, cfg.name, cfg.shape)) { return std::nullopt; }
|
||||||
std::cerr << "[EnemyConfig] Error: falta 'ai_type' a " << cfg.name << '\n';
|
if (!parsePhysics(node, cfg.name, cfg.physics)) { return std::nullopt; }
|
||||||
return std::nullopt;
|
parseBehavior(node, cfg.behavior);
|
||||||
}
|
if (!parseColors(node, cfg.name, cfg.colors)) { return std::nullopt; }
|
||||||
const auto AI_STR = node["ai_type"].get_value<std::string>();
|
if (!parseScore(node, cfg.name, cfg.score)) { return std::nullopt; }
|
||||||
const auto PARSED = aiTypeFromString(AI_STR);
|
|
||||||
if (!PARSED) {
|
|
||||||
std::cerr << "[EnemyConfig] Error: ai_type desconegut '" << AI_STR << "' a " << cfg.name << '\n';
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
if (*PARSED != expected_ai_type) {
|
|
||||||
std::cerr << "[EnemyConfig] Error: ai_type '" << AI_STR
|
|
||||||
<< "' no coincideix amb el tipus esperat (per directori) a " << cfg.name << '\n';
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
cfg.ai_type = *PARSED;
|
|
||||||
|
|
||||||
// shape
|
|
||||||
if (!node.contains("shape") || !node["shape"].contains("path")) {
|
|
||||||
std::cerr << "[EnemyConfig] Error: falta 'shape.path' a " << cfg.name << '\n';
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
cfg.shape.path = node["shape"]["path"].get_value<std::string>();
|
|
||||||
|
|
||||||
// physics
|
|
||||||
if (!node.contains("physics")) {
|
|
||||||
std::cerr << "[EnemyConfig] Error: falta 'physics' a " << cfg.name << '\n';
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
const auto& physics = node["physics"];
|
|
||||||
cfg.physics.mass = physics["mass"].get_value<float>();
|
|
||||||
cfg.physics.speed = physics["speed"].get_value<float>();
|
|
||||||
cfg.physics.rotation_delta_min = physics["rotation_delta_min"].get_value<float>();
|
|
||||||
cfg.physics.rotation_delta_max = physics["rotation_delta_max"].get_value<float>();
|
|
||||||
cfg.physics.collision_radius = physics["collision_radius"].get_value<float>();
|
|
||||||
|
|
||||||
// behavior — tots els camps són opcionals; només l'AI corresponent els consumeix.
|
|
||||||
if (node.contains("behavior")) {
|
|
||||||
const auto& b = node["behavior"];
|
|
||||||
if (b.contains("zigzag_prob_per_second")) {
|
|
||||||
cfg.behavior.zigzag_prob_per_second = b["zigzag_prob_per_second"].get_value<float>();
|
|
||||||
}
|
|
||||||
if (b.contains("angle_change_max")) {
|
|
||||||
cfg.behavior.angle_change_max = b["angle_change_max"].get_value<float>();
|
|
||||||
}
|
|
||||||
if (b.contains("tracking_strength")) {
|
|
||||||
cfg.behavior.tracking_strength = b["tracking_strength"].get_value<float>();
|
|
||||||
}
|
|
||||||
if (b.contains("tracking_interval")) {
|
|
||||||
cfg.behavior.tracking_interval = b["tracking_interval"].get_value<float>();
|
|
||||||
}
|
|
||||||
if (b.contains("rotation_proximity_multiplier")) {
|
|
||||||
cfg.behavior.rotation_proximity_multiplier = b["rotation_proximity_multiplier"].get_value<float>();
|
|
||||||
}
|
|
||||||
if (b.contains("proximity_distance")) {
|
|
||||||
cfg.behavior.proximity_distance = b["proximity_distance"].get_value<float>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// colors
|
|
||||||
if (!node.contains("colors") ||
|
|
||||||
!parseColor(node["colors"]["normal"], cfg.colors.normal) ||
|
|
||||||
!parseColor(node["colors"]["wounded"], cfg.colors.wounded)) {
|
|
||||||
std::cerr << "[EnemyConfig] Error: 'colors.normal' / 'colors.wounded' no són [r,g,b] a " << cfg.name << '\n';
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
// score
|
|
||||||
if (!node.contains("score")) {
|
|
||||||
std::cerr << "[EnemyConfig] Error: falta 'score' a " << cfg.name << '\n';
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
cfg.score = node["score"].get_value<int>();
|
|
||||||
|
|
||||||
return cfg;
|
return cfg;
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
struct EnemyConfig {
|
struct EnemyConfig {
|
||||||
struct ShapeCfg {
|
struct ShapeCfg {
|
||||||
std::string path;
|
std::string path;
|
||||||
|
float scale; // multiplicador visual + hitbox sobre la mida nativa del .shp
|
||||||
|
float collision_factor; // ajust opcional del hitbox respecte el cercle circumscrit (default 1.0)
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PhysicsCfg {
|
struct PhysicsCfg {
|
||||||
@@ -25,7 +27,6 @@ struct EnemyConfig {
|
|||||||
float speed;
|
float speed;
|
||||||
float rotation_delta_min;
|
float rotation_delta_min;
|
||||||
float rotation_delta_max;
|
float rotation_delta_max;
|
||||||
float collision_radius;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Camps específics de cada AI. Els no aplicables a un tipus queden a 0.0F
|
// Camps específics de cada AI. Els no aplicables a un tipus queden a 0.0F
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
#include <exception>
|
#include <exception>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -41,7 +40,12 @@ auto PlayerConfig::fromYaml(const fkyaml::node& node) -> std::optional<PlayerCon
|
|||||||
std::cerr << "[PlayerConfig] Error: falta 'shape.path'" << '\n';
|
std::cerr << "[PlayerConfig] Error: falta 'shape.path'" << '\n';
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
cfg.shape.path = node["shape"]["path"].get_value<std::string>();
|
const auto& shape = node["shape"];
|
||||||
|
cfg.shape.path = shape["path"].get_value<std::string>();
|
||||||
|
cfg.shape.scale = shape.contains("scale") ? shape["scale"].get_value<float>() : 1.0F;
|
||||||
|
cfg.shape.collision_factor = shape.contains("collision_factor")
|
||||||
|
? shape["collision_factor"].get_value<float>()
|
||||||
|
: 1.0F;
|
||||||
|
|
||||||
// physics
|
// physics
|
||||||
if (!node.contains("physics")) {
|
if (!node.contains("physics")) {
|
||||||
@@ -53,7 +57,6 @@ auto PlayerConfig::fromYaml(const fkyaml::node& node) -> std::optional<PlayerCon
|
|||||||
cfg.physics.restitution = physics["restitution"].get_value<float>();
|
cfg.physics.restitution = physics["restitution"].get_value<float>();
|
||||||
cfg.physics.linear_damping = physics["linear_damping"].get_value<float>();
|
cfg.physics.linear_damping = physics["linear_damping"].get_value<float>();
|
||||||
cfg.physics.angular_damping = physics["angular_damping"].get_value<float>();
|
cfg.physics.angular_damping = physics["angular_damping"].get_value<float>();
|
||||||
cfg.physics.collision_radius = physics["collision_radius"].get_value<float>();
|
|
||||||
cfg.physics.rotation_speed = physics["rotation_speed"].get_value<float>();
|
cfg.physics.rotation_speed = physics["rotation_speed"].get_value<float>();
|
||||||
cfg.physics.acceleration = physics["acceleration"].get_value<float>();
|
cfg.physics.acceleration = physics["acceleration"].get_value<float>();
|
||||||
cfg.physics.max_velocity = physics["max_velocity"].get_value<float>();
|
cfg.physics.max_velocity = physics["max_velocity"].get_value<float>();
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
struct PlayerConfig {
|
struct PlayerConfig {
|
||||||
struct ShapeCfg {
|
struct ShapeCfg {
|
||||||
std::string path;
|
std::string path;
|
||||||
|
float scale; // multiplicador visual + hitbox sobre la mida nativa del .shp
|
||||||
|
float collision_factor; // ajust opcional del hitbox respecte el cercle circumscrit (default 1.0)
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PhysicsCfg {
|
struct PhysicsCfg {
|
||||||
@@ -24,7 +26,6 @@ struct PlayerConfig {
|
|||||||
float restitution;
|
float restitution;
|
||||||
float linear_damping;
|
float linear_damping;
|
||||||
float angular_damping;
|
float angular_damping;
|
||||||
float collision_radius;
|
|
||||||
float rotation_speed; // rad/s
|
float rotation_speed; // rad/s
|
||||||
float acceleration; // px/s^2 multiplicat per la massa
|
float acceleration; // px/s^2 multiplicat per la massa
|
||||||
float max_velocity; // px/s (clamp post-integració)
|
float max_velocity; // px/s (clamp post-integració)
|
||||||
|
|||||||
@@ -26,18 +26,22 @@ Ship::Ship(Rendering::Renderer* renderer, PlayerConfig config, const char* shape
|
|||||||
config_(std::move(config)) {
|
config_(std::move(config)) {
|
||||||
brightness_ = Defaults::Brightness::NAU;
|
brightness_ = Defaults::Brightness::NAU;
|
||||||
|
|
||||||
body_.setMass(config_.physics.mass);
|
|
||||||
body_.radius = config_.physics.collision_radius;
|
|
||||||
body_.restitution = config_.physics.restitution;
|
|
||||||
body_.linear_damping = config_.physics.linear_damping;
|
|
||||||
body_.angular_damping = config_.physics.angular_damping;
|
|
||||||
|
|
||||||
// El shape pot venir del YAML o ser overridden (ex: P2 amb "ship2.shp").
|
// El shape pot venir del YAML o ser overridden (ex: P2 amb "ship2.shp").
|
||||||
const std::string SHAPE_PATH = (shape_override != nullptr) ? shape_override : config_.shape.path;
|
const std::string SHAPE_PATH = (shape_override != nullptr) ? shape_override : config_.shape.path;
|
||||||
shape_ = Graphics::ShapeLoader::load(SHAPE_PATH);
|
shape_ = Graphics::ShapeLoader::load(SHAPE_PATH);
|
||||||
if (!shape_ || !shape_->isValid()) {
|
if (!shape_ || !shape_->isValid()) {
|
||||||
std::cerr << "[Ship] Error: no se ha podido cargar " << SHAPE_PATH << '\n';
|
std::cerr << "[Ship] Error: no se ha podido cargar " << SHAPE_PATH << '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Radi de col·lisió derivat del cercle circumscrit de la shape * scale * collision_factor.
|
||||||
|
const float BOUNDING = (shape_ != nullptr) ? shape_->getBoundingRadius() : 0.0F;
|
||||||
|
collision_radius_ = BOUNDING * config_.shape.scale * config_.shape.collision_factor;
|
||||||
|
|
||||||
|
body_.setMass(config_.physics.mass);
|
||||||
|
body_.radius = collision_radius_;
|
||||||
|
body_.restitution = config_.physics.restitution;
|
||||||
|
body_.linear_damping = config_.physics.linear_damping;
|
||||||
|
body_.angular_damping = config_.physics.angular_damping;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Ship::init(const Vec2* spawn_point, bool activar_invulnerabilitat) {
|
void Ship::init(const Vec2* spawn_point, bool activar_invulnerabilitat) {
|
||||||
@@ -144,10 +148,11 @@ void Ship::draw() const {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Efecto visual de empuje: escala proporcional a la velocidad.
|
// Efecte visual d'empenta (modulador sobre l'escala base del YAML).
|
||||||
const float SPEED = getSpeed();
|
const float SPEED = getSpeed();
|
||||||
const float VISUAL_PUSH = SPEED / config_.visual_thrust.push_divisor;
|
const float VISUAL_PUSH = SPEED / config_.visual_thrust.push_divisor;
|
||||||
const float SCALE = 1.0F + (VISUAL_PUSH / config_.visual_thrust.scale_divisor);
|
const float THRUST_MODULATOR = 1.0F + (VISUAL_PUSH / config_.visual_thrust.scale_divisor);
|
||||||
|
const float SCALE = config_.shape.scale * THRUST_MODULATOR;
|
||||||
|
|
||||||
// Parpelleig daurat mentre està ferida: alterna color normal ↔ color hurt.
|
// Parpelleig daurat mentre està ferida: alterna color normal ↔ color hurt.
|
||||||
SDL_Color color = config_.colors.normal;
|
SDL_Color color = config_.colors.normal;
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
#include "core/defaults.hpp"
|
|
||||||
#include "core/entities/entity.hpp"
|
#include "core/entities/entity.hpp"
|
||||||
#include "core/types.hpp"
|
#include "core/types.hpp"
|
||||||
#include "game/entities/player_config.hpp"
|
#include "game/entities/player_config.hpp"
|
||||||
@@ -30,10 +29,9 @@ class Ship : public Entities::Entity {
|
|||||||
// Override: Interfaz de Entity
|
// Override: Interfaz de Entity
|
||||||
[[nodiscard]] auto isActive() const -> bool override { return !is_hit_; }
|
[[nodiscard]] auto isActive() const -> bool override { return !is_hit_; }
|
||||||
|
|
||||||
// Override: Interfaz de colisión
|
// Override: Interfaz de colisión. Derivat al ctor del bounding_radius del
|
||||||
[[nodiscard]] auto getCollisionRadius() const -> float override {
|
// shape carregat × scale × collision_factor.
|
||||||
return config_.physics.collision_radius;
|
[[nodiscard]] auto getCollisionRadius() const -> float override { return collision_radius_; }
|
||||||
}
|
|
||||||
[[nodiscard]] auto isCollidable() const -> bool override {
|
[[nodiscard]] auto isCollidable() const -> bool override {
|
||||||
return !is_hit_ && invulnerable_timer_ <= 0.0F;
|
return !is_hit_ && invulnerable_timer_ <= 0.0F;
|
||||||
}
|
}
|
||||||
@@ -73,6 +71,9 @@ class Ship : public Entities::Entity {
|
|||||||
// copy/move-assignment quan GameScene crea la nau real.
|
// copy/move-assignment quan GameScene crea la nau real.
|
||||||
PlayerConfig config_{};
|
PlayerConfig config_{};
|
||||||
|
|
||||||
|
// Radi de col·lisió derivat: shape.bounding_radius × shape.scale × shape.collision_factor.
|
||||||
|
float collision_radius_{0.0F};
|
||||||
|
|
||||||
bool is_hit_{false};
|
bool is_hit_{false};
|
||||||
float invulnerable_timer_{0.0F}; // 0.0f = vulnerable, >0.0f = invulnerable
|
float invulnerable_timer_{0.0F}; // 0.0f = vulnerable, >0.0f = invulnerable
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user