Compare commits

..

1 commit

Author SHA1 Message Date
075cb5977e Initial ideas 2023-06-02 20:24:04 +02:00
161 changed files with 479 additions and 6406 deletions

1
.gitignore vendored
View file

@ -1,3 +1,2 @@
/build/
/build-release/
/.idea/

View file

@ -11,9 +11,9 @@ include_directories(${SFML_INCLUDE_DIR})
find_package(Eigen3 3.3 REQUIRED NO_MODULE)
include_directories(${EIGEN3_INCLUDE_DIR})
# Include Box2D
find_package(box2d REQUIRED)
include_directories(${BOX2D_INCLUDE_DIR})
# Find and include Box2D
#find_package(Box2D REQUIRED)
# Set up your project's source files
set(SOURCES
@ -29,6 +29,8 @@ set(SOURCES
src/coordinates/translated_coordinates.cpp
src/coordinates/translated_coordinates.h
src/coordinates/coordinates.h
src/game/input_handler.cpp
src/game/input_handler.h
src/primitives/circle_object.cpp
src/primitives/circle_object.h
src/game/game_factory.cpp
@ -42,6 +44,7 @@ set(SOURCES
src/game/input/direction.cpp
src/game/player/player.cpp
src/game/player/player.hpp
src/game/input/key_features.hpp
src/game/camera/tracking_view.cpp
src/game/camera/tracking_view.h
src/utilities/smart_list.cpp
@ -50,95 +53,12 @@ set(SOURCES
src/game/camera/ITrackable.h
src/game/input/input_identity.h
src/utilities/magic_enum.hpp
src/game/player/player_spawner.cpp
src/game/player/player_spawner.hpp
src/game/camera/tracking_area.h
src/game/camera/tracking_view_options.hpp
src/game/collectables/collectable.cpp
src/game/collectables/collectable.hpp
src/sprites/texture_manager.cpp
src/sprites/texture_manager.hpp
src/sprites/sprite_sheet.cpp
src/sprites/sprite_sheet.hpp
src/sprites/animated_sprite.cpp
src/sprites/animated_sprite.hpp
src/sprites/single_sprite.cpp
src/sprites/single_sprite.hpp
src/texture_config.h
src/sprites/configs/sprite_config.hpp
src/sprites/configs/sheet_config.hpp
src/sprites/configs/animation_config.hpp
src/sprites/versatile_sprite.cpp
src/sprites/versatile_sprite.hpp
src/sprites/sprite.hpp
src/sprites/sprite_factory.cpp
src/sprites/sprite_factory.hpp
src/input_config.h
src/game/input/button_config.hpp
src/game/input/game_action.hpp
src/game/input/button_config_factory.cpp
src/game/input/button_config_factory.hpp
src/game/input/game_action_config.hpp
src/game/input/gamepad_buttons.hpp
src/game/physics/map/map_simulation.cpp
src/game/physics/map/map_simulation.hpp
src/game/physics/map/map_player.hpp
src/game/physics/map/map_player.cpp
src/game/level/level_config.hpp
src/game/level/level_loader.cpp
src/game/level/level_loader.hpp
src/levels.hpp src/collectables.hpp
src/game/collectables/collectable_config.hpp
src/game/collectables/collection/collectables_collection.cpp
src/game/collectables/collection/collectables_collection.hpp
src/game/collectables/collection/collectables_depth_collection.cpp
src/game/collectables/collection/collectables_depth_collection.hpp
src/game/collectables/collectable_in_level.hpp
src/game/collectables/collectable_factory.cpp
src/game/collectables/collectable_factory.hpp
src/game/player/player_collection.cpp
src/game/player/player_collection.hpp
src/levels.hpp
src/sprites/tiling/tilemap.cpp
src/sprites/tiling/tilemap.hpp
src/sprites/tiling/tilemap_config.hpp
src/sprites/tiling/tileset_config.hpp
src/sprites/tiling/tileset.cpp
src/sprites/tiling/tileset.hpp
src/game/frame_counter.cpp
src/game/frame_counter.hpp
src/game/level/level_renderer.cpp
src/game/level/level_renderer.hpp
src/sprites/skymap/skymap.cpp
src/sprites/skymap/skymap.hpp
src/game/camera/multiplayer_view.cpp
src/game/camera/multiplayer_view.hpp
src/game/camera/multiplayer_borders.cpp
src/game/camera/multiplayer_borders.hpp
src/game/physics/holes/collectable_simulation.cpp
src/game/physics/holes/collectable_simulation.hpp
src/game/physics/holes/holes_simulation.cpp
src/game/physics/holes/holes_simulation.hpp
src/game/physics/holes/layouts/hole_description.hpp
src/game/physics/holes/layouts/hole_layout.cpp
src/game/physics/holes/layouts/hole_layout.hpp
src/game/physics/holes/layouts/depth_hole_layout.hpp
src/game/physics/holes/layouts/depth_hole_description.hpp
src/game/physics/holes/ground/collectable_sim_ground_segment.hpp
src/game/physics/holes/ground/collectable_sim_ground.cpp
src/game/physics/holes/ground/collectable_sim_ground.hpp
src/game/physics/body_adapter.cpp
src/game/physics/body_adapter.hpp
src/sprites/masked_sprite.cpp
src/sprites/masked_sprite.hpp
src/sprites/configs/masked_sprite_config.hpp
src/sprites/masked_sprite_hole.hpp
src/game/time/countdown.cpp
src/game/time/countdown.hpp
src/game/layer/global_layer.cpp
src/game/layer/global_layer.hpp
src/typography/font_manager.cpp
src/typography/font_manager.hpp
src/screens/winner_screen.cpp
src/screens/winner_screen.hpp src/screens/screen.cpp src/screens/screen.hpp src/screens/join_screen.cpp src/screens/join_screen.hpp src/game/level/level_generator.cpp src/game/level/level_generator.hpp src/screens/loading_screen.cpp src/screens/loading_screen.hpp)
src/game/depth_renderer.cpp
src/game/depth_renderer.hpp)
set(PHYSICS_00_SOURCES
src/prototypes/physics_00.cpp)
@ -146,42 +66,17 @@ set(PHYSICS_00_SOURCES
set(MATH_00_SOURCES
src/prototypes/math_00.cpp)
set(COORDINATES_00_SOURCES
src/prototypes/coordinates_00.cpp
src/coordinates/coordinates.h
src/coordinates/coordinate_transformer.h
src/coordinates/coordinate_transformer.cpp
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})
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)
target_link_libraries(Holesome box2d::box2d)
#target_link_libraries(Holesome Box2D::Box2D)
target_link_libraries(Physics_00 box2d::box2d)
#target_link_libraries(Physics_00 Box2D::Box2D)
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
)
add_dependencies(Holesome copy_assets)

View file

@ -8,7 +8,7 @@ Maximilian Giller,
## What is this Game about?
Holesome is about currentLayoutHoles! But not the kind you have seen before ...
Holesome is about holes! But not the kind you have seen before ...
Gameplay:
- Navigate the environment as a hole
@ -33,7 +33,7 @@ The player controls a hole and has to consume as many objects as possible to gro
## Which components are included?
- **Physics**: Core gameplay element to make the objects fall into the currentLayoutHoles in a fun way
- **Physics**: Core gameplay element to make the objects fall into the holes in a fun way
- **Controller Support**: Use a controller to control the hole
- **Local Multiplayer**: Play with up to 4 players on one device using split screen
- **Level Files**: Levels are stored using a simple file format
@ -43,7 +43,7 @@ The player controls a hole and has to consume as many objects as possible to gro
Potential expansions:
- **Multithreading**: Improving performance by running the phsics in a separat thread and similar concepts
- **AI**: Some currentLayoutHoles could be controlled by AI, which makes singleplayer games more exciting
- **AI**: Some holes could be controlled by AI, which makes singleplayer games more exciting
- **Online Multiplayer**: Play with friends online
## Project Setup
@ -68,10 +68,3 @@ In case of "RandR headers were not found": `sudo apt install xorg-dev`.
cmake --build .
cmake --build . --target INSTALL
```
## Project structure
- Physics Simulations
- **Map Simulation**: World-bounds collider
- **Holes Simulation**: Creates hole physics by managing all the individual Collectable Simulations
- **Collectable Simulation**: Simulates physics for a single collectable

27
TODO.md
View file

@ -1,28 +1,3 @@
# Holesome - ToDos
## Next
- Physics
- Damage other players on contact
## Bugs
- Player disconnect in multiplayer sometimes targeting disconnected player or something like that
## High priority
- Players-join-screen before the game starts
- Short input delay when a new identity connects, to avoid accidental inputs at beginning
- Procedural points generation
- Game over screen
- Proper player graphics
- Collectables with graphics
- In-View-Detection for rendering more efficiently
## Low priority
- Score
- Player actions like shrinking hole or throwing bomb
- Menu
- Sound
- [ ] Link box2d???

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 849 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 617 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 967 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 313 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -1,18 +0,0 @@
#ifndef HOLESOME_COLLECTABLES_HPP
#define HOLESOME_COLLECTABLES_HPP
#include <string>
#include <map>
#include "game/collectables/collectable_config.hpp"
std::map<std::string, CollectableConfig> const all_collectables = {
{"rose", CollectableConfig("rose", 1)},
{"rosebush", CollectableConfig("rosebush", 3)},
{"stone", CollectableConfig("stone", 5)},
{"bike", CollectableConfig("bike", 15)},
{"small-tree", CollectableConfig("small-tree", 20)},
{"tram", CollectableConfig("tram", 50)},
{"lantern", CollectableConfig("lantern", 10)}
};
#endif //HOLESOME_COLLECTABLES_HPP

View file

@ -2,91 +2,75 @@
#define HOLESOME_CONFIG_H
#include <SFML/Graphics.hpp>
#include <box2d/box2d.h>
#include <map>
#include "game/input/input_device_group.h"
#define DEVELOPER_MODE false
#define GAME_NAME "Holesome"
#define CLOSE_GAME_BTN sf::Keyboard::Escape
// Player
#define DEFAULT_PLAYER_SPEED 5.f // World units per second
#define DEFAULT_PLAYER_POINTS 0
#define PLAYER_MIN_RADIUS 0.5f // World units
#define PLAYER_RADIUS_PER_LEVEL 0.25f
#define DEFAULT_MAX_PLAYER_NUMBER 4
#define MIN_PLAYER_COUNT 2
#define POINT_DELTA_FOR_ATTACK 20
#define ATTACK_PER_SECOND 1.f
#define PLAYER_ALIVE_THRESHOLD (-10)
#define PLAYER_RUN_SPEED (DEFAULT_PLAYER_SPEED * 2.f)
#define PLAYER_RUN_SPEED_COST_PER_SECOND 10.f
// World
#define WORLD_GRAVITY b2Vec2(0.f, -9.8f)
#define SKY_HEIGHT_SCALE 5.f
#define CONSIDER_COLLECTABLE_DEPTH_MOVEMENT false // Might cost performance and is currently not in use
// FPS
#define FRAME_RATE 60
#define FRAME_TIME sf::Time(sf::seconds(1.0f / FRAME_RATE))
#define FRAME_LIMIT_ENABLED true
// Window settings
#define ANTIALIASINGLEVEL 8
#define KEY_REPEAT_ENABLED false
#define REFERENCE_SIZE sf::Vector2f(1920, 1080)
// Graphic settings
#define ISOMETRIC_SKEW (16.f/32.f)
#define ISOMETRIC_SKEW 0.3f
#define MOVEMENT_SKEW sf::Vector2f(1.f, 1/ISOMETRIC_SKEW/2.f)
#define WORLD_TO_ISO_SCALE 50.0f // 50.f, don't change. Rather adjust the zoom of the camera
#define MASKED_HOLE_BORDER_TRANSITION_SIZE 0.2f
#define MASKED_HOLE_DARKNESS_LIMIT 0.5f
#define COLLECTABLE_SCALE 5.f
#define WORLD_TO_ISO_SCALE 10.0f
// Tracking view defaults
#define DEF_TV_IS_ABSOLUTE_FREE_MOVE_THRESHOLD false
#define DEF_TV_FREE_MOVE_THRESHOLD 0.f
#define DEF_TV_SOFT_FOLLOW_SPEED 2.5f
#define DEF_TV_SOFT_RESIZE_SPEED 5.f
#define DEF_TV_MIN_VIEW_SIZE sf::Vector2f(6, 6) * WORLD_TO_ISO_SCALE
#define DEF_TV_MIN_VIEW_SIZE sf::Vector2f(300, 300)
#define DEF_TV_MAX_VIEW_SIZE sf::Vector2f(0, 0)
#define DEF_TV_IS_ABSOLUTE_VIEW_SIZE_PADDING false
#define DEF_TV_VIEW_SIZE_PADDING sf::Vector2f(2.f, 2.f)
#define MP_VIEW_ADD_NEW_PLAYERS true
#define MP_VIEW_BORDER_COLOR sf::Color::Black
#define DEF_TV_VIEW_SIZE_PADDING sf::Vector2f(0.5f, 0.5f)
// Simulations
#define MAPSIM_WALL_THICKNESS 3.f
#define MAPSIM_VELOCITY_ITERATIONS 6
#define MAPSIM_POSITION_ITERATIONS 2
#define COLLECTABLES_SIM_VELOCITY_ITERATIONS 8
#define COLLECTABLES_SIM_POSITION_ITERATIONS 3
#define COLLECTABLES_SIM_LINEAR_DAMPING 0.5f
#define COLLECTABLES_SIM_ANGULAR_DAMPING 0.5f
#define COLLECTABLES_SIM_DENSITY 3.f
#define COLLECTABLES_SIM_FRICTION 0.0f
#define COLLECTABLES_SIM_RESTITUTION 0.5f
#define COLLECTABLES_SIM_SLEEPING true
#define COLLECTABLES_SIM_GROUND_THICKNESS 0.1f
// Inputs
#define JOYSTICK_DEADZONE 0.1f
// Key groups
const std::map<InputDeviceGroup, std::set<sf::Keyboard::Key>> KEY_GROUPS = {
{InputDeviceGroup::KEYBOARD_WASD,
{
sf::Keyboard::W,
sf::Keyboard::A,
sf::Keyboard::S,
sf::Keyboard::D,
sf::Keyboard::Q,
sf::Keyboard::E
}
},
{InputDeviceGroup::KEYBOARD_ARROWS,
{
sf::Keyboard::Up,
sf::Keyboard::Left,
sf::Keyboard::Down,
sf::Keyboard::Right,
sf::Keyboard::RShift,
sf::Keyboard::PageDown,
sf::Keyboard::PageUp,
sf::Keyboard::RControl,
}
},
{InputDeviceGroup::KEYBOARD_IJKL,
{
sf::Keyboard::I,
sf::Keyboard::J,
sf::Keyboard::K,
sf::Keyboard::L,
sf::Keyboard::U,
sf::Keyboard::O
}
}
};
// Directions
#define DIRECTION_HARD_ACTIVATION_THRESHOLD 0.1f
// DEBUG
#define DB_CIRCLE_RADIUS 1
#define DB_WORLD_GRID_RENDER false
#define DB_TRACKING_VIEW_CENTER false
// Fonts
#define FONT_BASE_PATH "assets/fonts/"
#define DEFAULT_FONT "pixel"
#define COUNTDOWN_FONT_SIZE 50
// Name => Path
const std::map<std::string, std::string> all_fonts = {
{"pixel", "pixel.ttf"}
};
#define DB_ISOPLANE_CORNER_RADIUS 2
#endif //HOLESOME_CONFIG_H

View file

@ -1,23 +1,11 @@
#include "coordinate_transformer.h"
#include "../config.h"
#include "../logging/easylogging++.h"
#include "../utilities/vector_utils.hpp"
#include <cmath>
// Initialize matrix
const Eigen::Matrix<float, 3, 3> CoordinateTransformer::worldToIsometricMatrix =
(Eigen::Matrix<float, 3, 3>() <<
WORLD_TO_ISO_SCALE, -WORLD_TO_ISO_SCALE, 0,
-ISOMETRIC_SKEW * WORLD_TO_ISO_SCALE, -ISOMETRIC_SKEW * WORLD_TO_ISO_SCALE, -WORLD_TO_ISO_SCALE,
1, 1, 0
).finished();
// Same as above, but with SKEW = 0 and height positive
const auto rotateFactor = cos(M_PI / 4);
const Eigen::Matrix<float, 3, 3> CoordinateTransformer::worldToDiagonalMatrix =
(Eigen::Matrix<float, 3, 3>() <<
rotateFactor, -rotateFactor, 0,
0, 0, 1,
1, -1, 0,
-ISOMETRIC_SKEW, -ISOMETRIC_SKEW, -1,
1, 1, 0
).finished();
@ -26,66 +14,21 @@ IsometricCoordinates CoordinateTransformer::worldToIsometric(WorldCoordinates wo
Eigen::Vector3f worldCoordinatesVector;
worldCoordinatesVector << worldCoordinates.x, worldCoordinates.y, worldCoordinates.z;
Eigen::Vector3f isoCoordinatesVector = worldToIsometricMatrix * worldCoordinatesVector;
Eigen::Vector3f isoCoordinatesVector = worldToIsometricMatrix * worldCoordinatesVector * WORLD_TO_ISO_SCALE;
return {
return IsometricCoordinates(
isoCoordinatesVector.x(), // x
isoCoordinatesVector.y(), // y
isoCoordinatesVector.z() // depth
};
);
}
WorldCoordinates CoordinateTransformer::isometricToWorld(IsometricCoordinates isometricCoordinates)
{
Eigen::Vector3f isoCoordinatesVector;
isoCoordinatesVector << isometricCoordinates.x,
isometricCoordinates.y,
isometricCoordinates.depth;
Eigen::Vector3f isoCoordinatesVector;
isoCoordinatesVector << isometricCoordinates.x, isometricCoordinates.y, isometricCoordinates.depth;
Eigen::Vector3f worldCoordinatesVector = worldToIsometricMatrix.inverse() * isoCoordinatesVector;
return {
worldCoordinatesVector.x(), // x
worldCoordinatesVector.y(), // y
worldCoordinatesVector.z() // z
};
}
float CoordinateTransformer::worldToDepth(sf::Vector2f worldGroundCoordinates)
{
return CoordinateTransformer::worldToIsometric({worldGroundCoordinates.x, worldGroundCoordinates.y, 0}).depth;
}
sf::Vector2f CoordinateTransformer::closestWorldPointAtDepth(float depth, sf::Vector2f referenceWorldPoint)
{
float depthAtReferencePoint = worldToDepth(referenceWorldPoint);
float depthDifference = depth - depthAtReferencePoint;
float stepSizePerAxis = depthDifference / 2.f;
return referenceWorldPoint + sf::Vector2f(stepSizePerAxis, stepSizePerAxis);
}
DiagonalWorldCoordinates CoordinateTransformer::worldToDiagonal(WorldCoordinates worldCoordinates)
{
Eigen::Vector3f worldCoordinatesVector;
worldCoordinatesVector << worldCoordinates.x, worldCoordinates.y, worldCoordinates.z;
Eigen::Vector3f diagonalCoordinateVector = worldToDiagonalMatrix * worldCoordinatesVector;
return {
diagonalCoordinateVector.x(), // horizontal
diagonalCoordinateVector.y(), // vertical
diagonalCoordinateVector.z() // depth
};
}
WorldCoordinates CoordinateTransformer::diagonalToWorld(DiagonalWorldCoordinates diagonalWorldCoordinates)
{
Eigen::Vector3f diagonalCoordinateVector;
diagonalCoordinateVector << diagonalWorldCoordinates.horizontal,
diagonalWorldCoordinates.vertical,
diagonalWorldCoordinates.depth;
Eigen::Vector3f worldCoordinatesVector = worldToDiagonalMatrix.inverse() * diagonalCoordinateVector;
Eigen::Vector3f worldCoordinatesVector = worldToIsometricMatrix.inverse() * isoCoordinatesVector / WORLD_TO_ISO_SCALE;
return {
worldCoordinatesVector.x(), // x

View file

@ -8,15 +8,10 @@ class CoordinateTransformer
{
private:
static const Eigen::Matrix<float, 3, 3> worldToIsometricMatrix;
static const Eigen::Matrix<float, 3, 3> worldToDiagonalMatrix;
public:
static IsometricCoordinates worldToIsometric(WorldCoordinates worldCoordinates);
static WorldCoordinates isometricToWorld(IsometricCoordinates isometricCoordinates);
static DiagonalWorldCoordinates worldToDiagonal(WorldCoordinates worldCoordinates);
static WorldCoordinates diagonalToWorld(DiagonalWorldCoordinates diagonalWorldCoordinates);
static float worldToDepth(sf::Vector2f worldGroundCoordinates);
static sf::Vector2f closestWorldPointAtDepth(float depth, sf::Vector2f referenceWorldPoint);
};

View file

@ -2,9 +2,6 @@
#define HOLESOME_COORDINATES_H
#include <SFML/System/Vector2.hpp>
#include "../utilities/vector_utils.hpp"
class DiagonalWorldCoordinates;
struct WorldCoordinates
{
@ -13,7 +10,7 @@ struct WorldCoordinates
WorldCoordinates(float x, float y, float z = 0) : x(x), y(y), z(z)
{}
explicit WorldCoordinates(sf::Vector2f vector) : x(vector.x), y(vector.y), z(0)
WorldCoordinates(sf::Vector2f vector) : x(vector.x), y(vector.y), z(0)
{}
float x;
@ -57,11 +54,6 @@ struct WorldCoordinates
{
return !(*this == other);
}
[[nodiscard]] sf::Vector2f toGroundCoordinates() const
{
return {x, y};
}
};
struct IsometricCoordinates
@ -70,7 +62,7 @@ struct IsometricCoordinates
float y;
float depth; // Bigger means further back. Can be used for accurate rendering order.
[[nodiscard]] sf::Vector2f toScreen() const
sf::Vector2f toScreen() const
{
return {x, y};
}
@ -80,20 +72,7 @@ struct IsometricCoordinates
IsometricCoordinates(float x, float y, float depth = 0) : x(x), y(y), depth(depth)
{}
explicit IsometricCoordinates(sf::Vector2f vector) : x(vector.x), y(vector.y), depth(0)
{}
};
struct DiagonalWorldCoordinates
{
float horizontal;
float vertical;
float depth; // Bigger means further back. Can be used for accurate rendering order.
const sf::Vector2f horizontalAxis = {1, -1};
DiagonalWorldCoordinates(float horizontal, float vertical, float depth) : horizontal(horizontal),
vertical(vertical), depth(depth)
explicit IsometricCoordinates (sf::Vector2f vector) : x(vector.x), y(vector.y), depth(0)
{}
};
@ -101,14 +80,6 @@ struct GridCoordinates
{
float x;
float y;
GridCoordinates() = default;
GridCoordinates(float x, float y) : x(x), y(y)
{}
GridCoordinates(int x, int y) : x(static_cast<float>(x)), y(static_cast<float>(y))
{}
};

View file

@ -2,8 +2,7 @@
#include <utility>
WorldCoordinates TranslatedCoordinates::world() const
{
WorldCoordinates TranslatedCoordinates::world() const {
auto coordinates = worldCoordinates;
if (parent != nullptr)
{
@ -13,52 +12,40 @@ WorldCoordinates TranslatedCoordinates::world() const
return coordinates;
}
IsometricCoordinates TranslatedCoordinates::isometric() const
{
return CoordinateTransformer::worldToIsometric(world());
IsometricCoordinates TranslatedCoordinates::isometric() const {
return coordTransformer->worldToIsometric(world());
}
GridCoordinates TranslatedCoordinates::grid() const
{
GridCoordinates TranslatedCoordinates::grid() const {
auto referenceWordCoordinates = world();
// Grid coords are just camera coords without height, and scaled differently
return {referenceWordCoordinates.x - 0.5f, referenceWordCoordinates.y - 0.5f};
return {referenceWordCoordinates.x * worldToGridFactor, referenceWordCoordinates.y * worldToGridFactor};
}
void TranslatedCoordinates::setWorld(WorldCoordinates newWorldCoordinates)
{
void TranslatedCoordinates::set(WorldCoordinates newWorldCoordinates) {
this->worldCoordinates = newWorldCoordinates;
}
void TranslatedCoordinates::setIsometric(IsometricCoordinates newIsometricCoordinates)
{
this->worldCoordinates = CoordinateTransformer::isometricToWorld(newIsometricCoordinates);
void TranslatedCoordinates::set(IsometricCoordinates newIsometricCoordinates) {
this->worldCoordinates = coordTransformer->isometricToWorld(newIsometricCoordinates);
}
void TranslatedCoordinates::setTranslated(const TranslatedCoordinates &newCoordinates)
{
void TranslatedCoordinates::set(const TranslatedCoordinates& newCoordinates) {
this->worldCoordinates = newCoordinates.world();
}
void TranslatedCoordinates::move(WorldCoordinates deltaWorldCoordinates)
{
void TranslatedCoordinates::move(WorldCoordinates deltaWorldCoordinates) {
this->worldCoordinates = this->worldCoordinates + deltaWorldCoordinates;
}
void TranslatedCoordinates::move(IsometricCoordinates deltaIsometricCoordinates)
{
move(CoordinateTransformer::isometricToWorld(deltaIsometricCoordinates));
}
TranslatedCoordinates::TranslatedCoordinates(WorldCoordinates worldCoordinates)
: worldCoordinates(worldCoordinates)
{
: worldCoordinates(worldCoordinates) {
}
void TranslatedCoordinates::move(sf::Vector2f deltaWorldCoordinates)
{
move(WorldCoordinates {deltaWorldCoordinates.x, deltaWorldCoordinates.y, 0});
move({deltaWorldCoordinates.x, deltaWorldCoordinates.y, 0});
}
void TranslatedCoordinates::setParent(std::shared_ptr<TranslatedCoordinates> parent, WorldCoordinates offset)
@ -66,51 +53,3 @@ void TranslatedCoordinates::setParent(std::shared_ptr<TranslatedCoordinates> par
this->parent = std::move(parent);
this->worldCoordinates = offset;
}
void TranslatedCoordinates::setGrid(GridCoordinates newGridCoordinates)
{
this->worldCoordinates = {newGridCoordinates.x + 0.5f, newGridCoordinates.y + 0.5f, 0};
}
void TranslatedCoordinates::setWorldOffset(WorldCoordinates offset)
{
this->worldCoordinates = offset;
}
void TranslatedCoordinates::setScreenOffset(IsometricCoordinates offset)
{
setWorldOffset(CoordinateTransformer::isometricToWorld(offset));
}
TranslatedCoordinates::TranslatedCoordinates(GridCoordinates gridCoordinates)
{
setGrid(gridCoordinates);
}
DiagonalWorldCoordinates TranslatedCoordinates::diagonalWorld() const
{
return CoordinateTransformer::worldToDiagonal(world());
}
void TranslatedCoordinates::setDiagonal(DiagonalWorldCoordinates newDiagonalWorldCoordinates)
{
this->worldCoordinates = CoordinateTransformer::diagonalToWorld(newDiagonalWorldCoordinates);
}
TranslatedCoordinates::TranslatedCoordinates(DiagonalWorldCoordinates diagonalWorldCoordinates)
{
setDiagonal(diagonalWorldCoordinates);
}
void TranslatedCoordinates::removeParent()
{
// Uncomment, if global position should be preserved
// auto globalWorldCoordinates = this->world();
// this->worldCoordinates = globalWorldCoordinates;
this->parent = nullptr;
}
TranslatedCoordinates::TranslatedCoordinates(IsometricCoordinates isometricCoordinates)
{
setIsometric(isometricCoordinates);
}

View file

@ -1,3 +1,7 @@
//
// Created by max on 27.04.23.
//
#ifndef HOLESOME_TRANSLATED_COORDINATES_H
#define HOLESOME_TRANSLATED_COORDINATES_H
@ -5,31 +9,24 @@
#include "coordinates.h"
#include "coordinate_transformer.h"
#define INITIAL_WORLD_TO_GRID_FACTOR 0.25f
class TranslatedCoordinates
{
public:
explicit TranslatedCoordinates(WorldCoordinates worldCoordinates);
explicit TranslatedCoordinates(DiagonalWorldCoordinates diagonalWorldCoordinates);
explicit TranslatedCoordinates(IsometricCoordinates isometricCoordinates);
explicit TranslatedCoordinates(GridCoordinates gridCoordinates);
[[nodiscard]] WorldCoordinates world() const;
WorldCoordinates world() const;
[[nodiscard]] IsometricCoordinates isometric() const;
IsometricCoordinates isometric() const;
[[nodiscard]] GridCoordinates grid() const;
GridCoordinates grid() const;
[[nodiscard]] DiagonalWorldCoordinates diagonalWorld() const;
void set(WorldCoordinates newWorldCoordinates);
void setWorld(WorldCoordinates newWorldCoordinates);
void set(const TranslatedCoordinates& newCoordinates);
void setTranslated(const TranslatedCoordinates& newCoordinates);
void setIsometric(IsometricCoordinates newIsometricCoordinates);
void setDiagonal(DiagonalWorldCoordinates newDiagonalWorldCoordinates);
void setGrid(GridCoordinates newGridCoordinates);
void set(IsometricCoordinates newIsometricCoordinates);
void move(WorldCoordinates deltaWorldCoordinates);
@ -37,19 +34,12 @@ public:
void setParent(std::shared_ptr<TranslatedCoordinates> parent, WorldCoordinates offset = {0, 0});
void removeParent();
void setWorldOffset(WorldCoordinates offset);
void setScreenOffset(IsometricCoordinates offset);
void move(IsometricCoordinates deltaIsometricCoordinates);
private:
WorldCoordinates worldCoordinates{};
WorldCoordinates worldCoordinates;
const float worldToGridFactor = INITIAL_WORLD_TO_GRID_FACTOR;
const std::shared_ptr<CoordinateTransformer> coordTransformer = std::make_shared<CoordinateTransformer>();
std::shared_ptr<TranslatedCoordinates> parent = nullptr;
};

View file

@ -22,13 +22,13 @@ GridDebugLayer::GridDebugLayer(int minX, int maxX, int minY, int maxY)
color = sf::Color::Blue;
}
auto gameObject = std::make_shared<CircleObject>(DB_CIRCLE_RADIUS, color);
addChildWorldOffset(gameObject, WorldCoordinates(x, y));
auto gameObject = std::make_shared<CircleObject>(DB_ISOPLANE_CORNER_RADIUS, color);
addChild(gameObject, WorldCoordinates(x, y));
}
}
}
void GridDebugLayer::draw(sf::RenderWindow *window)
void GridDebugLayer::draw(sf::RenderWindow *window) const
{
for (auto &gameObject: getChildren())
{

View file

@ -9,7 +9,7 @@ class GridDebugLayer : public GameObject
public:
GridDebugLayer(int minX, int maxX, int minY, int maxY);
void draw(sf::RenderWindow *window) override;
void draw(sf::RenderWindow *window) const override;
};

View file

@ -1,65 +0,0 @@
#include <SFML/Graphics/VertexArray.hpp>
#include "multiplayer_borders.hpp"
#include "../../config.h"
MultiplayerBorders::MultiplayerBorders(int firstRowViewCount, int secondRowViewCount)
: view(new sf::View(sf::Vector2f(0.5f, 0.5f), sf::Vector2f(1, 1)))
{
createBorders(firstRowViewCount, secondRowViewCount);
}
void MultiplayerBorders::createBorders(int firstRowViewCount, int secondRowViewCount)
{
int lineCount = firstRowViewCount + secondRowViewCount - 1;
if (lineCount <= 0)
{
return;
}
lines = sf::VertexArray(sf::Lines, lineCount * 2);
float lineHeight = secondRowViewCount > 0 ? 0.5f : 1.f;
// First row
int firstRowLineCount = firstRowViewCount - 1;
createRowOfLines(0, firstRowLineCount, 0, lineHeight);
if (secondRowViewCount <= 0)
{
return;
}
// Horizontal separator
createLine(firstRowLineCount,
{0, lineHeight},
{1, lineHeight});
// Second row
int secondRowLineCount = secondRowViewCount - 1;
createRowOfLines(firstRowLineCount + 1, secondRowLineCount, lineHeight, lineHeight);
}
void MultiplayerBorders::draw(sf::RenderWindow *window)
{
window->setView(*view);
window->draw(lines);
}
void MultiplayerBorders::createRowOfLines(int startIndex, int count, float topOffset, float height)
{
for (int i = 0; i < count; i++)
{
float horizontalProgress = (float) (i + 1) / (float) (count + 1);
createLine(startIndex + i,
{horizontalProgress, topOffset},
{horizontalProgress, topOffset + height});
}
}
void MultiplayerBorders::createLine(int lineIndex, sf::Vector2f firstPoint, sf::Vector2f secondPoint)
{
lines[lineIndex * 2].position = firstPoint;
lines[lineIndex * 2 + 1].position = secondPoint;
lines[lineIndex * 2].color = MP_VIEW_BORDER_COLOR;
lines[lineIndex * 2 + 1].color = MP_VIEW_BORDER_COLOR;
}

View file

@ -1,26 +0,0 @@
#ifndef HOLESOME_MULTIPLAYER_BORDERS_HPP
#define HOLESOME_MULTIPLAYER_BORDERS_HPP
#include "../game_object.h"
class MultiplayerBorders : public GameObject
{
public:
explicit MultiplayerBorders(int firstRowViewCount = 0, int secondRowViewCount = 0);
void draw(sf::RenderWindow *window) override;
void createBorders(int firstRowViewCount, int secondRowViewCount = 0);
private:
sf::VertexArray lines{};
sf::View* view = nullptr;
void createRowOfLines(int startIndex, int count, float topOffset, float height);
void createLine(int lineIndex, sf::Vector2f firstPoint, sf::Vector2f secondPoint);
};
#endif //HOLESOME_MULTIPLAYER_BORDERS_HPP

View file

@ -1,150 +0,0 @@
#include "multiplayer_view.hpp"
#include "../player/player_collection.hpp"
#include "ITrackable.h"
std::shared_ptr<MultiplayerView> MultiplayerView::getInstance()
{
if (singletonInstance == nullptr)
{
singletonInstance = std::make_shared<MultiplayerView>();
}
return singletonInstance;
}
void MultiplayerView::update()
{
// Update based on PlayerCollection
// Add new
if (MP_VIEW_ADD_NEW_PLAYERS)
{
for (const auto &player: PlayerCollection::getInstance()->getNewPlayers())
{
if (player->getTrackableState() == TrackableState::TRACKING)
{
addPlayer(player);
}
}
}
GameObject::update();
}
void MultiplayerView::addPlayer(const std::shared_ptr<Player> &player)
{
// Player already added?
if (viewsForPlayers.find(player->getPlayerId()) != viewsForPlayers.end())
{
LOG(WARNING) << "Player " << player->getPlayerId() << " already added to MultiplayerView.";
return;
}
// Create new view
auto view = std::make_shared<TrackingView>();
view->addTrackable(player);
viewsForPlayers[player->getPlayerId()] = view;
addChild(view);
updateLayout();
}
void MultiplayerView::removePlayer(const std::shared_ptr<Player> &player)
{
// Player already removed?
if (viewsForPlayers.find(player->getPlayerId()) == viewsForPlayers.end())
{
LOG(WARNING) << "Player " << player->getPlayerId() << " already removed from MultiplayerView.";
return;
}
// Make view show all remaining players
auto view = viewsForPlayers[player->getPlayerId()];
view->removeTrackable(player);
for (const auto &remainingPlayers: PlayerCollection::getInstance()->getPlayers())
{
view->addTrackable(remainingPlayers);
}
}
void MultiplayerView::updateLayout() const
{
if (viewsForPlayers.empty())
{
return;
}
int viewCount = viewsForPlayers.size();
// Handle simple cases
if (viewCount == 1)
{
viewsForPlayers.begin()->second->setViewport({0, 0}, {1, 1});
return;
}
if (viewCount == 2)
{
auto it = viewsForPlayers.begin();
it->second->setViewport({0, 0}, {0.5, 1});
it++;
it->second->setViewport({0.5, 0}, {0.5, 1});
borders->createBorders(2);
return;
}
// Calculate layout
int firstRowViewCount = ceil(viewCount / 2.f);
int secondRowViewCount = viewCount - firstRowViewCount;
borders->createBorders(firstRowViewCount, secondRowViewCount);
int index = 0;
for (auto &view: getViews())
{
bool secondRow = index >= viewCount / 2.f;
int rowViewCount = secondRow ? secondRowViewCount : firstRowViewCount;
int rowIndexOfView = index - (secondRow ? firstRowViewCount : 0);
view->setViewport(
{
rowIndexOfView / (float) rowViewCount,
secondRow ? 0.5f : 0
}, {
1.f / rowViewCount,
0.5f
}
);
index++;
}
}
std::vector<std::shared_ptr<TrackingView>> MultiplayerView::getViews() const
{
std::vector<std::shared_ptr<TrackingView>> views;
for (const auto &[_, view]: viewsForPlayers)
{
views.push_back(view);
}
return views;
}
void MultiplayerView::draw(sf::RenderWindow *window)
{
if (borders != nullptr)
{
borders->draw(window);
}
}
MultiplayerView::MultiplayerView()
: borders(std::make_shared<MultiplayerBorders>()), viewsForPlayers({})
{
}
void MultiplayerView::clear()
{
viewsForPlayers.clear();
}

View file

@ -1,40 +0,0 @@
#ifndef HOLESOME_MULTIPLAYER_VIEW_HPP
#define HOLESOME_MULTIPLAYER_VIEW_HPP
#include <map>
#include "../game_object.h"
#include "tracking_view.h"
#include "../player/player.hpp"
#include "multiplayer_borders.hpp"
class MultiplayerView : public GameObject
{
public:
static std::shared_ptr<MultiplayerView> getInstance();
MultiplayerView();
void draw(sf::RenderWindow *window) override;
void update() override;
void clear();
void addPlayer(const std::shared_ptr<Player>& player);
void removePlayer(const std::shared_ptr<Player>& player);
std::vector<std::shared_ptr<TrackingView>> getViews() const;
private:
static inline std::shared_ptr<MultiplayerView> singletonInstance = nullptr;
std::map<int, std::shared_ptr<TrackingView>> viewsForPlayers; // Player ID => View
void updateLayout() const;
std::shared_ptr<MultiplayerBorders> borders;
};
#endif //HOLESOME_MULTIPLAYER_VIEW_HPP

View file

@ -1,13 +1,13 @@
#include "tracking_view.h"
#include "../../utilities/vector_utils.hpp"
#include "../player/player_collection.hpp"
TrackingView::TrackingView(TrackingViewOptions options) : options(options),
view(new sf::View(options.initialCenter,
options.minViewSize)),
trackables({})
view(nullptr),
hasViewChanged(false)
{
marker = new CircleObject(DB_CIRCLE_RADIUS, sf::Color::Yellow);
trackables = std::vector<ITrackable *>();
marker = new CircleObject(2, sf::Color::Yellow);
Game::getInstance()->registerView(this);
}
TrackingView::~TrackingView()
@ -18,13 +18,33 @@ TrackingView::~TrackingView()
void TrackingView::lateUpdate()
{
// Initialize if necessary
if (view == nullptr)
{
initializeView();
}
processTrackableStates();
if (!trackables.empty())
{
followTrackables();
adjustSizeToTrackables();
}
adjustSizeToTrackables();
// Update window if necessary
if (hasViewChanged)
{
Game::getInstance()->window->setView(*this->view);
hasViewChanged = false;
}
}
void TrackingView::initializeView()
{
auto size = Game::getInstance()->window->getView().getSize();
view = new sf::View({0, 0}, size);
hasViewChanged = true;
}
void TrackingView::setSize(sf::Vector2f newSize)
@ -45,13 +65,13 @@ void TrackingView::setSize(sf::Vector2f newSize)
didAspectRationChange = true;
}
if (options.softResizeSpeed != 0 && !didAspectRationChange)
{
if (options.softResizeSpeed != 0 && !didAspectRationChange) {
// Smooth out transition to new size
newSize = size + (newSize - size) * options.softResizeSpeed * FRAME_TIME.asSeconds();
}
view->setSize(newSize);
hasViewChanged = true;
}
sf::Vector2f TrackingView::getSize() const
@ -59,11 +79,10 @@ sf::Vector2f TrackingView::getSize() const
return view->getSize();
}
sf::Vector2f TrackingView::getSizeInWindow() const
sf::Vector2f TrackingView::getWindowSize() const
{
auto size = Game::getInstance()->window->getSize();
auto viewPort = view->getViewport();
return {static_cast<float>(size.x * viewPort.width), static_cast<float>(size.y * viewPort.height)};
return {static_cast<float>(size.x), static_cast<float>(size.y)};
}
sf::Vector2f TrackingView::getCenter() const
@ -74,9 +93,9 @@ sf::Vector2f TrackingView::getCenter() const
void TrackingView::followTrackables()
{
auto trackingPoint = getTrackingArea().getCenter();
if (DB_TRACKING_VIEW_CENTER)
if (DEVELOPER_MODE)
{
marker->coordinates->setIsometric(IsometricCoordinates(trackingPoint));
marker->coordinates->set(IsometricCoordinates(trackingPoint));
}
// Calculate distance to target to check how to handle it
@ -84,7 +103,7 @@ void TrackingView::followTrackables()
auto vectorToTarget = trackingPoint - currentCenter;
auto distanceToTarget = length(vectorToTarget);
if (distanceToTarget <= getFreeMovementRadius())
if (distanceToTarget <= getRadius(options.freeMoveThreshold))
{
// Nothing to do
return;
@ -96,7 +115,7 @@ void TrackingView::followTrackables()
auto deltaToDesiredView = normalize(vectorToTarget);
// Reduce delta to edge of free-move area to make it less jaring
deltaToDesiredView *= distanceToTarget - getFreeMovementRadius();
deltaToDesiredView *= distanceToTarget - getRadius(options.freeMoveThreshold);
moveCenter(deltaToDesiredView);
}
@ -110,11 +129,12 @@ void TrackingView::moveCenter(sf::Vector2<float> delta)
}
view->move(delta);
hasViewChanged = true;
}
void TrackingView::draw(sf::RenderWindow *window)
void TrackingView::draw(sf::RenderWindow *window) const
{
if (!DB_TRACKING_VIEW_CENTER)
if (!DEVELOPER_MODE)
{
return;
}
@ -122,21 +142,15 @@ void TrackingView::draw(sf::RenderWindow *window)
marker->draw(window);
}
void TrackingView::addTrackable(const std::shared_ptr<ITrackable> &trackable)
void TrackingView::addTrackable(ITrackable *trackable)
{
trackables.push_back(trackable);
// Set initial values if first trackable
if (trackables.size() == 1)
{
view->setCenter(trackable->getTrackablePosition());
}
}
void TrackingView::processTrackableStates()
{
// Remove trackables that have ended tracking
std::remove_if(trackables.begin(), trackables.end(), [](std::shared_ptr<ITrackable> trackable)
std::remove_if(trackables.begin(), trackables.end(), [](ITrackable *trackable)
{
return trackable->getTrackableState() == TrackableState::END_TRACKING;
});
@ -144,11 +158,6 @@ void TrackingView::processTrackableStates()
TrackingArea TrackingView::getTrackingArea() const
{
if (trackables.empty())
{
return TrackingArea();
}
auto initialTrackable = trackables[0];
TrackingArea area = {
initialTrackable->getTrackablePosition() - initialTrackable->getTrackableSize() / 2.f,
@ -194,7 +203,7 @@ void TrackingView::adjustSizeToTrackables()
newViewSize = restrainToBounds(newViewSize);
// Extend view to match aspect ratio
auto windowSize = getSizeInWindow();
auto windowSize = getWindowSize();
auto aspectRatio = windowSize.x / windowSize.y;
if (newViewSize.x / newViewSize.y > aspectRatio)
{
@ -233,49 +242,32 @@ sf::Vector2f TrackingView::restrainToBounds(sf::Vector2f viewSize) const
sf::Vector2f TrackingView::applyPadding(sf::Vector2f viewSize) const
{
auto padding = options.viewSizePadding;
if (!options.isAbsoluteViewSizePadding)
if (padding.x <= 1)
{
padding.x *= viewSize.x;
}
if (padding.y <= 1)
{
padding.y *= viewSize.y;
}
return viewSize + padding;
}
float TrackingView::getFreeMovementRadius() const
float TrackingView::getRadius(float threshold) const
{
if (options.freeMoveThreshold <= 0)
if (threshold <= 0)
{
return 0;
}
if (options.isAbsoluteFreeMoveThreshold)
if (threshold > 1)
{
return options.freeMoveThreshold;
return threshold;
}
auto windowSize = getSizeInWindow();
auto windowSize = getWindowSize();
float smallestSide = std::min(windowSize.x, windowSize.y);
// Only half of the side, since we are calculating radius
return smallestSide / 2.f * options.freeMoveThreshold;
}
void TrackingView::removeTrackable(const std::shared_ptr<ITrackable> &trackable)
{
trackables.erase(
std::remove_if(trackables.begin(), trackables.end(), [&trackable](const std::shared_ptr<ITrackable> &t)
{
return t == trackable;
}), trackables.end());
}
void TrackingView::setViewport(sf::Vector2f center, sf::Vector2f size)
{
// TODO: Resize content for viewport
view->setViewport(sf::FloatRect(center.x, center.y, size.x, size.y));
}
void TrackingView::setViewForWindow()
{
Game::getInstance()->window->setView(*this->view);
return smallestSide / 2.f * threshold;
}

View file

@ -17,17 +17,12 @@ public:
~TrackingView();
void draw(sf::RenderWindow *window) override;
void draw(sf::RenderWindow *window) const override;
void lateUpdate() override;
void addTrackable(const std::shared_ptr<ITrackable>& trackable);
void removeTrackable(const std::shared_ptr<ITrackable> &trackable);
void setViewport(sf::Vector2f center, sf::Vector2f size);
void setViewForWindow();
void addTrackable(ITrackable *trackable);
sf::Vector2f getSize() const;
@ -35,11 +30,14 @@ public:
private:
sf::View *view{};
bool hasViewChanged{};
TrackingViewOptions options;
std::vector<std::shared_ptr<ITrackable>> trackables;
std::vector<ITrackable *> trackables;
CircleObject *marker;
void initializeView();
void followTrackables();
void moveCenter(sf::Vector2<float> delta);
@ -56,9 +54,9 @@ private:
sf::Vector2f restrainToBounds(sf::Vector2f viewSize) const;
float getFreeMovementRadius() const;
float getRadius(float threshold) const;
sf::Vector2f getSizeInWindow() const;
sf::Vector2f getWindowSize() const;
};

View file

@ -7,9 +7,10 @@
struct TrackingViewOptions
{
/**
* Value >1 to set pixel radius.
* Value between 0 and 1 to set relative radius based on smallest half-axis-size.
*/
float freeMoveThreshold = DEF_TV_FREE_MOVE_THRESHOLD;
bool isAbsoluteFreeMoveThreshold = DEF_TV_IS_ABSOLUTE_FREE_MOVE_THRESHOLD;
/**
* 0 for instant follow.
@ -30,14 +31,12 @@ struct TrackingViewOptions
*/
sf::Vector2f maxViewSize = DEF_TV_MAX_VIEW_SIZE;
sf::Vector2f initialCenter = {0, 0};
/**
* Will be added to tracked area size twice, as padding for each side.
* If isAbsoluteViewSizePadding is set to true, padding will be added to view size, is multiplied with tracking size and added.
* Value >1 to set pixel padding.
* Value between 0 and 1 to set relative padding.
*/
sf::Vector2f viewSizePadding = DEF_TV_VIEW_SIZE_PADDING;
bool isAbsoluteViewSizePadding = DEF_TV_IS_ABSOLUTE_VIEW_SIZE_PADDING;
};
#endif //HOLESOME_TRACKING_VIEW_OPTIONS_HPP

View file

@ -1,58 +0,0 @@
#include "collectable.hpp"
#include "../../sprites/versatile_sprite.hpp"
#include "../../config.h"
#include "../input/input_mapper.h"
#include "../physics/holes/holes_simulation.hpp"
#include "collection/collectables_collection.hpp"
#include "../player/player_collection.hpp"
Collectable::Collectable(int points)
: points(points)
{
collectableId = collectableCount;
collectableCount++;
}
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
{
return coordinates->isometric().depth;
}
void Collectable::setSprite(const std::string &spriteName)
{
// Create versatile sprite
auto spriteObject = std::make_shared<VersatileSprite>(spriteName);
size = spriteObject->getSize() * COLLECTABLE_SCALE;
spriteObject->setSize(size);
addChildScreenOffset(spriteObject, IsometricCoordinates(-size / 2.f));
sprite = spriteObject;
// Set half size offset of coordinates
coordinates->move(IsometricCoordinates(0, -size.x / 2.f, 0));
}
sf::Vector2f Collectable::getSize() const
{
return size / WORLD_TO_ISO_SCALE;
}
void Collectable::preRenderUpdate()
{
if (!sprite->isVisible())
{
auto closestPlayer = PlayerCollection::getInstance()->getClosestPlayer(*coordinates);
closestPlayer->consume(points);
setActive(false);
return;
}
GameObject::preRenderUpdate();
}

View file

@ -1,41 +0,0 @@
#ifndef HOLESOME_COLLECTABLE_HPP
#define HOLESOME_COLLECTABLE_HPP
#include "../game_object.h"
#include "../player/player.hpp"
#include "../../sprites/masked_sprite.hpp"
class Collectable : public GameObject
{
public:
Collectable(int points);
void setSprite(const std::string &spriteName);
void setRotation(float angle);
sf::Vector2f getSize() const;
float getDepth() const;
int getId() const
{
return collectableId;
}
void preRenderUpdate() override;
private:
int collectableId = 0;
static inline int collectableCount = 0;
std::shared_ptr<Sprite> sprite;
sf::Vector2f size;
int points = 0;
std::shared_ptr<Player> consumedBy = nullptr;
};
#endif //HOLESOME_COLLECTABLE_HPP

View file

@ -1,23 +0,0 @@
#ifndef HOLESOME_COLLECTABLE_CONFIG_HPP
#define HOLESOME_COLLECTABLE_CONFIG_HPP
#include <string>
struct CollectableConfig
{
std::string spriteName;
int points = 0;
explicit CollectableConfig(std::string spriteName, int points)
: spriteName(std::move(spriteName)), points(points)
{}
CollectableConfig() = default;
bool isValid() const
{
return !spriteName.empty();
}
};
#endif //HOLESOME_COLLECTABLE_CONFIG_HPP

View file

@ -1,12 +0,0 @@
#include "collectable_factory.hpp"
std::shared_ptr<Collectable> CollectableFactory::createFromInLevelConfig(const CollectableInLevel &config)
{
auto collectableConfig = config.collectableConfig;
auto collectable = std::make_shared<Collectable>(config.collectableConfig.points);
collectable->coordinates->setGrid(config.position);
collectable->setSprite(collectableConfig.spriteName);
return collectable;
}

View file

@ -1,15 +0,0 @@
#ifndef HOLESOME_COLLECTABLE_FACTORY_HPP
#define HOLESOME_COLLECTABLE_FACTORY_HPP
#include "collectable_in_level.hpp"
#include "collectable.hpp"
class CollectableFactory
{
public:
static std::shared_ptr<Collectable> createFromInLevelConfig(const CollectableInLevel &config);
};
#endif //HOLESOME_COLLECTABLE_FACTORY_HPP

View file

@ -1,35 +0,0 @@
#ifndef HOLESOME_COLLECTABLE_IN_LEVEL_HPP
#define HOLESOME_COLLECTABLE_IN_LEVEL_HPP
#include "../../coordinates/coordinates.h"
#include "collectable_config.hpp"
#include "../../logging/easylogging++.h"
#include "../../collectables.hpp"
struct CollectableInLevel
{
std::string name;
GridCoordinates position;
CollectableConfig collectableConfig;
CollectableInLevel(const std::string &name, GridCoordinates position)
: name(name),
position(position)
{
if (all_collectables.find(name) == all_collectables.end())
{
LOG(ERROR) << "Collectable config " << name << " not found. Skipping collectable.";
this->name = ""; // Make this invalid
return;
}
this->collectableConfig = all_collectables.at(name);
}
bool isValid() const
{
return !name.empty();
}
};
#endif //HOLESOME_COLLECTABLE_IN_LEVEL_HPP

View file

@ -1,132 +0,0 @@
#include "collectables_collection.hpp"
#include <cmath>
#include "collectables_depth_collection.hpp"
#include "../../../logging/easylogging++.h"
#include "../../../config.h"
#include "../../physics/holes/holes_simulation.hpp"
std::shared_ptr<CollectablesCollection> CollectablesCollection::getInstance()
{
if (singletonInstance == nullptr)
{
singletonInstance = std::make_shared<CollectablesCollection>();
}
return singletonInstance;
}
void CollectablesCollection::createEmpty(int maxDepth)
{
// Remove previous collections
depthCollections.clear();
LOG(INFO) << "Creating empty collectables collection with a max depth of " << maxDepth << " ...";
// Create new collections
for (int d = 0; d < maxDepth; d++)
{
auto depthCollection = std::make_shared<CollectablesDepthCollection>(d);
depthCollections[d] = depthCollection;
}
}
void CollectablesCollection::remove(int collectableId)
{
HolesSimulation::getInstance()->removeCollectable(collectableId);
std::shared_ptr<Collectable> collectable = nullptr;
for (auto &child: getChildren())
{
collectable = std::dynamic_pointer_cast<Collectable>(child);
if (collectable->getId() == collectableId)
{
break;
}
}
if (collectable == nullptr)
{
LOG(ERROR) << "No collectables left to remove.";
return;
}
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()
{
GameObject::update();
// Remove inactive collectables
auto collectables = getChildren();
for (auto &child: collectables)
{
auto collectable = std::dynamic_pointer_cast<Collectable>(child);
if (collectable->getActive())
{
continue;
}
remove(collectable->getId());
}
if (!CONSIDER_COLLECTABLE_DEPTH_MOVEMENT)
{
return;
}
// Move collectables to new depth collections if necessary
// First, clear history of all depth collections
for (auto &[depth, depthCollection]: depthCollections)
{
depthCollection->clearHistory();
}
// Then, move collectables to new depth collections
for (auto &[depth, depthCollection]: depthCollections)
{
auto collectables = depthCollection->collectables;
for (auto &collectable: collectables)
{
int newDepth = std::floor(collectable->getDepth());
if (newDepth == depth)
{
continue;
}
if (!isValidDepth(newDepth))
{
LOG(ERROR) << "Collectable " << collectable->getId() << " has invalid depth " << newDepth
<< "! Keeping it at previous depth " << depth << " ...";
continue;
}
depthCollection->remove(collectable);
depthCollections[newDepth]->add(collectable);
}
}
}
bool CollectablesCollection::isValidDepth(int desiredDepth) const
{ return desiredDepth >= 0 && desiredDepth < (int) depthCollections.size(); }
void CollectablesCollection::draw(sf::RenderWindow *window)
{
// Render collectables in reverse order of depth
int maxDepth = (int) depthCollections.size();
for (int depth = maxDepth - 1; depth >= 0; depth--)
{
auto depthCollection = depthCollections.at(depth);
for (auto &collectable: depthCollection->collectables)
{
collectable->draw(window);
}
}
}

View file

@ -1,35 +0,0 @@
#ifndef HOLESOME_COLLECTABLES_COLLECTION_HPP
#define HOLESOME_COLLECTABLES_COLLECTION_HPP
#include <memory>
#include <map>
#include "collectables_depth_collection.hpp"
/**
* @brief Collection of all collections for each depth.
*/
class CollectablesCollection : public GameObject
{
public:
static std::shared_ptr<CollectablesCollection> getInstance();
void createEmpty(int maxDepth);
void update() override;
void draw(sf::RenderWindow *window) override;
void add(const std::shared_ptr<Collectable>& collectable);
void remove(int collectableId);
private:
static inline std::shared_ptr<CollectablesCollection> singletonInstance = nullptr;
std::map<int, std::shared_ptr<CollectablesDepthCollection>> depthCollections = {};
bool isValidDepth(int desiredDepth) const;
};
#endif //HOLESOME_COLLECTABLES_COLLECTION_HPP

View file

@ -1,23 +0,0 @@
#include "collectables_depth_collection.hpp"
CollectablesDepthCollection::CollectablesDepthCollection(int depth)
: depth(depth)
{}
void CollectablesDepthCollection::clearHistory()
{
recentlyAdded.clear();
recentlyRemoved.clear();
}
void CollectablesDepthCollection::add(const std::shared_ptr<Collectable> &collectable)
{
collectables.insert(collectable);
recentlyAdded.insert(collectable);
}
void CollectablesDepthCollection::remove(const std::shared_ptr<Collectable> &collectable)
{
recentlyRemoved.insert(collectable);
collectables.erase(collectable);
}

View file

@ -1,28 +0,0 @@
#ifndef HOLESOME_COLLECTABLES_DEPTH_COLLECTION_HPP
#define HOLESOME_COLLECTABLES_DEPTH_COLLECTION_HPP
#include <set>
#include <memory>
#include "../collectable.hpp"
/**
* @brief Collection of collectables for a specific depth.
*/
struct CollectablesDepthCollection
{
int depth;
std::set<std::shared_ptr<Collectable>> collectables = {};
std::set<std::shared_ptr<Collectable>> recentlyRemoved = {};
std::set<std::shared_ptr<Collectable>> recentlyAdded = {};
explicit CollectablesDepthCollection(int depth);
void clearHistory();
void add(const std::shared_ptr<Collectable>& collectable);
void remove(const std::shared_ptr<Collectable>& collectable);
};
#endif //HOLESOME_COLLECTABLES_DEPTH_COLLECTION_HPP

View file

@ -0,0 +1,14 @@
#include "depth_renderer.hpp"
void DepthRenderer::addGameObject(const std::shared_ptr<GameObject>& gameObject)
{
// TODO: Subscribe to depth changes!
gameObjects.insert(gameObject);
}
void DepthRenderer::render(const std::shared_ptr<sf::RenderWindow> &window) const
{
for (const auto& gameObject : gameObjects) {
gameObject->draw(window.get());
}
}

View file

@ -0,0 +1,24 @@
#ifndef HOLESOME_DEPTH_RENDERER_HPP
#define HOLESOME_DEPTH_RENDERER_HPP
#include <set>
#include "game_object.h"
auto compareGameObjectDepth = [](const std::shared_ptr<GameObject>& a, const std::shared_ptr<GameObject>& b) { return a->coordinates->isometric().depth < b->coordinates->isometric().depth; };
class DepthRenderer
{
public:
DepthRenderer() = default;
void addGameObject(const std::shared_ptr<GameObject>& gameObject);
void render(const std::shared_ptr<sf::RenderWindow>& window) const;
private:
std::multiset<std::shared_ptr<GameObject>, decltype(compareGameObjectDepth)> gameObjects;
};
#endif //HOLESOME_DEPTH_RENDERER_HPP

View file

@ -1,36 +0,0 @@
#include "frame_counter.hpp"
#include "../logging/easylogging++.h"
unsigned int FrameCounter::getFps() const
{
return lastFps;
}
void FrameCounter::addFrame()
{
liveFrameCount++;
timeSinceLastFpsUpdate += clock.restart();
auto const second = sf::seconds(1);
if (timeSinceLastFpsUpdate < second)
{
return;
}
lastFps = liveFrameCount;
liveFrameCount = 0;
timeSinceLastFpsUpdate -= second;
if (printFpsInConsole)
{
LOG(INFO) << "FPS: " << lastFps;
}
}
void FrameCounter::reset()
{
clock.restart();
timeSinceLastFpsUpdate = sf::Time::Zero;
liveFrameCount = 0;
lastFps = 0;
}

View file

@ -1,25 +0,0 @@
#ifndef HOLESOME_FRAME_COUNTER_HPP
#define HOLESOME_FRAME_COUNTER_HPP
#include <SFML/System/Clock.hpp>
class FrameCounter
{
public:
void reset();
void addFrame();
unsigned int getFps() const;
bool printFpsInConsole = true;
private:
sf::Clock clock;
sf::Time timeSinceLastFpsUpdate = sf::Time::Zero;
unsigned int liveFrameCount = 0;
unsigned int lastFps = 0;
};
#endif //HOLESOME_FRAME_COUNTER_HPP

View file

@ -3,29 +3,29 @@
#include <SFML/Graphics/RenderWindow.hpp>
#include "game.h"
#include "level/level_loader.hpp"
#include "physics/map/map_simulation.hpp"
#include "../logging/easylogging++.h"
#include "layer/global_layer.hpp"
#include "player/player_collection.hpp"
#include "../screens/winner_screen.hpp"
#include "../screens/join_screen.hpp"
#include "level/level_generator.hpp"
#include "../screens/loading_screen.hpp"
Game::Game(std::shared_ptr<sf::RenderWindow> window) : window(std::move(window))
Game::Game(std::shared_ptr<sf::RenderWindow> window) : window(std::move(window)),
gameObjects(),
views()
{
}
Game::~Game()
{
for (auto &gameObject: gameObjects)
{
delete gameObject;
}
}
void Game::run()
{
sf::Clock clock;
sf::Time TimeSinceLastUpdate = sf::seconds(0);
frameCounter->reset();
LOG(INFO) << "Game loop started ...";
while (window->isOpen())
{
InputMapper::getInstance()->processEvents();
TimeSinceLastUpdate += clock.restart();
while (TimeSinceLastUpdate >= FRAME_TIME)
@ -35,11 +35,8 @@ void Game::run()
update();
InputMapper::getInstance()->processEvents();
}
drawFrame();
}
LOG(INFO) << "Game closing ...";
}
void Game::exit()
@ -60,27 +57,17 @@ void Game::drawFrame()
}
window->display();
frameCounter->addFrame();
}
void Game::addGameObject(const std::shared_ptr<GameObject> &gameObject)
void Game::addGameObject(GameObject *gameObject)
{
gameObjectsBuffer.push_back(gameObject);
gameObjects.push_back(gameObject);
}
void Game::update()
{
handleWinning();
// Add new game objects
for (const auto &gameObject: gameObjectsBuffer)
{
gameObjects.push_back(gameObject);
}
gameObjectsBuffer.clear();
// Basic Updates
for (const auto &gameObject: gameObjects)
for (auto &gameObject: gameObjects)
{
if (gameObject->getActive())
{
@ -89,7 +76,7 @@ void Game::update()
}
// Late updates
for (const auto &gameObject: gameObjects)
for (auto &gameObject: gameObjects)
{
if (gameObject->getActive())
{
@ -97,24 +84,7 @@ void Game::update()
}
}
// Physics updates
for (const auto &gameObject: gameObjects)
{
if (gameObject->getActive())
{
gameObject->physicsUpdate();
}
}
InputMapper::getInstance()->updateIdentities();
for (const auto &gameObject: gameObjects)
{
if (gameObject->getActive())
{
gameObject->preRenderUpdate();
}
}
InputMapper::getInstance()->updateIdentityEvents();
}
std::shared_ptr<Game> Game::getInstance()
@ -139,113 +109,7 @@ std::shared_ptr<Game> Game::constructInstance(const std::shared_ptr<sf::RenderWi
return singletonInstance;
}
void Game::clearGameObjects()
void Game::registerView(TrackingView *view)
{
gameObjects.clear();
}
void Game::setLevel(LevelConfig levelConfig)
{
loadedLevelConfig = std::move(levelConfig);
}
bool Game::isLevelLoaded() const
{
return loadedLevelConfig.isValid();
}
void Game::startCountdown(int durationInSeconds)
{
countdown = std::make_shared<Countdown>(durationInSeconds);
GlobalLayer::getInstance()->add(countdown);
}
std::shared_ptr<Player> Game::checkForWinner()
{
std::vector<std::shared_ptr<Player>> remainingPlayers = PlayerCollection::getInstance()->getRemainingPlayers();
// Has timer run out or is only one player left?
if (!countdown->isFinished() && remainingPlayers.size() > 1)
{
return nullptr;
}
// Return player with highest score as winner
std::shared_ptr<Player> winner = nullptr;
for (const auto &player: remainingPlayers)
{
if (winner == nullptr || player->getPoints() > winner->getPoints())
{
winner = player;
}
}
return winner;
}
void Game::handleWinning()
{
if (!isLevelLoaded())
{
return;
}
// Any players in game?
if (PlayerCollection::getInstance()->getPlayers().empty())
{
return;
}
std::shared_ptr<Player> winner = checkForWinner();
if (winner == nullptr)
{
return;
}
LOG(INFO) << "Player " << winner->getPlayerId() << " won the game with " << winner->getPoints() << " points.";
// Stop game and show winner
auto players = PlayerCollection::getInstance()->getPlayers();
LevelLoader::cleanUp();
auto winnerScreen = std::make_shared<WinnerScreen>(players);
GlobalLayer::getInstance()->add(winnerScreen);
addGameObject(GlobalLayer::getInstance());
}
void Game::showJoinScreen()
{
GlobalLayer::getInstance()->clear();
clearGameObjects();
InputMapper::getInstance()->allowNewInputIdentities = true;
PlayerCollection::getInstance()->deactivatePlayers();
auto joinScreen = std::make_shared<JoinScreen>();
GlobalLayer::getInstance()->add(joinScreen);
addGameObject(GlobalLayer::getInstance());
addGameObject(PlayerCollection::getInstance());
}
void Game::generateNewLevel()
{
showLoadingScreen("Generating new level ...");
LevelConfig newLevel = LevelGenerator::generateLevel("Procedural Level");
LevelLoader::loadLevel(newLevel);
}
void Game::showLoadingScreen(const std::string &message)
{
GlobalLayer::getInstance()->clear();
clearGameObjects();
auto loadingScreen = std::make_shared<LoadingScreen>(message);
GlobalLayer::getInstance()->add(loadingScreen);
addGameObject(GlobalLayer::getInstance());
// Mini loop
update();
drawFrame();
views.push_back(view);
}

View file

@ -7,9 +7,6 @@
#include "game_object.h"
#include "input/input_mapper.h"
#include "camera/tracking_view.h"
#include "level/level_config.hpp"
#include "frame_counter.hpp"
#include "time/countdown.hpp"
class TrackingView;
@ -17,51 +14,29 @@ class TrackingView;
class Game
{
public:
static std::shared_ptr<Game> constructInstance(const std::shared_ptr<sf::RenderWindow> &window);
static std::shared_ptr<Game> constructInstance(const std::shared_ptr<sf::RenderWindow>& window);
static std::shared_ptr<Game> getInstance();
explicit Game(std::shared_ptr<sf::RenderWindow> window);
~Game();
void run();
void exit();
void clearGameObjects();
void addGameObject(GameObject *gameObject);
void setLevel(LevelConfig levelConfig);
void startCountdown(int durationInSeconds);
[[nodiscard]] bool isLevelLoaded() const;
void addGameObject(const std::shared_ptr<GameObject> &gameObject);
void registerView(TrackingView *view);
std::shared_ptr<sf::RenderWindow> window;
void showJoinScreen();
void generateNewLevel();
std::vector<TrackingView*> views;
private:
static inline std::shared_ptr<Game> singletonInstance = nullptr;
std::vector<std::shared_ptr<GameObject>> gameObjects = {};
std::vector<std::shared_ptr<GameObject>> gameObjectsBuffer = {};
LevelConfig loadedLevelConfig = {};
std::shared_ptr<FrameCounter> frameCounter = std::make_shared<FrameCounter>();
std::shared_ptr<Countdown> countdown = std::make_shared<Countdown>();
std::vector<GameObject *> gameObjects;
void drawFrame();
void update();
std::shared_ptr<Player> checkForWinner();
void handleWinning();
void showLoadingScreen(const std::string &message = "Loading...");
};

View file

@ -3,8 +3,7 @@
#include "../config.h"
std::shared_ptr<Game> GameFactory::createWindowed(const std::string &title, int width, int height)
{
std::shared_ptr<Game> GameFactory::createWindowed(const std::string &title, int width, int height) {
auto window = std::make_shared<sf::RenderWindow>(sf::VideoMode(width, height), title,
sf::Style::Default,
getAdditionalSettings());
@ -12,16 +11,13 @@ std::shared_ptr<Game> GameFactory::createWindowed(const std::string &title, int
return Game::constructInstance(window);
}
std::shared_ptr<Game> GameFactory::createFullscreen(const std::string &title)
{
std::shared_ptr<Game> GameFactory::createFullscreen(const std::string &title) {
sf::VideoMode fullScreenMode;
auto availableModes = sf::VideoMode::getFullscreenModes();
if (availableModes.empty())
{
if (availableModes.empty()) {
LOG(INFO) << "No fullscreen modes available, falling back to Desktop Mode.";
fullScreenMode = sf::VideoMode::getDesktopMode();
} else
{
} else {
fullScreenMode = availableModes[0];
fullScreenMode.bitsPerPixel = sf::VideoMode::getDesktopMode().bitsPerPixel;
}
@ -34,8 +30,7 @@ std::shared_ptr<Game> GameFactory::createFullscreen(const std::string &title)
return Game::constructInstance(window);
}
sf::ContextSettings GameFactory::getAdditionalSettings()
{
sf::ContextSettings GameFactory::getAdditionalSettings() {
sf::ContextSettings settings = sf::ContextSettings();
settings.antialiasingLevel = ANTIALIASINGLEVEL;
@ -43,11 +38,7 @@ sf::ContextSettings GameFactory::getAdditionalSettings()
return settings;
}
void GameFactory::applyAdditionalWindowConfig(sf::RenderWindow *window)
{
if (FRAME_LIMIT_ENABLED)
{
window->setFramerateLimit(FRAME_RATE);
}
void GameFactory::applyAdditionalWindowConfig(sf::RenderWindow *window) {
window->setFramerateLimit(FRAME_RATE);
window->setKeyRepeatEnabled(KEY_REPEAT_ENABLED);
}

View file

@ -6,6 +6,12 @@ GameObject::GameObject()
children = std::vector<std::shared_ptr<GameObject>>();
}
void GameObject::addChild(const std::shared_ptr<GameObject>& child, WorldCoordinates offset)
{
children.push_back(child);
child->coordinates->setParent(coordinates, offset);
}
void GameObject::setActive(bool active)
{
// Careful: potential infinite loop!
@ -16,80 +22,3 @@ void GameObject::setActive(bool active)
child->setActive(active);
}
}
void GameObject::draw(sf::RenderWindow *window)
{
for (auto &child: children)
{
child->draw(window);
}
}
void GameObject::update()
{
for (auto &child: children)
{
child->update();
}
}
void GameObject::lateUpdate()
{
for (auto &child: children)
{
child->lateUpdate();
}
}
void GameObject::addChildScreenOffset(const std::shared_ptr<GameObject> &child, IsometricCoordinates offset)
{
auto worldOffset = CoordinateTransformer::isometricToWorld(offset);
addChildWorldOffset(child, worldOffset);
}
void GameObject::addChildWorldOffset(const std::shared_ptr<GameObject> &child, WorldCoordinates offset)
{
children.push_back(child);
child->coordinates->setParent(coordinates, offset);
}
void GameObject::addChild(const std::shared_ptr<GameObject> &child)
{
addChildWorldOffset(child, {0, 0});
}
void GameObject::removeChild(const std::shared_ptr<GameObject> &child)
{
auto it = std::find(children.begin(), children.end(), child);
if (it != children.end())
{
children.erase(it);
child->coordinates->removeParent();
}
}
void GameObject::clearChildren()
{
children.clear();
}
void GameObject::physicsUpdate()
{
for (auto &child: children)
{
child->physicsUpdate();
}
}
void GameObject::addDetachedChild(const std::shared_ptr<GameObject> &child)
{
children.push_back(child);
}
void GameObject::preRenderUpdate()
{
for (auto &child: children)
{
child->preRenderUpdate();
}
}

View file

@ -12,26 +12,20 @@ class GameObject
public:
GameObject();
virtual void draw(sf::RenderWindow *window);
virtual void draw(sf::RenderWindow *window) const
{}
virtual void update();
virtual void update()
{}
virtual void lateUpdate();
virtual void physicsUpdate();
virtual void preRenderUpdate();
virtual void lateUpdate()
{}
void setActive(bool active);
bool getActive() const { return isActive; }
void addChildScreenOffset(const std::shared_ptr<GameObject> &child, IsometricCoordinates offset = {0, 0});
void addChildWorldOffset(const std::shared_ptr<GameObject> &child, WorldCoordinates offset);
void addChild(const std::shared_ptr<GameObject> &child);
void addDetachedChild(const std::shared_ptr<GameObject> &child);
void removeChild(const std::shared_ptr<GameObject> &child);
void clearChildren();
[[nodiscard]] std::vector<std::shared_ptr<GameObject>> getChildren() const { return children; }
void addChild(const std::shared_ptr<GameObject> &child, WorldCoordinates offset = {0, 0});
std::vector<std::shared_ptr<GameObject>> getChildren() const { return children; }
std::shared_ptr<TranslatedCoordinates> coordinates;

View file

@ -1,37 +0,0 @@
#ifndef HOLESOME_BUTTON_CONFIG_HPP
#define HOLESOME_BUTTON_CONFIG_HPP
#include <vector>
#include "game_action.hpp"
#include "input_device_group.h"
#include "direction.h"
struct ButtonConfig {
std::vector<GameAction> actions;
InputDeviceGroup deviceGroup;
HardDirection direction;
bool isValid;
ButtonConfig(InputDeviceGroup group, std::vector<GameAction> actions, HardDirection direction = HardDirection::NONE) :
actions(std::move(actions)),
deviceGroup(group),
direction(direction),
isValid(true)
{}
ButtonConfig(InputDeviceGroup group, HardDirection direction) :
actions({}),
deviceGroup(group),
direction(direction),
isValid(true)
{}
ButtonConfig() :
actions({}),
deviceGroup(InputDeviceGroup::UNKNOWN),
direction(HardDirection::NONE),
isValid(false)
{}
};
#endif //HOLESOME_BUTTON_CONFIG_HPP

View file

@ -1,22 +0,0 @@
#include "button_config_factory.hpp"
#include "../../input_config.h"
ButtonConfig ButtonConfigFactory::fromKey(sf::Keyboard::Key key)
{
if (KEY_CONFIGS.find(key) != KEY_CONFIGS.end())
{
return KEY_CONFIGS.at(key);
}
return {};
}
ButtonConfig ButtonConfigFactory::fromGamepadButton(unsigned int button)
{
if (GAMEPAD_BUTTON_CONFIGS.find(button) != GAMEPAD_BUTTON_CONFIGS.end())
{
return GAMEPAD_BUTTON_CONFIGS.at(button);
}
return {};
}

View file

@ -1,17 +0,0 @@
#ifndef HOLESOME_BUTTON_CONFIG_FACTORY_HPP
#define HOLESOME_BUTTON_CONFIG_FACTORY_HPP
#include <SFML/Window/Keyboard.hpp>
#include "button_config.hpp"
class ButtonConfigFactory
{
public:
static ButtonConfig fromKey(sf::Keyboard::Key key);
static ButtonConfig fromGamepadButton(unsigned int button);
};
#endif //HOLESOME_BUTTON_CONFIG_FACTORY_HPP

View file

@ -3,6 +3,28 @@
#include "../../utilities/vector_utils.hpp"
#include "../../config.h"
HardDirection Direction::getKeyDirection(sf::Keyboard::Key key)
{
auto map = std::map<sf::Keyboard::Key, HardDirection>();
map[sf::Keyboard::W] = HardDirection::UP;
map[sf::Keyboard::S] = HardDirection::DOWN;
map[sf::Keyboard::A] = HardDirection::LEFT;
map[sf::Keyboard::D] = HardDirection::RIGHT;
map[sf::Keyboard::Up] = HardDirection::UP;
map[sf::Keyboard::Down] = HardDirection::DOWN;
map[sf::Keyboard::Left] = HardDirection::LEFT;
map[sf::Keyboard::Right] = HardDirection::RIGHT;
map[sf::Keyboard::I] = HardDirection::UP;
map[sf::Keyboard::K] = HardDirection::DOWN;
map[sf::Keyboard::J] = HardDirection::LEFT;
map[sf::Keyboard::L] = HardDirection::RIGHT;
if (map.find(key) == map.end())
return HardDirection::NONE;
return map[key];
}
sf::Vector2f Direction::getScreenVector(HardDirection direction)
{
auto vector = getVector(direction);

View file

@ -22,6 +22,8 @@ public:
explicit Direction(HardDirection direction);
static HardDirection getKeyDirection(sf::Keyboard::Key key);
static sf::Vector2f getVector(HardDirection direction);
static sf::Vector2f getScreenVector(HardDirection direction);

View file

@ -1,10 +0,0 @@
#ifndef HOLESOME_GAME_ACTION_HPP
#define HOLESOME_GAME_ACTION_HPP
enum GameAction
{
CONFIRM,
RUN
};
#endif //HOLESOME_GAME_ACTION_HPP

View file

@ -1,19 +0,0 @@
#ifndef HOLESOME_GAME_ACTION_CONFIG_HPP
#define HOLESOME_GAME_ACTION_CONFIG_HPP
enum InteractionMode
{
PRESS,
HOLD
};
struct GameActionConfig
{
InteractionMode mode;
GameActionConfig(InteractionMode mode) :
mode(mode)
{}
};
#endif //HOLESOME_GAME_ACTION_CONFIG_HPP

View file

@ -1,23 +0,0 @@
#ifndef HOLESOME_GAMEPAD_BUTTONS_HPP
#define HOLESOME_GAMEPAD_BUTTONS_HPP
enum GamepadButton {
SOUTH = 0,
EAST = 1,
NORTH = 2,
WEST = 3,
LEFT_SHOULDER = 4,
RIGHT_SHOULDER = 5,
LEFT_TRIGGER = 6,
RIGHT_TRIGGER = 7,
SELECT = 8,
START = 9,
HOME = 10,
LEFT_STICK = 11,
RIGHT_STICK = 12
};
#endif //HOLESOME_GAMEPAD_BUTTONS_HPP

View file

@ -1,6 +1,7 @@
#ifndef HOLESOME_INPUT_DEVICE_GROUP_H
#define HOLESOME_INPUT_DEVICE_GROUP_H
#include "direction.h"
#include <set>
enum InputDeviceGroup

View file

@ -4,18 +4,13 @@
#include <set>
#include "direction.h"
#include "input_device_group.h"
#include "key_features.hpp"
#include "../../utilities/magic_enum.hpp"
#include "../../logging/easylogging++.h"
#include "../../input_config.h"
#include <SFML/Window/Joystick.hpp>
struct InputIdentity
{
std::set<GameAction> current_actions = {};
std::set<GameAction> previous_actions = {};
Direction direction = Direction();
InputDeviceGroup deviceGroup = InputDeviceGroup::UNKNOWN;
unsigned int gamepadId = 0;
bool isActive = true;
@ -26,60 +21,25 @@ struct InputIdentity
gamepadId = gamepad;
LOG(INFO) << "Created input identity [" << magic_enum::enum_name(type) << ", gamepad " << gamepadId << "]";
if (type == InputDeviceGroup::GAMEPAD)
{
auto numberOfButtons = sf::Joystick::getButtonCount(gamepadId);
LOG(INFO) << "Gamepad has " << numberOfButtons << " buttons";
}
};
bool isPerformingAction(GameAction action)
static InputDeviceGroup getDeviceTypeFromEvent(sf::Event event)
{
bool currently_performing = current_actions.find(action) != current_actions.end();
bool previously_performing = previous_actions.find(action) != previous_actions.end();
auto action_config = GAME_ACTION_CONFIGS.at(action);
if (action_config.mode == InteractionMode::PRESS)
switch (event.type)
{
return currently_performing && !previously_performing;
} else if (action_config.mode == InteractionMode::HOLD)
{
return currently_performing;
} else
{
LOG(ERROR) << "Unknown interaction mode [" << magic_enum::enum_name(action_config.mode) << "]";
return false;
case sf::Event::KeyPressed:
case sf::Event::KeyReleased:
return KeyFeatures(event.key.code).deviceGroup;
case sf::Event::JoystickButtonPressed:
case sf::Event::JoystickButtonReleased:
case sf::Event::JoystickConnected:
case sf::Event::JoystickDisconnected:
case sf::Event::JoystickMoved:
return InputDeviceGroup::GAMEPAD;
default:
return InputDeviceGroup::UNKNOWN;
}
}
void press(const ButtonConfig &button)
{
for (auto &action: button.actions)
{
current_actions.insert(action);
}
if (button.direction != HardDirection::NONE) {
direction.add(button.direction);
}
}
void release(const ButtonConfig &button)
{
for (auto &action: button.actions)
{
current_actions.erase(action);
}
if (button.direction != HardDirection::NONE) {
direction.remove(button.direction);
}
}
void update()
{
previous_actions = current_actions;
}
};

View file

@ -1,6 +1,5 @@
#include "input_mapper.h"
#include "input_device_group.h"
#include "button_config_factory.hpp"
InputMapper::InputMapper()
@ -27,13 +26,7 @@ void InputMapper::processEvents()
case sf::Event::Closed:
Game::getInstance()->exit();
break;
case sf::Event::JoystickButtonPressed:
getInputIdentity(InputDeviceGroup::GAMEPAD, event.joystickButton.joystickId)->press(
ButtonConfigFactory::fromGamepadButton(event.joystickButton.button));
break;
case sf::Event::JoystickButtonReleased:
getInputIdentity(InputDeviceGroup::GAMEPAD, event.joystickButton.joystickId)->release(
ButtonConfigFactory::fromGamepadButton(event.joystickButton.button));
case sf::Event::Resized:
break;
case sf::Event::JoystickMoved:
handleJoystickMovement(event.joystickMove);
@ -52,28 +45,28 @@ void InputMapper::processEvents()
void InputMapper::handleKeyPress(sf::Event::KeyEvent event)
{
// Close game on Special button
if (event.code == CLOSE_GAME_BTN)
// Close game on Escape or Q in DEV Mode
if (DEVELOPER_MODE && (event.code == sf::Keyboard::Escape || event.code == sf::Keyboard::Q))
{
Game::getInstance()->exit();
return;
}
// Handle directionVector
auto button = ButtonConfigFactory::fromKey(event.code);
if (button.deviceGroup != InputDeviceGroup::UNKNOWN)
auto feature = KeyFeatures(event.code);
if (feature.deviceGroup != InputDeviceGroup::UNKNOWN)
{
getInputIdentity(button.deviceGroup)->press(button);
getInputIdentity(feature.deviceGroup)->direction.add(feature.hardDirection);
return;
}
}
void InputMapper::handleKeyRelease(sf::Event::KeyEvent event)
{
auto button = ButtonConfigFactory::fromKey(event.code);
if (button.deviceGroup != InputDeviceGroup::UNKNOWN)
auto feature = KeyFeatures(event.code);
if (feature.deviceGroup != InputDeviceGroup::UNKNOWN)
{
getInputIdentity(button.deviceGroup)->release(button);
getInputIdentity(feature.deviceGroup)->direction.remove(feature.hardDirection);
return;
}
}
@ -82,7 +75,7 @@ void InputMapper::handleJoystickMovement(sf::Event::JoystickMoveEvent event)
{
auto gamepadIdentity = getInputIdentity(InputDeviceGroup::GAMEPAD, event.joystickId);
auto value = event.position / 100.f; // Normalize to -1 to 1
auto value = event.position / 100.f;
auto axis = event.axis;
// Handle deadzone and joystick drift
@ -121,13 +114,8 @@ std::shared_ptr<InputIdentity> InputMapper::getInputIdentity(InputDeviceGroup de
// No identity found, create new
auto newIdentity = std::make_shared<InputIdentity>(deviceGroup, gamepadId);
// Only add to newInputIdentities if it is allowed, otherwise it will be discarded when possible anyway
if (allowNewInputIdentities) {
inputIdentities.insert(newIdentity);
newInputIdentities.insert(newIdentity);
}
inputIdentities.insert(newIdentity);
newInputIdentities.insert(newIdentity);
return newIdentity;
}
@ -157,13 +145,8 @@ std::set<std::shared_ptr<InputIdentity>> InputMapper::getInputIdentities()
return inputIdentities;
}
void InputMapper::updateIdentities()
void InputMapper::updateIdentityEvents()
{
for (auto &identity: inputIdentities)
{
identity->update();
}
newInputIdentities.clear();
deprecatedInputIdentities.clear();
}

View file

@ -19,14 +19,12 @@ public:
static std::shared_ptr<InputMapper> getInstance();
void processEvents();
void updateIdentities();
void updateIdentityEvents();
std::shared_ptr<InputIdentity> getInputIdentity(InputDeviceGroup deviceGroup, unsigned int gamepadId = 0);
std::set<std::shared_ptr<InputIdentity>> getInputIdentities();
bool allowNewInputIdentities = true;
public:
std::set<std::shared_ptr<InputIdentity>> newInputIdentities;
std::set<std::shared_ptr<InputIdentity>> deprecatedInputIdentities;

View file

@ -0,0 +1,34 @@
#ifndef HOLESOME_KEY_FEATURES_HPP
#define HOLESOME_KEY_FEATURES_HPP
#include <memory>
#include "direction.h"
#include "input_identity.h"
#include "../../config.h"
#include "input_device_group.h"
struct KeyFeatures
{
sf::Keyboard::Key key;
HardDirection hardDirection = HardDirection::NONE;
InputDeviceGroup deviceGroup = InputDeviceGroup::UNKNOWN;
explicit KeyFeatures(sf::Keyboard::Key key)
{
this->key = key;
hardDirection = Direction::getKeyDirection(key);
// Determine device group
for (const auto &groupKeys: KEY_GROUPS)
{
if (groupKeys.second.contains(key))
{
deviceGroup = groupKeys.first;
break;
}
}
}
};
#endif //HOLESOME_KEY_FEATURES_HPP

View file

@ -0,0 +1,5 @@
//
// Created by max on 27.04.23.
//
#include "input_handler.h"

14
src/game/input_handler.h Normal file
View file

@ -0,0 +1,14 @@
//
// Created by max on 27.04.23.
//
#ifndef HOLESOME_INPUT_HANDLER_H
#define HOLESOME_INPUT_HANDLER_H
class InputHandler {
};
#endif //HOLESOME_INPUT_HANDLER_H

View file

@ -1,39 +0,0 @@
#include "global_layer.hpp"
void GlobalLayer::clear()
{
clearChildren();
addDetachedChild(view);
}
std::shared_ptr<GlobalLayer> GlobalLayer::getInstance()
{
if (singletonInstance == nullptr)
{
singletonInstance = std::make_shared<GlobalLayer>();
}
return singletonInstance;
}
void GlobalLayer::draw(sf::RenderWindow *window)
{
view->setViewForWindow();
GameObject::draw(window);
}
GlobalLayer::GlobalLayer()
{
// Reference screen size of 1920x1080
TrackingViewOptions options = {
.minViewSize = sf::Vector2f{10, REFERENCE_SIZE.y},
.initialCenter = REFERENCE_SIZE / 2.0f,
};
view = std::make_shared<TrackingView>(options);
addDetachedChild(view);
}
void GlobalLayer::add(const std::shared_ptr<GameObject>& gameObject)
{
addDetachedChild(gameObject);
}

View file

@ -1,27 +0,0 @@
#ifndef HOLESOME_GLOBAL_LAYER_HPP
#define HOLESOME_GLOBAL_LAYER_HPP
#include "../game_object.h"
#include "../camera/tracking_view.h"
class GlobalLayer : public GameObject
{
public:
GlobalLayer();
static std::shared_ptr<GlobalLayer> getInstance();
void clear();
void draw(sf::RenderWindow *window) override;
void add(const std::shared_ptr<GameObject>& gameObject);
private:
static inline std::shared_ptr<GlobalLayer> singletonInstance = nullptr;
std::shared_ptr<TrackingView> view;
};
#endif //HOLESOME_GLOBAL_LAYER_HPP

View file

@ -1,56 +0,0 @@
#ifndef HOLESOME_LEVEL_CONFIG_HPP
#define HOLESOME_LEVEL_CONFIG_HPP
#include <SFML/System/Vector2.hpp>
#include "../../coordinates/coordinates.h"
#include "../../logging/easylogging++.h"
#include "../collectables/collectable_in_level.hpp"
#include <vector>
#include "../../sprites/tiling/tilemap_config.hpp"
#include "../../sprites/sprite_factory.hpp"
struct LevelConfig
{
std::string name;
int durationInSeconds = 0;
sf::Vector2i worldMapSize = {};
TileMapConfig tileMapConfig = {};
std::vector<GridCoordinates> playerSpawnPoints = {};
std::vector<CollectableInLevel> collectables = {};
std::vector<sf::Color> skyColors = {};
LevelConfig(std::string name,
int durationInSeconds,
const std::vector<GridCoordinates> &playerSpawnPoints,
const std::vector<CollectableInLevel> &collectables,
std::vector<sf::Color> skyColors,
const TileMapConfig &tileMapConfig)
: name(std::move(name)),
durationInSeconds(durationInSeconds),
playerSpawnPoints(playerSpawnPoints),
tileMapConfig(tileMapConfig),
skyColors(std::move(skyColors))
{
worldMapSize = tileMapConfig.getSize();
// Remove invalid collectables
for (auto &collectable: collectables)
{
if (collectable.isValid())
{
this->collectables.push_back(collectable);
}
}
}
LevelConfig() = default;
bool isValid() const
{
return !name.empty();
}
};
#endif //HOLESOME_LEVEL_CONFIG_HPP

View file

@ -1,134 +0,0 @@
#include "level_generator.hpp"
LevelConfig LevelGenerator::generateLevel(const std::string &name)
{
int levelSize = 50;
TileMapConfig map = generateRandomMap(levelSize, levelSize);
std::vector<CollectableInLevel> collectables = generateRandomCollectables(levelSize, levelSize);
return LevelConfig(name,
90,
{
{1, 2},
{2, 1},
{0, 3},
{3, 0}
},
collectables,
{
// Blues
sf::Color(2, 100, 234),
sf::Color(2, 100, 234),
sf::Color(2, 100, 234),
sf::Color(2, 195, 234),
// Neutral
sf::Color::White,
// Browns
sf::Color(163, 128, 68),
sf::Color(100, 80, 40),
sf::Color(20, 18, 11),
sf::Color::Black
},
map
);
}
TileMapConfig LevelGenerator::generateRandomMap(int width, int height)
{
int tileVariants = 6 * 2;
std::vector<std::vector<int>> tiles;
for (int h = 0; h < height; h++)
{
std::vector<int> row;
for (int w = 0; w < width; w++)
{
row.push_back(rand() % tileVariants);
}
tiles.push_back(row);
}
return TileMapConfig("iso-tiles", tiles);
}
std::vector<CollectableInLevel> LevelGenerator::generateRandomCollectables(int width, int height)
{
std::vector<CollectableInLevel> collectables;
// Add rose fields
for (int i = 0; i < 20; i++)
{
// Random coordinates for center of rose field with enough distance from edges
int x = rand() % int(width - 2 * roseFieldRadius) + roseFieldRadius;
int y = rand() % int(height - 2 * roseFieldRadius) + roseFieldRadius;
GridCoordinates fieldCenter = GridCoordinates(x, y);
// Add rose field by creating a dense field of roses around the center
int roseCount = rand() % 15 + 5;
for (int j = 0; j < roseCount; j++)
{
// Use cosine distribution to get a more dense field around the center
float angle = rand() % 360;
float radius = rand() % 1000 / 1000.f;
// Skew radius to avoid too dense at center
radius = pow(radius, 0.3f) * roseFieldRadius;
float xOffset = radius * cos(angle);
float yOffset = radius * sin(angle);
GridCoordinates roseCoordinates = GridCoordinates(fieldCenter.x + xOffset, fieldCenter.y + yOffset);
collectables.push_back(CollectableInLevel("rose", roseCoordinates));
}
}
// Place rows of lanterns
int rowCount = rand() % 3 + 2;
int heightDelta = height / (rowCount + 1);
int stepSize = 7;
for (int i = 0; i < rowCount; i++)
{
int y = (i + 1) * heightDelta;
for (int j = stepSize; j < width; j += stepSize)
{
collectables.push_back(CollectableInLevel("lantern", GridCoordinates(j, y)));
}
}
// Place random bikes
int bikeCount = rand() % 5 + 5;
for (int i = 0; i < bikeCount; i++)
{
int x = rand() % width;
int y = rand() % height;
collectables.push_back(CollectableInLevel("bike", GridCoordinates(x, y)));
}
// Place random rose bushes
int roseBushCount = rand() % 40 + 40;
for (int i = 0; i < roseBushCount; i++)
{
int x = rand() % width;
int y = rand() % height;
collectables.push_back(CollectableInLevel("rose-bush", GridCoordinates(x, y)));
}
// Place random stones
int stoneCount = rand() % 20 + 20;
for (int i = 0; i < stoneCount; i++)
{
int x = rand() % width;
int y = rand() % height;
collectables.push_back(CollectableInLevel("stone", GridCoordinates(x, y)));
}
// Place a single tree
int x = rand() % width;
int y = rand() % height;
collectables.push_back(CollectableInLevel("small-tree", GridCoordinates(x, y)));
return collectables;
}

View file

@ -1,21 +0,0 @@
#ifndef HOLESOME_LEVEL_GENERATOR_HPP
#define HOLESOME_LEVEL_GENERATOR_HPP
#include "level_config.hpp"
class LevelGenerator
{
public:
static LevelConfig generateLevel(const std::string& name);
static TileMapConfig generateRandomMap(int width, int height);
static std::vector<CollectableInLevel> generateRandomCollectables(int width, int height);
private:
static const inline float roseFieldRadius = 3.f;
};
#endif //HOLESOME_LEVEL_GENERATOR_HPP

View file

@ -1,110 +0,0 @@
#include "level_loader.hpp"
#include "../game.h"
#include "../physics/map/map_simulation.hpp"
#include "../../debug/grid_debug_layer.h"
#include "../../levels.hpp"
#include "../collectables/collection/collectables_collection.hpp"
#include "../collectables/collectable_factory.hpp"
#include "../player/player_collection.hpp"
#include "level_renderer.hpp"
#include "../../sprites/skymap/skymap.hpp"
#include "../camera/multiplayer_view.hpp"
#include "../physics/holes/holes_simulation.hpp"
#include "../physics/holes/layouts/hole_layout.hpp"
#include "../layer/global_layer.hpp"
void LevelLoader::loadLevel(const LevelConfig &levelConfig)
{
auto game = Game::getInstance();
game->clearGameObjects();
LOG(INFO) << "Loading level '" << levelConfig.name << "' ...";
game->setLevel(levelConfig);
InputMapper::getInstance()->allowNewInputIdentities = false;
MapSimulation::getInstance()->resetMap(levelConfig.worldMapSize);
HolesSimulation::getInstance()->clear();
GlobalLayer::getInstance()->clear();
HoleLayout::getInstance()->clear();
MultiplayerView::getInstance()->clear();
// Add views
game->addGameObject(MultiplayerView::getInstance());
// Add rendered level objects
std::shared_ptr<LevelRenderer> levelRenderer = std::make_shared<LevelRenderer>();
game->addGameObject(levelRenderer);
levelRenderer->addChild(std::make_shared<Skymap>(levelConfig.skyColors, levelConfig.worldMapSize));
levelRenderer->addChild(SpriteFactory::createTileMap(levelConfig.tileMapConfig));
if (DB_WORLD_GRID_RENDER)
{
levelRenderer->addChild(std::make_shared<GridDebugLayer>(0, 50, 0, 50));
}
levelRenderer->addChild(PlayerCollection::getInstance());
PlayerCollection::getInstance()->setSpawnPoints(levelConfig.playerSpawnPoints);
PlayerCollection::getInstance()->resetPlayers();
PlayerCollection::getInstance()->activatePlayers();
// Prepare collectables framework
auto maxDepth = (int) levelConfig.worldMapSize.x * 2;
auto collectablesCollection = CollectablesCollection::getInstance();
collectablesCollection->createEmpty(maxDepth);
levelRenderer->addChild(collectablesCollection);
// Add physics simulations
game->addGameObject(MapSimulation::getInstance());
game->addGameObject(HolesSimulation::getInstance());
game->addGameObject(HoleLayout::getInstance());
// Spawn collectibles
for (auto const &collectableInfo: levelConfig.collectables)
{
spawnCollectable(collectableInfo);
}
game->startCountdown(levelConfig.durationInSeconds);
// Must be last
game->addGameObject(GlobalLayer::getInstance());
LOG(INFO) << "Finished loading level '" << levelConfig.name << "'.";
}
void LevelLoader::loadLevel(const std::string &levelName)
{
// Does level exist?
if (!all_levels.contains(levelName))
{
LOG(ERROR) << "Level '" << levelName << "' not found. Could not load it.";
throw std::invalid_argument("Could not load level.");
}
LevelLoader::loadLevel(all_levels.at(levelName));
}
void LevelLoader::spawnCollectable(const CollectableInLevel &collectableInfo)
{
LOG(INFO) << "Spawning collectable '" << collectableInfo.name << "' ...";
auto collectable = CollectableFactory::createFromInLevelConfig(collectableInfo);
LOG(INFO) << "Has depth " << collectable->getDepth() << ".";
CollectablesCollection::getInstance()->add(collectable);
HolesSimulation::getInstance()->addCollectable(collectable);
}
void LevelLoader::cleanUp()
{
auto game = Game::getInstance();
game->clearGameObjects();
game->setLevel(LevelConfig());
HolesSimulation::getInstance()->clear();
PlayerCollection::getInstance()->resetPlayers();
GlobalLayer::getInstance()->clear();
HoleLayout::getInstance()->clear();
LOG(INFO) << "Cleaned up level.";
}

View file

@ -1,21 +0,0 @@
#ifndef HOLESOME_LEVEL_LOADER_HPP
#define HOLESOME_LEVEL_LOADER_HPP
#include <memory>
#include "level_config.hpp"
class LevelLoader
{
public:
static void loadLevel(const LevelConfig& levelConfig);
static void loadLevel(const std::string &levelName);
static void cleanUp();
static void spawnCollectable(const CollectableInLevel &collectableInfo);
};
#endif //HOLESOME_LEVEL_LOADER_HPP

View file

@ -1,15 +0,0 @@
#include "level_renderer.hpp"
#include "../camera/multiplayer_view.hpp"
#include "../../logging/easylogging++.h"
void LevelRenderer::draw(sf::RenderWindow *window)
{
for(auto &view : MultiplayerView::getInstance()->getViews())
{
view->setViewForWindow();
GameObject::draw(window);
}
// Render borders between views
MultiplayerView::getInstance()->draw(window);
}

View file

@ -1,14 +0,0 @@
#ifndef HOLESOME_LEVEL_RENDERER_HPP
#define HOLESOME_LEVEL_RENDERER_HPP
#include "../game_object.h"
class LevelRenderer : public GameObject
{
public:
void draw(sf::RenderWindow *window) override;
};
#endif //HOLESOME_LEVEL_RENDERER_HPP

View file

@ -1,78 +0,0 @@
#include "body_adapter.hpp"
#include "../../config.h"
BodyAdapter::BodyAdapter(std::shared_ptr<b2World> world)
: world(std::move(world)), bodyObject(nullptr)
{}
BodyAdapter::~BodyAdapter()
{
destroyBody();
}
void BodyAdapter::destroyBody()
{
if (bodyObject != nullptr)
{
world->DestroyBody(bodyObject);
}
}
void BodyAdapter::createSquare(b2BodyType type, sf::Vector2f center, sf::Vector2f size)
{
destroyBody();
b2BodyDef bodyDef;
bodyDef.type = type;
// bodyDef.angularDamping = COLLECTABLES_SIM_ANGULAR_DAMPING;
// bodyDef.linearDamping = COLLECTABLES_SIM_LINEAR_DAMPING;
bodyDef.position.Set(center.x, center.y);
bodyObject = world->CreateBody(&bodyDef);
setBoxSize(size);
}
b2Body *BodyAdapter::body() const
{
return bodyObject;
}
sf::Vector2f BodyAdapter::getCenter() const
{
auto position = bodyObject->GetPosition();
return {position.x, position.y};
}
sf::Vector2f BodyAdapter::getSize() const
{
return boundingBox;
}
void BodyAdapter::setCenter(sf::Vector2f center)
{
bodyObject->SetTransform({center.x, center.y}, bodyObject->GetAngle());
}
void BodyAdapter::setBoxSize(sf::Vector2f size)
{
// Remove old fixture
if (bodyObject->GetFixtureList() != nullptr)
{
bodyObject->DestroyFixture(bodyObject->GetFixtureList());
}
// Recreates a square fixture
b2PolygonShape shape;
shape.SetAsBox(size.x / 2.f, size.y / 2.f);
boundingBox = size;
b2FixtureDef fixtureDef;
fixtureDef.shape = &shape;
// fixtureDef.friction = COLLECTABLES_SIM_FRICTION;
// fixtureDef.restitution = COLLECTABLES_SIM_RESTITUTION;
fixtureDef.density = COLLECTABLES_SIM_DENSITY;
bodyObject->CreateFixture(&fixtureDef);
}

View file

@ -1,37 +0,0 @@
#ifndef HOLESOME_BODY_ADAPTER_HPP
#define HOLESOME_BODY_ADAPTER_HPP
#include <box2d/box2d.h>
#include <memory>
#include <SFML/System/Vector2.hpp>
class BodyAdapter
{
public:
explicit BodyAdapter(std::shared_ptr<b2World> world);
~BodyAdapter();
[[nodiscard]] b2Body *body() const;
void createSquare(b2BodyType type, sf::Vector2f center, sf::Vector2f size);
[[nodiscard]] sf::Vector2f getCenter() const;
void setCenter(sf::Vector2f center);
[[nodiscard]] sf::Vector2f getSize() const;
void setBoxSize(sf::Vector2f size);
private:
std::shared_ptr<b2World> world;
b2Body *bodyObject;
sf::Vector2f boundingBox;
void destroyBody();
};
#endif //HOLESOME_BODY_ADAPTER_HPP

View file

@ -1,52 +0,0 @@
#include "collectable_simulation.hpp"
#include "../../../logging/easylogging++.h"
#include "../../../config.h"
#include "layouts/hole_layout.hpp"
CollectableSimulation::CollectableSimulation(const std::shared_ptr<Collectable> &collectable)
: collectable(collectable), simulationDepth(collectable->getDepth())
{
// Create simulation
world = std::make_shared<b2World>(WORLD_GRAVITY);
world->SetAllowSleeping(COLLECTABLES_SIM_SLEEPING);
// Create ground
simGround = std::make_shared<CollectableSimGround>(world);
// Todo: Set proper ground width
// Create body
collectableBody = std::make_shared<BodyAdapter>(world);
auto coordinates = collectable->coordinates->diagonalWorld();
collectableBody->createSquare(b2_dynamicBody, {coordinates.horizontal, coordinates.vertical}, collectable->getSize());
}
void CollectableSimulation::physicsUpdate()
{
updateGroundHole();
world->Step(FRAME_TIME.asSeconds(), COLLECTABLES_SIM_VELOCITY_ITERATIONS, COLLECTABLES_SIM_POSITION_ITERATIONS);
updateCollectable();
}
std::shared_ptr<Collectable> CollectableSimulation::getCollectable() const
{
return collectable;
}
void CollectableSimulation::updateGroundHole()
{
auto holeLayout = HoleLayout::getInstance()->atDepth(simulationDepth);
simGround->createLayout(holeLayout);
}
void CollectableSimulation::updateCollectable()
{
auto bodyPosition = collectableBody->body()->GetPosition();
collectable->coordinates->setDiagonal({bodyPosition.x, bodyPosition.y, simulationDepth});
// Convert radians to degrees
float angle = -collectableBody->body()->GetAngle() * 180 / M_PI;
collectable->setRotation(angle);
}

View file

@ -1,37 +0,0 @@
#ifndef HOLESOME_COLLECTABLE_SIMULATION_HPP
#define HOLESOME_COLLECTABLE_SIMULATION_HPP
#include <box2d/box2d.h>
#include <map>
#include "../../game_object.h"
#include "../../collectables/collectable.hpp"
#include "ground/collectable_sim_ground_segment.hpp"
#include "ground/collectable_sim_ground.hpp"
#include "../body_adapter.hpp"
class CollectableSimulation : public GameObject
{
public:
explicit CollectableSimulation(const std::shared_ptr<Collectable> &collectable);
void physicsUpdate() override;
[[nodiscard]] std::shared_ptr<Collectable> getCollectable() const;
private:
std::shared_ptr<b2World> world;
std::shared_ptr<BodyAdapter> collectableBody;
float simulationDepth;
std::shared_ptr<Collectable> collectable;
std::shared_ptr<CollectableSimGround> simGround;
void updateGroundHole();
void updateCollectable();
};
#endif //HOLESOME_COLLECTABLE_SIMULATION_HPP

View file

@ -1,193 +0,0 @@
#include "collectable_sim_ground.hpp"
#include <utility>
#include "../../../../config.h"
void CollectableSimGround::closeAllHoles()
{
// Create one segment for the ground
createSegment(-groundWidth / 2.f, groundWidth / 2.f);
}
void CollectableSimGround::createLayout(DepthHoleLayout &layout)
{
if (!hasLayoutChanged(layout))
{
return;
}
currentLayoutHoles = layout.holes;
segments.clear();
if (currentLayoutHoles.empty())
{
closeAllHoles();
return;
}
// Sort holes from left to right
std::sort(currentLayoutHoles.begin(), currentLayoutHoles.end(),
[](DepthHoleDescription a, DepthHoleDescription b)
{
return a.x < b.x;
});
// Create segments for holes
float leftCorner = -groundWidth / 2.f;
float leftHoleId = -1;
for (auto &hole: currentLayoutHoles)
{
auto rightCorner = hole.x - hole.width / 2.f;
auto segment = createSegment(leftCorner, rightCorner);
if (segment != nullptr)
{
segment->rightHoleId = hole.playerId;
segment->leftHoleId = leftHoleId;
}
leftHoleId = hole.playerId;
leftCorner = hole.x + hole.width / 2.f;
}
// Create segment for the right side
auto segment = createSegment(leftCorner, groundWidth / 2.f);
if (segment != nullptr)
{
segment->leftHoleId = leftHoleId;
}
}
CollectableSimGround::CollectableSimGround(std::shared_ptr<b2World> world, float groundWidth)
: world(std::move(world)), groundWidth(groundWidth)
{
closeAllHoles();
}
void CollectableSimGround::setGroundWidth(float width)
{
if (width <= 0)
{
throw std::runtime_error("Ground width must be greater than 0");
}
if (width == groundWidth)
{
return;
}
groundWidth = width;
updateOuterSegmentsToWidth();
}
void CollectableSimGround::updateOuterSegmentsToWidth()
{
auto outerSegments = getOuterSegments();
auto borderPoints = groundWidth / 2.f;
// Left segment
if (outerSegments.left != nullptr)
{
auto leftSegment = outerSegments.left->body;
auto leftCenter = leftSegment->getCenter();
auto leftSize = leftSegment->getSize();
auto rightPoint = leftCenter.x + leftSize.x / 2.f;
createSegment(-borderPoints, rightPoint, outerSegments.left);
}
// Right segment
if (outerSegments.right != nullptr)
{
auto rightSegment = outerSegments.right->body;
auto rightCenter = rightSegment->getCenter();
auto rightSize = rightSegment->getSize();
auto leftPoint = rightCenter.x - rightSize.x / 2.f;
createSegment(leftPoint, borderPoints, outerSegments.right);
}
}
std::shared_ptr<CollectableSimGroundSegment> CollectableSimGround::createSegment(float leftCorner, float rightCorner,
std::shared_ptr<CollectableSimGroundSegment> segment)
{
if (leftCorner > rightCorner)
{
// Segment would have negative width, don't create it and leave empty instead
return segment;
}
if (segment == nullptr)
{
segment = std::make_shared<CollectableSimGroundSegment>(world);
segments.push_back(segment);
}
auto center = sf::Vector2f((leftCorner + rightCorner) / 2.f, -COLLECTABLES_SIM_GROUND_THICKNESS / 2.f);
auto size = sf::Vector2f(rightCorner - leftCorner, COLLECTABLES_SIM_GROUND_THICKNESS);
segment->body->createSquare(b2_kinematicBody, center, size);
return segment;
}
CollectableSimGround::SideSegments CollectableSimGround::getOuterSegments()
{
SideSegments sideSegments;
for (const auto &segment: segments)
{
if (segment->rightHoleId < 0)
{
sideSegments.right = segment;
}
if (segment->leftHoleId < 0)
{
sideSegments.left = segment;
}
if (sideSegments.left != nullptr && sideSegments.right != nullptr)
{
break;
}
}
return sideSegments;
}
bool CollectableSimGround::hasLayoutChanged(DepthHoleLayout &layout)
{
if (layout.holes.size() != currentLayoutHoles.size())
{
// Number of holes changed
return true;
}
// Below here: Number of holes is the same
if (currentLayoutHoles.empty())
{
// Both are empty, so no change
return false;
}
// Sort holes from left to right
std::sort(layout.holes.begin(), layout.holes.end(),
[](DepthHoleDescription a, DepthHoleDescription b)
{
return a.x < b.x;
});
// Compare holes
for (int i = 0; i < layout.holes.size(); i++)
{
auto currentHole = currentLayoutHoles[i];
auto newHole = layout.holes[i];
if (newHole.x != currentHole.x ||
newHole.width != currentHole.width)
{
// Hole changed
return true;
}
}
return false;
}

View file

@ -1,43 +0,0 @@
#ifndef HOLESOME_COLLECTABLE_SIM_GROUND_HPP
#define HOLESOME_COLLECTABLE_SIM_GROUND_HPP
#include <vector>
#include <memory>
#include "collectable_sim_ground_segment.hpp"
#include "../layouts/depth_hole_layout.hpp"
class CollectableSimGround
{
public:
explicit CollectableSimGround(std::shared_ptr<b2World> world, float groundWidth = 1000);
void setGroundWidth(float width);
void createLayout(DepthHoleLayout &layout);
private:
std::vector<std::shared_ptr<CollectableSimGroundSegment>> segments;
std::shared_ptr<b2World> world;
std::vector<DepthHoleDescription> currentLayoutHoles;
float groundWidth;
struct SideSegments {
std::shared_ptr<CollectableSimGroundSegment> left;
std::shared_ptr<CollectableSimGroundSegment> right;
};
CollectableSimGround::SideSegments getOuterSegments();
void updateOuterSegmentsToWidth();
std::shared_ptr<CollectableSimGroundSegment> createSegment(float leftCorner, float rightCorner, std::shared_ptr<CollectableSimGroundSegment> segment = nullptr);
void closeAllHoles();
bool hasLayoutChanged(DepthHoleLayout &layout);
};
#endif //HOLESOME_COLLECTABLE_SIM_GROUND_HPP

View file

@ -1,22 +0,0 @@
#ifndef HOLESOME_COLLECTABLE_SIM_GROUND_SEGMENT_HPP
#define HOLESOME_COLLECTABLE_SIM_GROUND_SEGMENT_HPP
#include <box2d/box2d.h>
#include <memory>
#include "../layouts/depth_hole_description.hpp"
#include "../../body_adapter.hpp"
class CollectableSimGroundSegment
{
public:
explicit CollectableSimGroundSegment(const std::shared_ptr<b2World>& world)
{
body = std::make_shared<BodyAdapter>(world);
}
int leftHoleId = -1;
int rightHoleId = -1;
std::shared_ptr<BodyAdapter> body;
};
#endif //HOLESOME_COLLECTABLE_SIM_GROUND_SEGMENT_HPP

View file

@ -1,63 +0,0 @@
#include "holes_simulation.hpp"
std::shared_ptr<HolesSimulation> HolesSimulation::getInstance()
{
if (singletonInstance == nullptr)
{
singletonInstance = std::make_shared<HolesSimulation>();
}
return singletonInstance;
}
std::vector<std::shared_ptr<CollectableSimulation>> HolesSimulation::getCollectableSimulations() const
{
std::vector<std::shared_ptr<CollectableSimulation>> collectableSimulations{};
for (auto &child: getChildren())
{
auto sim = std::dynamic_pointer_cast<CollectableSimulation>(child);
collectableSimulations.push_back(sim);
}
return collectableSimulations;
}
void HolesSimulation::clear()
{
clearChildren();
}
void HolesSimulation::addCollectable(const std::shared_ptr<Collectable> &collectable)
{
// Create new collectable simulation
auto collectableSim = std::make_shared<CollectableSimulation>(collectable);
addDetachedChild(collectableSim);
}
void HolesSimulation::lateUpdate()
{
// Remove disabled collectables
for (const auto &sim: getCollectableSimulations())
{
if (sim->getCollectable()->getActive())
{
continue;
}
removeChild(sim);
}
GameObject::lateUpdate();
}
void HolesSimulation::removeCollectable(int collectableId)
{
for (const auto &sim: getCollectableSimulations())
{
if (sim->getCollectable()->getId() == collectableId)
{
removeChild(sim);
return;
}
}
}

View file

@ -1,29 +0,0 @@
#ifndef HOLESOME_HOLES_SIMULATION_HPP
#define HOLESOME_HOLES_SIMULATION_HPP
#include <memory>
#include "../../game_object.h"
#include "collectable_simulation.hpp"
class HolesSimulation : public GameObject
{
public:
static std::shared_ptr<HolesSimulation> getInstance();
void addCollectable(const std::shared_ptr<Collectable> &collectable);
void removeCollectable(int collectableId);
void lateUpdate() override;
void clear();
std::vector<std::shared_ptr<CollectableSimulation>> getCollectableSimulations() const;
private:
static inline std::shared_ptr<HolesSimulation> singletonInstance = nullptr;
};
#endif //HOLESOME_HOLES_SIMULATION_HPP

View file

@ -1,15 +0,0 @@
#ifndef HOLESOME_DEPTH_HOLE_DESCRIPTION_HPP
#define HOLESOME_DEPTH_HOLE_DESCRIPTION_HPP
struct DepthHoleDescription
{
int playerId;
float x;
float width;
DepthHoleDescription(int playerId, float x, float width)
: playerId(playerId), x(x), width(width)
{}
};
#endif //HOLESOME_DEPTH_HOLE_DESCRIPTION_HPP

View file

@ -1,38 +0,0 @@
#ifndef HOLESOME_DEPTH_HOLE_LAYOUT_HPP
#define HOLESOME_DEPTH_HOLE_LAYOUT_HPP
#include <vector>
#include "depth_hole_description.hpp"
#include "../../../../logging/easylogging++.h"
struct DepthHoleLayout
{
float depth;
std::vector<DepthHoleDescription> holes;
DepthHoleLayout(float depth, std::vector<DepthHoleDescription> holes)
: depth(depth), holes(std::move(holes))
{}
[[nodiscard]] DepthHoleDescription getHole(int playerId) const
{
for (const auto &hole: holes)
{
if (hole.playerId == playerId)
{
return hole;
}
}
throw std::runtime_error(
"No hole for player " + std::to_string(playerId) + " at depth " + std::to_string(depth) +
" in layout.");
}
[[nodiscard]] bool hasHole(int playerId) const
{
return std::ranges::any_of(holes, [playerId](const auto &hole) { return hole.playerId == playerId; });
}
};
#endif //HOLESOME_DEPTH_HOLE_LAYOUT_HPP

View file

@ -1,44 +0,0 @@
#ifndef HOLESOME_HOLE_DESCRIPTION_HPP
#define HOLESOME_HOLE_DESCRIPTION_HPP
#include <SFML/System/Vector2.hpp>
#include "../../../player/player.hpp"
#include "../../../../utilities/vector_utils.hpp"
#include "depth_hole_description.hpp"
struct HoleDescription
{
int playerId;
sf::Vector2f worldPosition;
float radius;
explicit HoleDescription(const std::shared_ptr<Player> &player)
{
playerId = player->getPlayerId();
radius = player->getWorldRadius();
auto world3d = player->coordinates->world();
worldPosition = sf::Vector2f(world3d.x, world3d.y);
}
[[nodiscard]] DepthHoleDescription atDepth(float depth) const
{
auto closestPointAtDepth = CoordinateTransformer::closestWorldPointAtDepth(depth, worldPosition);
auto distance = length(worldPosition - closestPointAtDepth);
if (distance > radius)
{
// No hole at this depth
return {playerId, 0.f, 0.f};
}
float chordLength = 2.f * std::sqrt(radius * radius - distance * distance);
auto diagonalWorldCoords = CoordinateTransformer::worldToDiagonal(WorldCoordinates{worldPosition});
return {playerId, diagonalWorldCoords.horizontal, chordLength};
}
};
#endif //HOLESOME_HOLE_DESCRIPTION_HPP

View file

@ -1,50 +0,0 @@
#include "hole_layout.hpp"
#include "../../../player/player_collection.hpp"
std::shared_ptr<HoleLayout> HoleLayout::getInstance()
{
if (singletonInstance == nullptr)
{
singletonInstance = std::make_shared<HoleLayout>();
}
return singletonInstance;
}
void HoleLayout::clear()
{
currentHoles.clear();
}
void HoleLayout::lateUpdate()
{
currentHoles.clear();
// Collect hole descriptions of active players
for (const auto &player: PlayerCollection::getInstance()->getPlayers())
{
if (player->getActive())
{
currentHoles.emplace_back(player);
}
}
}
HoleLayout::HoleLayout()
: currentHoles()
{}
DepthHoleLayout HoleLayout::atDepth(float depth) const
{
std::vector<DepthHoleDescription> depthHoles{};
for (const auto &hole: currentHoles)
{
auto holeAtDepth = hole.atDepth(depth);
if (holeAtDepth.width <= 0)
{
continue;
}
depthHoles.push_back(holeAtDepth);
}
return {depth, depthHoles};
}

View file

@ -1,30 +0,0 @@
#ifndef HOLESOME_HOLE_LAYOUT_HPP
#define HOLESOME_HOLE_LAYOUT_HPP
#include <memory>
#include "../../../game_object.h"
#include "hole_description.hpp"
#include "depth_hole_layout.hpp"
class HoleLayout : public GameObject
{
public:
HoleLayout();
static std::shared_ptr<HoleLayout> getInstance();
void lateUpdate() override;
void clear();
[[nodiscard]] DepthHoleLayout atDepth(float depth) const;
private:
static inline std::shared_ptr<HoleLayout> singletonInstance = nullptr;
std::vector<HoleDescription> currentHoles;
};
#endif //HOLESOME_HOLE_LAYOUT_HPP

View file

@ -1,100 +0,0 @@
#include "map_player.hpp"
#include "map_simulation.hpp"
void MapPlayer::updateSimulationPosition()
{
auto coordinates = player->coordinates->world();
b2Vec2 playerPosition = b2Vec2(coordinates.x, coordinates.y);
// Calculate velocity that theoretically needs to be applied to the body, to get to the same position
b2Vec2 delta = playerPosition - body->GetPosition();
b2Vec2 velocity = {delta.x * FRAME_RATE, delta.y * FRAME_RATE};
body->SetLinearVelocity(velocity);
if (player->getWorldRadius() != shapeRadius)
{
updateShape();
}
}
void MapPlayer::updatePlayerPosition() const
{
b2Vec2 playerPosition = body->GetPosition();
player->coordinates->setWorld({playerPosition.x, playerPosition.y});
}
void MapPlayer::updateShape()
{
shapeRadius = player->getWorldRadius();
b2Fixture *oldFixture = body->GetFixtureList();
if (oldFixture != nullptr)
{
body->DestroyFixture(oldFixture);
}
b2CircleShape shape;
shape.m_radius = shapeRadius;
shape.m_p.Set(0, 0);
b2FixtureDef fixtureDef;
fixtureDef.shape = &shape;
fixtureDef.density = 1.0f;
body->CreateFixture(&fixtureDef);
}
void MapPlayer::updateCollidingWithPlayers()
{
collidingWithPlayers.clear();
for (b2ContactEdge *contactEdge = body->GetContactList(); contactEdge != nullptr; contactEdge = contactEdge->next)
{
b2Contact *contact = contactEdge->contact;
if (!contact->IsTouching())
{
continue;
}
b2Fixture *fixtureA = contact->GetFixtureA();
b2Fixture *fixtureB = contact->GetFixtureB();
if (fixtureA->IsSensor() || fixtureB->IsSensor())
{
continue;
}
b2Body *bodyA = fixtureA->GetBody();
b2Body *bodyB = fixtureB->GetBody();
if (bodyA->GetType() != b2_dynamicBody || bodyB->GetType() != b2_dynamicBody)
{
continue;
}
std::shared_ptr<MapPlayer> mapPlayerA = MapSimulation::getInstance()->getMapPlayerByBody(bodyA);
std::shared_ptr<MapPlayer> mapPlayerB = MapSimulation::getInstance()->getMapPlayerByBody(bodyB);
if (mapPlayerA == nullptr || mapPlayerB == nullptr)
{
continue;
}
if (mapPlayerA->player->getPlayerId() == player->getPlayerId())
{
collidingWithPlayers.push_back(mapPlayerB->player->getPlayerId());
} else
{
collidingWithPlayers.push_back(mapPlayerA->player->getPlayerId());
}
}
player->setCollidingPlayers(collidingWithPlayers);
}
void MapPlayer::updatePlayer()
{
updatePlayerPosition();
updateCollidingWithPlayers();
}

View file

@ -1,37 +0,0 @@
#ifndef HOLESOME_MAP_PLAYER_HPP
#define HOLESOME_MAP_PLAYER_HPP
#include <box2d/box2d.h>
#include <utility>
#include "../../player/player.hpp"
#include "../../../config.h"
class MapPlayer
{
public:
std::shared_ptr<Player> player;
b2Body *body;
float shapeRadius = 0;
std::vector<int> collidingWithPlayers = {};
MapPlayer(std::shared_ptr<Player> player, b2Body *body) : player(std::move(player)), body(body)
{
updateShape();
}
void updateSimulationPosition();
void updatePlayer();
void updateShape();
private:
void updatePlayerPosition() const;
void updateCollidingWithPlayers();
};
#endif //HOLESOME_MAP_PLAYER_HPP

View file

@ -1,131 +0,0 @@
#include "map_simulation.hpp"
#include "../../../config.h"
#include "../../player/player_collection.hpp"
MapSimulation::MapSimulation()
{
mapPlayersById = std::map<int, std::shared_ptr<MapPlayer>>();
}
std::shared_ptr<MapSimulation> MapSimulation::getInstance()
{
if (singletonInstance == nullptr)
{
singletonInstance = std::make_shared<MapSimulation>();
}
return singletonInstance;
}
void MapSimulation::physicsUpdate()
{
// Update simulation positions
for (auto &mapPlayer: mapPlayersById)
{
mapPlayer.second->updateSimulationPosition();
}
world->Step(FRAME_TIME.asSeconds(), MAPSIM_VELOCITY_ITERATIONS, MAPSIM_POSITION_ITERATIONS);
// Update players
for (auto &mapPlayer: mapPlayersById)
{
mapPlayer.second->updatePlayer();
}
}
void MapSimulation::resetMap(sf::Vector2<int> worldMapSize)
{
// Clear all players
for (auto &[id, _]: mapPlayersById)
{
world->DestroyBody(mapPlayersById[id]->body);
}
mapPlayersById.clear();
// No gravity, since this a top-down view of the map
world = std::make_shared<b2World>(b2Vec2(0.0f, 0.0f));
// Create map borders
constructSquareObstacle(-MAPSIM_WALL_THICKNESS, -MAPSIM_WALL_THICKNESS,
0, worldMapSize.y + MAPSIM_WALL_THICKNESS); // Bottom left
constructSquareObstacle(0, -MAPSIM_WALL_THICKNESS,
worldMapSize.x, 0); // Bottom right
constructSquareObstacle(worldMapSize.x, -MAPSIM_WALL_THICKNESS,
worldMapSize.x + MAPSIM_WALL_THICKNESS,
worldMapSize.y + MAPSIM_WALL_THICKNESS); // Top right
constructSquareObstacle(0, worldMapSize.y,
worldMapSize.x, worldMapSize.y + MAPSIM_WALL_THICKNESS); // Top left
}
void MapSimulation::constructSquareObstacle(float minX, float minY, float maxX, float maxY)
{
b2BodyDef bodyDef;
bodyDef.type = b2_staticBody;
bodyDef.position.Set((maxX + minX) / 2.f, (maxY + minY) / 2.f);
b2Body *body = world->CreateBody(&bodyDef);
b2PolygonShape shape;
shape.SetAsBox((maxX - minX) / 2.f, (maxY - minY) / 2.f);
b2FixtureDef fixtureDef;
fixtureDef.shape = &shape;
fixtureDef.density = 1.0f;
body->CreateFixture(&fixtureDef);
}
void MapSimulation::addPlayer(const std::shared_ptr<Player> &player)
{
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position.Set(player->spawnPosition.world().x, player->spawnPosition.world().y);
b2Body *body = world->CreateBody(&bodyDef);
mapPlayersById[player->getPlayerId()] = std::make_shared<MapPlayer>(player, body);
}
void MapSimulation::update()
{
// Update players from player collection
// New player
for (auto &player: PlayerCollection::getInstance()->getNewPlayers())
{
addPlayer(player);
}
// Removed players
for (auto &player: PlayerCollection::getInstance()->getRemovedPlayers())
{
removePlayer(player);
}
}
void MapSimulation::removePlayer(std::shared_ptr<Player> &player)
{
// Remove body from simulation
int playerId = player->getPlayerId();
world->DestroyBody(mapPlayersById[playerId]->body);
mapPlayersById.erase(playerId);
}
std::shared_ptr<MapPlayer> MapSimulation::getMapPlayerByBody(b2Body *body) const
{
for (auto &mapPlayer: mapPlayersById)
{
if (mapPlayer.second->body == body)
{
return mapPlayer.second;
}
}
return nullptr;
}
void MapSimulation::removePlayer(int playerId)
{
// Still in simulation?
if (mapPlayersById.find(playerId) == mapPlayersById.end())
{
return;
}
world->DestroyBody(mapPlayersById[playerId]->body);
mapPlayersById.erase(playerId);
}

Some files were not shown because too many files have changed in this diff Show more