Implemented rotation for sprites, among other things

This commit is contained in:
Maximilian Giller 2023-07-01 20:06:53 +02:00
parent 24963b2d0a
commit e8319fd6b9
20 changed files with 301 additions and 76 deletions

View file

@ -137,6 +137,9 @@ set(COORDINATES_00_SOURCES
src/coordinates/translated_coordinates.h
src/coordinates/translated_coordinates.cpp)
set(MINIMAP_00_SOURCES
src/prototypes/minimap_00.cpp)
# Add an executable target
add_executable(Holesome ${SOURCES})
add_executable(Physics_00 ${PHYSICS_00_SOURCES})
@ -145,6 +148,8 @@ add_executable(Math_00 ${MATH_00_SOURCES})
add_executable(Coordinates_00 ${COORDINATES_00_SOURCES})
add_executable(Minimap_00 ${MINIMAP_00_SOURCES})
# Link SFML and other libraries to your executable target
target_link_libraries(Holesome sfml-graphics sfml-audio)
target_link_libraries(Holesome Eigen3::Eigen)
@ -156,6 +161,8 @@ target_link_libraries(Math_00 Eigen3::Eigen)
target_link_libraries(Coordinates_00 Eigen3::Eigen)
target_link_libraries(Minimap_00 sfml-graphics sfml-audio)
# Assets
add_custom_target(copy_assets
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/assets ${CMAKE_CURRENT_BINARY_DIR}/assets

View file

@ -13,7 +13,7 @@
#define PLAYER_RADIUS_PER_LEVEL 0.25f
// World
#define WORLD_GRAVITY b2Vec2(0.f, 0.f)
#define WORLD_GRAVITY b2Vec2(0.f, -9.8f)
#define SKY_HEIGHT_SCALE 2.f
// FPS

View file

@ -101,3 +101,11 @@ TranslatedCoordinates::TranslatedCoordinates(DiagonalWorldCoordinates diagonalWo
{
setDiagonal(diagonalWorldCoordinates);
}
void TranslatedCoordinates::removeParent()
{
// Uncomment, if global position should be preserved
// auto globalWorldCoordinates = this->world();
// this->worldCoordinates = globalWorldCoordinates;
this->parent = nullptr;
}

View file

@ -36,6 +36,8 @@ public:
void setParent(std::shared_ptr<TranslatedCoordinates> parent, WorldCoordinates offset = {0, 0});
void removeParent();
void setWorldOffset(WorldCoordinates offset);
void setScreenOffset(IsometricCoordinates offset);

View file

@ -1,6 +1,7 @@
#include "collectable.hpp"
#include "../../sprites/versatile_sprite.hpp"
#include "../../config.h"
#include "../input/input_mapper.h"
Collectable::Collectable()
{
@ -10,7 +11,9 @@ Collectable::Collectable()
void Collectable::setRotation(float angle)
{
// Assume that the sprite is the first child
auto sprite = std::dynamic_pointer_cast<VersatileSprite>(getChildren()[0]);
sprite->setRotation(angle);
}
float Collectable::getDepth() const
@ -29,3 +32,28 @@ void Collectable::setSprite(const std::string &spriteName)
// Set half size offset of coordinates
coordinates->move(IsometricCoordinates(0, -sizeWidth / 2.f, 0));
}
void Collectable::update()
{
GameObject::update();
// TODO: Remove this
if (InputMapper::getInstance()->getInputIdentities().empty())
{
return;
}
std::shared_ptr<InputIdentity> inputIdentity = InputMapper::getInstance()->getInputIdentity(
InputDeviceGroup::KEYBOARD_WASD);
// Rotate based on grow/shrink input
if (inputIdentity->isPerformingAction(GameAction::GROW))
{
angle += 10.f;
} else if (inputIdentity->isPerformingAction(GameAction::SHRINK))
{
angle -= 10.f;
}
setRotation(angle);
}

View file

@ -13,16 +13,20 @@ public:
void setRotation(float angle);
void update() override;
float getDepth() const;
int getId() const
{
return collectableId;
}
private:
int collectableId = 0;
static inline int collectableCount = 0;
float angle = 0;
};

View file

@ -30,17 +30,18 @@ void CollectablesCollection::createEmpty(int maxDepth)
void CollectablesCollection::remove(const std::shared_ptr<Collectable> &collectable)
{
depthCollections[collectable->getDepth()]->remove(collectable);
removeChild(collectable);
}
void CollectablesCollection::add(const std::shared_ptr<Collectable> &collectable)
{
depthCollections[collectable->getDepth()]->add(collectable);
addDetachedChild(collectable);
}
void CollectablesCollection::update()
{
updateCollectables();
GameObject::update();
// Move collectables to new depth collections if necessary
@ -57,9 +58,12 @@ void CollectablesCollection::update()
for (auto &collectable: collectables)
{
int newDepth = std::floor(collectable->getDepth());
if (newDepth != depth)
if (newDepth == depth)
{
if (newDepth < 0 || newDepth >= (int) depthCollections.size())
continue;
}
if (!isValidDepth(newDepth))
{
LOG(ERROR) << "Collectable " << collectable->getId() << " has invalid depth " << newDepth
<< "! Keeping it at previous depth " << depth << " ...";
@ -71,7 +75,9 @@ void CollectablesCollection::update()
}
}
}
}
bool CollectablesCollection::isValidDepth(int desiredDepth) const
{ return desiredDepth >= 0 && desiredDepth < (int) depthCollections.size(); }
void CollectablesCollection::draw(sf::RenderWindow *window)
{
@ -86,25 +92,3 @@ void CollectablesCollection::draw(sf::RenderWindow *window)
}
}
}
void CollectablesCollection::updateCollectables()
{
for (auto &[depth, depthCollection]: depthCollections)
{
for (auto &collectable: depthCollection->collectables)
{
collectable->update();
}
}
}
std::shared_ptr<CollectablesDepthCollection> CollectablesCollection::getDepthCollection(int depth)
{
if (depthCollections.find(depth) == depthCollections.end())
{
LOG(ERROR) << "Depth collection for depth " << depth << " does not exist! Returning empty collection ...";
return std::make_shared<CollectablesDepthCollection>(depth);
}
return depthCollections[depth];
}

View file

@ -23,14 +23,12 @@ public:
void add(const std::shared_ptr<Collectable>& collectable);
void remove(const std::shared_ptr<Collectable>& collectable);
std::shared_ptr<CollectablesDepthCollection> getDepthCollection(int depth);
private:
static inline std::shared_ptr<CollectablesCollection> singletonInstance = nullptr;
std::map<int, std::shared_ptr<CollectablesDepthCollection>> depthCollections = {};
void updateCollectables();
bool isValidDepth(int desiredDepth) const;
};

View file

@ -99,6 +99,14 @@ void Game::update()
}
InputMapper::getInstance()->updateIdentities();
for (const auto &gameObject: gameObjects)
{
if (gameObject->getActive())
{
gameObject->preRenderUpdate();
}
}
}
std::shared_ptr<Game> Game::getInstance()

View file

@ -64,6 +64,7 @@ void GameObject::removeChild(const std::shared_ptr<GameObject> &child)
if (it != children.end())
{
children.erase(it);
child->coordinates->removeParent();
}
}
@ -84,3 +85,11 @@ void GameObject::addDetachedChild(const std::shared_ptr<GameObject> &child)
{
children.push_back(child);
}
void GameObject::preRenderUpdate()
{
for (auto &child: children)
{
child->preRenderUpdate();
}
}

View file

@ -20,6 +20,8 @@ public:
virtual void physicsUpdate();
virtual void preRenderUpdate();
void setActive(bool active);
bool getActive() const { return isActive; }

View file

@ -20,7 +20,7 @@ void CollectableSimulation::physicsUpdate()
{
updateGroundHole();
// world->Step(FRAME_TIME.asSeconds(), COLLECTABLES_SIM_VELOCITY_ITERATIONS, COLLECTABLES_SIM_POSITION_ITERATIONS);
world->Step(FRAME_TIME.asSeconds(), COLLECTABLES_SIM_VELOCITY_ITERATIONS, COLLECTABLES_SIM_POSITION_ITERATIONS);
updateCollectable();
}

View file

@ -0,0 +1,72 @@
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Window/Event.hpp>
#include <SFML/Graphics/CircleShape.hpp>
#include <SFML/Graphics/RectangleShape.hpp>
void drawMainContent(sf::RenderWindow *window)
{
sf::RectangleShape rectangle(sf::Vector2f(800, 600));
rectangle.setFillColor(sf::Color::Green);
rectangle.setPosition(0, 0);
window->draw(rectangle);
sf::CircleShape circle(50);
circle.setFillColor(sf::Color::Red);
circle.setPosition(150, 150);
window->draw(circle);
}
void drawMiniMapContent(sf::RenderWindow *window)
{
sf::RectangleShape rectangle(sf::Vector2f(196, 196));
rectangle.setFillColor(sf::Color::White);
rectangle.setPosition(2, 2);
rectangle.setOutlineColor(sf::Color::Black);
rectangle.setOutlineThickness(2);
window->draw(rectangle);
sf::CircleShape circle(20);
circle.setFillColor(sf::Color::Blue);
circle.setPosition(80, 80);
window->draw(circle);
}
int main(int argc, char *argv[])
{
// Create simple window
sf::RenderWindow window(sf::VideoMode(800, 600), "Minimap example");
// Create minimap view
sf::View minimapView(sf::FloatRect(0, 0, 200, 200));
// Set viewport to the top right corner
minimapView.setViewport(sf::FloatRect(0.75f, 0, 0.25f, 0.25f));
while (window.isOpen()) {
window.clear(sf::Color::Black);
// Draw main content
auto fullView = window.getDefaultView();
window.setView(fullView);
drawMainContent(&window);
// Draw minimap
window.setView(minimapView);
drawMiniMapContent(&window);
// Finished
window.display();
// Handle close event
sf::Event event{};
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
window.close();
break;
}
}
}
}

View file

@ -4,7 +4,12 @@
AnimatedSprite::AnimatedSprite(const std::vector<sf::Sprite> &sprites, const sf::Vector2f &size)
{
this->sprites = sprites;
// Create a copy of the sprites as pointers
for (auto &sprite: sprites)
{
this->sprites.push_back(std::make_shared<sf::Sprite>(sprite));
}
setSize(size);
}
@ -25,27 +30,50 @@ void AnimatedSprite::update()
void AnimatedSprite::draw(sf::RenderWindow *window)
{
auto currentSprite = sprites[currentFrame];
currentSprite.setPosition(coordinates->isometric().toScreen());
currentSprite->setPosition(renderPosition);
window->draw(currentSprite);
window->draw(*currentSprite);
}
void AnimatedSprite::setSize(const sf::Vector2f &size)
{
for (auto &sprite: sprites)
{
sprite.setScale(size.x / sprite.getTextureRect().width, size.y / sprite.getTextureRect().height);
sprite->setScale(size.x / sprite->getTextureRect().width, size.y / sprite->getTextureRect().height);
}
}
sf::Vector2f AnimatedSprite::getSize() const
{
auto scale = sprites[currentFrame].getScale();
auto textureSize = sprites[currentFrame].getTextureRect();
auto scale = sprites[currentFrame]->getScale();
auto textureSize = sprites[currentFrame]->getTextureRect();
return {textureSize.width * scale.x, textureSize.height * scale.y};
}
sf::Sprite AnimatedSprite::getSprite() const
{
return sprites[currentFrame];
return *sprites[currentFrame];
}
void AnimatedSprite::setRotation(float angle)
{
this->angle = restrictAngle(angle);
for (auto &sprite: sprites)
{
sprite->setRotation(angle);
}
}
void AnimatedSprite::preRenderUpdate()
{
if (angle == 0)
{
renderPosition = coordinates->isometric().toScreen();
return;
}
auto position = coordinates->isometric().toScreen();
renderPosition = calculateRotatedCornerPosition(position, angle);
}

View file

@ -23,10 +23,17 @@ public:
sf::Sprite getSprite() const override;
void preRenderUpdate() override;
void setRotation(float angle) override;
private:
int currentFrame = 0;
sf::Time timeSinceLastFrame = sf::Time::Zero;
std::vector<sf::Sprite> sprites;
std::vector<std::shared_ptr<sf::Sprite>> sprites;
sf::Vector2f renderPosition;
float angle = 0;
};

View file

@ -17,7 +17,7 @@ SingleSprite::SingleSprite(const std::shared_ptr<sf::Texture> &texture, const sf
void SingleSprite::draw(sf::RenderWindow *window)
{
sprite.setPosition(coordinates->isometric().toScreen());
sprite.setPosition(renderPosition);
window->draw(sprite);
}
@ -42,3 +42,22 @@ sf::Sprite SingleSprite::getSprite() const
{
return sprite;
}
void SingleSprite::setRotation(float angle)
{
this->angle = restrictAngle(angle);
sprite.setRotation(angle);
}
void SingleSprite::preRenderUpdate()
{
if (angle == 0)
{
renderPosition = coordinates->isometric().toScreen();
return;
}
auto position = coordinates->isometric().toScreen();
renderPosition = calculateRotatedCornerPosition(position, angle);
}

View file

@ -9,9 +9,9 @@
class SingleSprite : public GameObject, public Sprite
{
public:
SingleSprite(const sf::Sprite &sprite, const sf::Vector2f &size = sf::Vector2f(0, 0));
explicit SingleSprite(const sf::Sprite &sprite, const sf::Vector2f &size = sf::Vector2f(0, 0));
SingleSprite(const std::shared_ptr<sf::Texture> &texture, const sf::Vector2f &size = sf::Vector2f(0, 0));
explicit SingleSprite(const std::shared_ptr<sf::Texture> &texture, const sf::Vector2f &size = sf::Vector2f(0, 0));
void draw(sf::RenderWindow *window) override;
@ -21,8 +21,14 @@ public:
sf::Sprite getSprite() const override;
void setRotation(float angle) override;
void preRenderUpdate() override;
private:
sf::Sprite sprite;
float angle = 0;
sf::Vector2f renderPosition;
};

View file

@ -5,6 +5,7 @@
#include <vector>
#include <memory>
#include <SFML/Graphics/Sprite.hpp>
#include <cmath>
class Sprite
{
@ -23,6 +24,52 @@ public:
{
throw std::runtime_error("Sprite::getSprite() not implemented");
}
/**
* Rotates a sprite around its center.
* @param angle [0, 360]
*/
virtual void setRotation(float angle)
{
throw std::runtime_error("Sprite::setRotation() not implemented");
}
protected:
static float restrictAngle(float angle)
{
float const MAX_ANGLE = 360.f;
angle = std::fmod(angle, MAX_ANGLE); // Get the remainder after dividing by 360
if (angle < 0.0f)
{
float numberOfTimes = std::ceil(std::abs(angle) / MAX_ANGLE);
angle += MAX_ANGLE * numberOfTimes; // Convert negative angles to positive
}
return angle;
}
[[nodiscard]] sf::Vector2f
calculateRotatedCornerPosition(sf::Vector2f spriteScreenPosition, float angle) const
{
angle = restrictAngle(angle);
auto spriteSize = getSize();
// Calculate the center point of the sprite
sf::Vector2f center(spriteScreenPosition.x + spriteSize.x / 2, spriteScreenPosition.y + spriteSize.y / 2);
// Convert the angle to radians
float angle_rad = angle * (M_PI / 180.0f);
// Calculate the rotated coordinates of the top left corner
float new_x = center.x + (spriteScreenPosition.x - center.x) * std::cos(angle_rad) -
(spriteScreenPosition.y - center.y) * std::sin(angle_rad);
float new_y = center.y + (spriteScreenPosition.x - center.x) * std::sin(angle_rad) +
(spriteScreenPosition.y - center.y) * std::cos(angle_rad);
return {new_x, new_y};
}
};
#endif //HOLESOME_SPRITE_HPP

View file

@ -22,42 +22,34 @@ VersatileSprite::VersatileSprite(const std::string &name, sf::Vector2f size)
void VersatileSprite::setSize(const sf::Vector2f &size)
{
if (singleSprite != nullptr)
{
singleSprite->setSize(size);
} else if (animatedSprite != nullptr)
{
animatedSprite->setSize(size);
} else
{
Sprite::setSize(size);
}
getUsedSpritePtr()->setSize(size);
}
sf::Vector2f VersatileSprite::getSize() const
{
if (singleSprite != nullptr)
{
return singleSprite->getSize();
} else if (animatedSprite != nullptr)
{
return animatedSprite->getSize();
} else
{
return Sprite::getSize();
}
return getUsedSpritePtr()->getSize();
}
sf::Sprite VersatileSprite::getSprite() const
{
return getUsedSpritePtr()->getSprite();
}
void VersatileSprite::setRotation(float angle)
{
getUsedSpritePtr()->setRotation(angle);
}
std::shared_ptr<Sprite> VersatileSprite::getUsedSpritePtr() const
{
if (singleSprite != nullptr)
{
return singleSprite->getSprite();
return singleSprite;
} else if (animatedSprite != nullptr)
{
return animatedSprite->getSprite();
return animatedSprite;
} else
{
return {};
throw std::runtime_error("Versatile sprite has no sprite");
}
}

View file

@ -18,11 +18,15 @@ public:
[[nodiscard]] sf::Vector2f getSize() const override;
sf::Sprite getSprite() const override;
[[nodiscard]] sf::Sprite getSprite() const override;
void setRotation(float angle) override;
private:
std::shared_ptr<SingleSprite> singleSprite = nullptr;
std::shared_ptr<AnimatedSprite> animatedSprite = nullptr;
std::shared_ptr<Sprite> getUsedSpritePtr() const;
};