/********************\ | Copyright 2024, | | Ulysse Cura | \********************/ //////////////////////////////////////////// // // // Système pour le joueur, gère tout ce // // qui est necessaire pour le joueur. // // // //////////////////////////////////////////// #ifndef PLAYER_HPP #define PLAYER_HPP #include #include #include #include "AnimationSystem.hpp" #include "ECS.hpp" #include "HitboxComponent.hpp" #include "InteractableComponent.hpp" #include "SpriteComponent.hpp" #include "TransformComponent.hpp" #include "../Camera.hpp" #include "../Events.hpp" #include "../Game.hpp" #include "../TileMap.hpp" #include "../Vector2D.hpp" using std::lerp, std::abs, std::vector, std::sort; constexpr const float EPSILON = 1e-6f; class PlayerSystem : public Component { public: void init() override { m_transform = &entity->getComponent(); m_sprite = &entity->getComponent(); m_hitbox = &entity->getComponent(); m_animation = &entity->getComponent(); } void update() override { m_getInputs(); m_handleHitboxes(); m_adjustVelocity(); m_setStateAndAnimation(); m_setFlip(); m_setCamera(); m_checkInteractions(); } #ifdef DEBUG_MODE void draw() override { SDL_Rect futureHitboxR = m_hitbox->hitboxR; futureHitboxR.x += static_cast(m_transform->velocity.x * m_transform->speed); futureHitboxR.y += static_cast(m_transform->velocity.y * m_transform->speed); SDL_SetRenderDrawColor(Game::renderer, 200, 20, 18, 255); SDL_RenderDrawRect(Game::renderer, &futureHitboxR); for(auto it {Game::entityManager.getEntities().begin() + 1}; it < Game::entityManager.getEntities().end(); it++) { if(it->get()->hasComponent()) { SDL_Rect interactableR; if(it->get()->hasComponent()) { interactableR = it->get()->getComponent().hitboxR; } else { interactableR.x = static_cast(it->get()->getComponent().position.x); interactableR.y = static_cast(it->get()->getComponent().position.y); interactableR.w = it->get()->getComponent().dimension.x * it->get()->getComponent().scale; interactableR.h = it->get()->getComponent().dimension.y * it->get()->getComponent().scale; } interactableR.x -= m_interactionRange; interactableR.y -= m_interactionRange; interactableR.w += (m_interactionRange * 2); interactableR.h += (m_interactionRange * 2); SDL_SetRenderDrawColor(Game::renderer, 200, 20, 18, 255); SDL_RenderDrawRect(Game::renderer, &interactableR); } if(it->get()->hasComponent()) { SDL_Rect intersectR; if(SDL_IntersectRect(&it->get()->getComponent().hitboxR, &m_hitbox->hitboxR, &intersectR)) { SDL_SetRenderDrawColor(Game::renderer, 20, 20, 200, 255); SDL_RenderDrawRect(Game::renderer, &intersectR); } } } SDL_SetRenderDrawColor(Game::renderer, 20, 20, 18, 255); } #endif // DEBUG_MODE private: void m_getInputs() { m_transform->velocity.x = Game::events.keys[SDL_SCANCODE_A] * -1 + Game::events.keys[SDL_SCANCODE_D] * 1; m_transform->velocity.y = Game::events.keys[SDL_SCANCODE_W] * -1 + Game::events.keys[SDL_SCANCODE_S] * 1; } void m_handleHitboxes() { for(int i {0}; i < 2; i++) { SDL_Rect futureHitboxR = m_hitbox->hitboxR; if(i == 0) futureHitboxR.x += static_cast(m_transform->velocity.x * m_transform->speed); if(i == 1) futureHitboxR.y += static_cast(m_transform->velocity.y * m_transform->speed); SDL_Rect hitboxTilesR; hitboxTilesR.x = futureHitboxR.x / (TILE_SIZE * TILEMAP_SCALE); hitboxTilesR.y = futureHitboxR.y / (TILE_SIZE * TILEMAP_SCALE); hitboxTilesR.w = (futureHitboxR.w + futureHitboxR.x) / (TILE_SIZE * TILEMAP_SCALE); hitboxTilesR.h = (futureHitboxR.h + futureHitboxR.y) / (TILE_SIZE * TILEMAP_SCALE); vector touchedHitboxes; for(int tileY = hitboxTilesR.y; tileY <= hitboxTilesR.h; tileY++) { for(int tileX = hitboxTilesR.x; tileX <= hitboxTilesR.w; tileX++) { touchedHitboxes.emplace_back(Game::tileMap.hitboxes[tileX + tileY * Game::tileMap.tileMapWidth]); } } sort(touchedHitboxes.begin(), touchedHitboxes.end()); for(const auto &touchedHitbox : touchedHitboxes) { if(touchedHitbox != 0) { if(touchedHitbox == 1) { if(i == 0) m_transform->velocity.x = 0; if(i == 1) m_transform->velocity.y = 0; break; } else { Game::tileMap.LoadNextMap(touchedHitbox); m_transform->position = Game::tileMap.getPlayerInitPos(); Game::camera.centerPos.x = static_cast(m_transform->position.x + static_cast((m_transform->dimension.x * m_transform->scale) >> 1)); Game::camera.centerPos.y = static_cast(m_transform->position.y + static_cast((m_transform->dimension.y * m_transform->scale) >> 1)); playerState = State::None; break; } } } for(auto it {Game::entityManager.getEntities().begin() + 1}; it < Game::entityManager.getEntities().end(); it++) { if(it->get()->hasComponent()) { if(it->get()->getComponent().hitboxActivated) { SDL_Rect &entityHitboxR {it->get()->getComponent().hitboxR}; SDL_Rect intersectR; if(SDL_IntersectRect(&m_hitbox->hitboxR, &entityHitboxR, &intersectR)) { Vector2D overlap; overlap.x = intersectR.w; overlap.y = intersectR.h; if(overlap.x < overlap.y) { int leftDistance = abs(entityHitboxR.x + entityHitboxR.w / 2 - m_hitbox->hitboxR.x); int rightDistance = abs(entityHitboxR.x + entityHitboxR.w / 2 - (m_hitbox->hitboxR.x + m_hitbox->hitboxR.w)); if(leftDistance > rightDistance) { m_transform->position.x -= m_transform->speed; } else { m_transform->position.x += m_transform->speed; } } else { int upDistance = abs(entityHitboxR.y + entityHitboxR.h / 2 - m_hitbox->hitboxR.y); int downDistance = abs(entityHitboxR.y + entityHitboxR.h / 2 - (m_hitbox->hitboxR.y + m_hitbox->hitboxR.h)); if(upDistance > downDistance) { m_transform->position.y -= m_transform->speed; } else { m_transform->position.y += m_transform->speed; } } break; } if(SDL_HasIntersection(&futureHitboxR, &entityHitboxR)) { if(i == 0) m_transform->velocity.x = 0; if(i == 1) m_transform->velocity.y = 0; break; } } } } } } void m_adjustVelocity() { if(isNotZero(m_transform->velocity.x) && isNotZero(m_transform->velocity.y)) { m_transform->velocity.x *= 0.707106781186548f; m_transform->velocity.y *= 0.707106781186548f; } } void m_setStateAndAnimation() { if((isNotZero(m_transform->velocity.x) || isNotZero(m_transform->velocity.y)) && playerState != State::Running) { playerState = State::Running; m_animation->changeAnimation("ressources/heroes/knight/run-sheet.png", 6, 10); } else if(!(isNotZero(m_transform->velocity.x) || isNotZero(m_transform->velocity.y)) && playerState != State::Idle) { playerState = State::Idle; m_animation->changeAnimation("ressources/heroes/knight/idle-sheet.png", 4, 15); } } void m_setFlip() { if(m_transform->velocity.x < 0.0f) m_sprite->flipSprite = true; if(m_transform->velocity.x > 0.0f) m_sprite->flipSprite = false; } void m_setCamera() { Vector2D target; target.x = m_transform->position.x + static_cast((m_transform->dimension.x * m_transform->scale) >> 2); target.y = m_transform->position.y + static_cast((m_transform->dimension.y * m_transform->scale) >> 2); float smoothingFactor {0.08f}; if(playerState == State::Running) smoothingFactor = 0.12f; Game::camera.centerPos.x = static_cast(lerp(Game::camera.centerPos.x, target.x, smoothingFactor)); Game::camera.centerPos.y = static_cast(lerp(Game::camera.centerPos.y, target.y, smoothingFactor)); } void m_checkInteractions() { if(Game::events.keys[SDL_SCANCODE_E]) { for(auto it {Game::entityManager.getEntities().begin() + 1}; it < Game::entityManager.getEntities().end(); it++) { if(it->get()->hasComponent()) { SDL_Rect interactableR; if(it->get()->hasComponent()) { interactableR = it->get()->getComponent().hitboxR; } else { interactableR.x = static_cast(it->get()->getComponent().position.x); interactableR.y = static_cast(it->get()->getComponent().position.y); interactableR.w = it->get()->getComponent().dimension.x * it->get()->getComponent().scale; interactableR.h = it->get()->getComponent().dimension.y * it->get()->getComponent().scale; } if(m_isInRange(interactableR)) { it->get()->getComponent().interact(); } } } } } bool m_isInRange(const SDL_Rect &entityR) { SDL_Rect intersectR {entityR}; intersectR.x -= m_interactionRange; intersectR.y -= m_interactionRange; intersectR.w += (m_interactionRange * 2); intersectR.h += (m_interactionRange * 2); return SDL_HasIntersection(&m_hitbox->hitboxR, &intersectR); } inline bool isNotZero(float value) { return abs(value) > EPSILON; } enum class State { None, Idle, Running }; TransformComponent *m_transform; SpriteComponent *m_sprite; HitboxComponent *m_hitbox; AnimationSystem *m_animation; int m_interactionRange {10}; State playerState {State::None}; }; #endif // PLAYER_HPP