Compare commits
1 commit
master
...
feature-fi
Author | SHA1 | Date | |
---|---|---|---|
9dc26a6b06 |
1
.gitignore
vendored
|
@ -1,3 +1,2 @@
|
|||
/build/
|
||||
/build-release/
|
||||
/.idea/
|
|
@ -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
|
||||
|
@ -87,86 +89,25 @@ set(SOURCES
|
|||
src/game/level/level_loader.hpp
|
||||
src/levels.hpp src/collectables.hpp
|
||||
src/game/collectables/collectable_config.hpp
|
||||
src/game/physics/hole/hole_depth_simulation.cpp
|
||||
src/game/physics/hole/hole_depth_simulation.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/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/game/physics/hole/hole_sim_player.cpp src/game/physics/hole/hole_sim_player.hpp src/game/collectables/collectable_sim_parameters.hpp src/game/collectables/sim_collectable.hpp)
|
||||
|
||||
set(PHYSICS_00_SOURCES
|
||||
src/prototypes/physics_00.cpp)
|
||||
src/prototypes/physics_00.cpp)
|
||||
|
||||
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)
|
||||
|
@ -176,10 +117,6 @@ 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
|
||||
|
|
13
README.md
|
@ -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
|
@ -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
|
||||
- [ ] Optimize hole depth simulations by not simulating when nothing happens
|
Before Width: | Height: | Size: 849 B |
Before Width: | Height: | Size: 630 B |
Before Width: | Height: | Size: 517 B |
Before Width: | Height: | Size: 578 B |
Before Width: | Height: | Size: 835 B |
Before Width: | Height: | Size: 617 B |
Before Width: | Height: | Size: 967 B |
BIN
assets/edge.png
Normal file
After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 4 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 1.4 KiB |
|
@ -6,13 +6,7 @@
|
|||
#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)}
|
||||
{"box", CollectableConfig("numbers")}
|
||||
};
|
||||
|
||||
#endif //HOLESOME_COLLECTABLES_HPP
|
||||
|
|
65
src/config.h
|
@ -4,89 +4,56 @@
|
|||
#include <SFML/Graphics.hpp>
|
||||
#include <box2d/box2d.h>
|
||||
|
||||
#define DEVELOPER_MODE false
|
||||
|
||||
#define GAME_NAME "Holesome"
|
||||
#define CLOSE_GAME_BTN sf::Keyboard::Escape
|
||||
#define DEVELOPER_MODE true
|
||||
|
||||
// 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
|
||||
#define DEFAULT_PLAYER_RADIUS .5f // In World units
|
||||
#define PLAYER_PROPORTIONAL_SIZE_CHANGE_SPEED 0.4f
|
||||
|
||||
// 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
|
||||
#define WORLD_GRAVITY b2Vec2(0.f, 9.8f)
|
||||
|
||||
// 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
|
||||
|
||||
// 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_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)
|
||||
#define DEF_TV_ADD_PLAYERS_DYNAMICALLY true
|
||||
|
||||
// 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
|
||||
#define HOLESIM_VELOCITY_ITERATIONS 8
|
||||
#define HOLESIM_POSITION_ITERATIONS 3
|
||||
#define HOLESIM_COLLECTABLE_LINEAR_DAMPING 0.5f
|
||||
#define HOLESIM_COLLECTABLE_ANGULAR_DAMPING 0.5f
|
||||
#define HOLESIM_COLLECTABLE_DENSITY 1.f
|
||||
#define HOLESIM_COLLECTABLE_FRICTION 0.3f
|
||||
#define HOLESIM_COLLECTABLE_RESTITUTION 0.5f
|
||||
#define HOLESIM_FLOOR_THICKNESS 0.5f
|
||||
#define HOLESIM_WALL_HEIGHT 5.f
|
||||
|
||||
// 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"}
|
||||
};
|
||||
|
||||
#endif //HOLESOME_CONFIG_H
|
||||
|
|
|
@ -1,23 +1,12 @@
|
|||
#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();
|
||||
|
||||
|
@ -28,64 +17,19 @@ IsometricCoordinates CoordinateTransformer::worldToIsometric(WorldCoordinates wo
|
|||
|
||||
Eigen::Vector3f isoCoordinatesVector = worldToIsometricMatrix * worldCoordinatesVector;
|
||||
|
||||
return {
|
||||
isoCoordinatesVector.x(), // x
|
||||
isoCoordinatesVector.y(), // y
|
||||
return IsometricCoordinates(
|
||||
isoCoordinatesVector.x() * WORLD_TO_ISO_SCALE, // x
|
||||
isoCoordinatesVector.y() * WORLD_TO_ISO_SCALE, // 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
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -106,9 +85,6 @@ struct GridCoordinates
|
|||
|
||||
GridCoordinates(float x, float y) : x(x), y(y)
|
||||
{}
|
||||
|
||||
GridCoordinates(int x, int y) : x(static_cast<float>(x)), y(static_cast<float>(y))
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
{
|
||||
IsometricCoordinates TranslatedCoordinates::isometric() const {
|
||||
return CoordinateTransformer::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};
|
||||
}
|
||||
|
||||
void TranslatedCoordinates::setWorld(WorldCoordinates newWorldCoordinates)
|
||||
{
|
||||
void TranslatedCoordinates::set(WorldCoordinates newWorldCoordinates) {
|
||||
this->worldCoordinates = newWorldCoordinates;
|
||||
}
|
||||
|
||||
void TranslatedCoordinates::setIsometric(IsometricCoordinates newIsometricCoordinates)
|
||||
{
|
||||
void TranslatedCoordinates::set(IsometricCoordinates newIsometricCoordinates) {
|
||||
this->worldCoordinates = CoordinateTransformer::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)
|
||||
|
@ -67,7 +54,7 @@ void TranslatedCoordinates::setParent(std::shared_ptr<TranslatedCoordinates> par
|
|||
this->worldCoordinates = offset;
|
||||
}
|
||||
|
||||
void TranslatedCoordinates::setGrid(GridCoordinates newGridCoordinates)
|
||||
void TranslatedCoordinates::set(GridCoordinates newGridCoordinates)
|
||||
{
|
||||
this->worldCoordinates = {newGridCoordinates.x + 0.5f, newGridCoordinates.y + 0.5f, 0};
|
||||
}
|
||||
|
@ -84,33 +71,5 @@ void TranslatedCoordinates::setScreenOffset(IsometricCoordinates 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);
|
||||
set(gridCoordinates);
|
||||
}
|
||||
|
|
|
@ -9,8 +9,6 @@ class TranslatedCoordinates
|
|||
{
|
||||
public:
|
||||
explicit TranslatedCoordinates(WorldCoordinates worldCoordinates);
|
||||
explicit TranslatedCoordinates(DiagonalWorldCoordinates diagonalWorldCoordinates);
|
||||
explicit TranslatedCoordinates(IsometricCoordinates isometricCoordinates);
|
||||
explicit TranslatedCoordinates(GridCoordinates gridCoordinates);
|
||||
|
||||
[[nodiscard]] WorldCoordinates world() const;
|
||||
|
@ -19,17 +17,13 @@ public:
|
|||
|
||||
[[nodiscard]] 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 set(IsometricCoordinates newIsometricCoordinates);
|
||||
|
||||
void setIsometric(IsometricCoordinates newIsometricCoordinates);
|
||||
|
||||
void setDiagonal(DiagonalWorldCoordinates newDiagonalWorldCoordinates);
|
||||
|
||||
void setGrid(GridCoordinates newGridCoordinates);
|
||||
void set(GridCoordinates newGridCoordinates);
|
||||
|
||||
void move(WorldCoordinates deltaWorldCoordinates);
|
||||
|
||||
|
@ -37,19 +31,14 @@ 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{};
|
||||
|
||||
std::shared_ptr<TranslatedCoordinates> parent = nullptr;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -3,11 +3,13 @@
|
|||
#include "../player/player_collection.hpp"
|
||||
|
||||
TrackingView::TrackingView(TrackingViewOptions options) : options(options),
|
||||
view(new sf::View(options.initialCenter,
|
||||
options.minViewSize)),
|
||||
view(nullptr),
|
||||
hasViewChanged(false),
|
||||
trackables({})
|
||||
{
|
||||
;
|
||||
marker = new CircleObject(DB_CIRCLE_RADIUS, sf::Color::Yellow);
|
||||
Game::getInstance()->registerView(this);
|
||||
}
|
||||
|
||||
TrackingView::~TrackingView()
|
||||
|
@ -18,13 +20,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)
|
||||
|
@ -52,6 +74,7 @@ void TrackingView::setSize(sf::Vector2f newSize)
|
|||
}
|
||||
|
||||
view->setSize(newSize);
|
||||
hasViewChanged = true;
|
||||
}
|
||||
|
||||
sf::Vector2f TrackingView::getSize() const
|
||||
|
@ -59,11 +82,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 +96,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 +106,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 +118,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 +132,12 @@ void TrackingView::moveCenter(sf::Vector2<float> delta)
|
|||
}
|
||||
|
||||
view->move(delta);
|
||||
hasViewChanged = true;
|
||||
}
|
||||
|
||||
void TrackingView::draw(sf::RenderWindow *window)
|
||||
{
|
||||
if (!DB_TRACKING_VIEW_CENTER)
|
||||
if (!DEVELOPER_MODE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -125,12 +148,6 @@ void TrackingView::draw(sf::RenderWindow *window)
|
|||
void TrackingView::addTrackable(const std::shared_ptr<ITrackable> &trackable)
|
||||
{
|
||||
trackables.push_back(trackable);
|
||||
|
||||
// Set initial values if first trackable
|
||||
if (trackables.size() == 1)
|
||||
{
|
||||
view->setCenter(trackable->getTrackablePosition());
|
||||
}
|
||||
}
|
||||
|
||||
void TrackingView::processTrackableStates()
|
||||
|
@ -144,11 +161,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 +206,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,31 +245,61 @@ 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;
|
||||
return smallestSide / 2.f * threshold;
|
||||
}
|
||||
|
||||
void TrackingView::update()
|
||||
{
|
||||
if (options.addPlayersDynamically)
|
||||
{
|
||||
addPlayersDynamically();
|
||||
}
|
||||
}
|
||||
|
||||
void TrackingView::addPlayersDynamically()
|
||||
{
|
||||
// Update based on PlayerCollection
|
||||
// Add new
|
||||
for (const auto &player: PlayerCollection::getInstance()->getNewPlayers())
|
||||
{
|
||||
if (player->getTrackableState() == TrackableState::TRACKING)
|
||||
{
|
||||
addTrackable(player);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove old
|
||||
for (const auto &player: PlayerCollection::getInstance()->getRemovedPlayers())
|
||||
{
|
||||
removeTrackable(player);
|
||||
}
|
||||
}
|
||||
|
||||
void TrackingView::removeTrackable(const std::shared_ptr<ITrackable> &trackable)
|
||||
|
@ -268,14 +310,3 @@ void TrackingView::removeTrackable(const std::shared_ptr<ITrackable> &trackable)
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ public:
|
|||
|
||||
~TrackingView();
|
||||
|
||||
void update() override;
|
||||
|
||||
void draw(sf::RenderWindow *window) override;
|
||||
|
||||
void lateUpdate() override;
|
||||
|
@ -25,21 +27,20 @@ public:
|
|||
|
||||
void removeTrackable(const std::shared_ptr<ITrackable> &trackable);
|
||||
|
||||
void setViewport(sf::Vector2f center, sf::Vector2f size);
|
||||
|
||||
void setViewForWindow();
|
||||
|
||||
sf::Vector2f getSize() const;
|
||||
|
||||
sf::Vector2f getCenter() const;
|
||||
|
||||
private:
|
||||
sf::View *view{};
|
||||
bool hasViewChanged{};
|
||||
TrackingViewOptions options;
|
||||
std::vector<std::shared_ptr<ITrackable>> trackables;
|
||||
|
||||
CircleObject *marker;
|
||||
|
||||
void initializeView();
|
||||
|
||||
void followTrackables();
|
||||
|
||||
void moveCenter(sf::Vector2<float> delta);
|
||||
|
@ -56,9 +57,11 @@ private:
|
|||
|
||||
sf::Vector2f restrainToBounds(sf::Vector2f viewSize) const;
|
||||
|
||||
float getFreeMovementRadius() const;
|
||||
float getRadius(float threshold) const;
|
||||
|
||||
sf::Vector2f getSizeInWindow() const;
|
||||
sf::Vector2f getWindowSize() const;
|
||||
|
||||
void addPlayersDynamically();
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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,17 @@ 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;
|
||||
|
||||
/**
|
||||
* If set to true, view will add all new players automatically and remove them accordingly, based on PlayerCollection.
|
||||
*/
|
||||
bool addPlayersDynamically = DEF_TV_ADD_PLAYERS_DYNAMICALLY;
|
||||
};
|
||||
|
||||
#endif //HOLESOME_TRACKING_VIEW_OPTIONS_HPP
|
||||
|
|
|
@ -1,58 +1,33 @@
|
|||
#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)
|
||||
Collectable::Collectable()
|
||||
{
|
||||
collectableId = collectableCount;
|
||||
collectableCount++;
|
||||
}
|
||||
|
||||
void Collectable::setRotation(float angle)
|
||||
int Collectable::getDepth() const
|
||||
{
|
||||
// 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;
|
||||
return static_cast<int>(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));
|
||||
float sizeWidth = 1.f * WORLD_TO_ISO_SCALE;
|
||||
auto size = sf::Vector2f{sizeWidth, sizeWidth};
|
||||
auto sprite = std::make_shared<VersatileSprite>(spriteName, size);
|
||||
addChildScreenOffset(sprite, IsometricCoordinates(-sizeWidth / 2.f, -sizeWidth));
|
||||
}
|
||||
|
||||
sf::Vector2f Collectable::getSize() const
|
||||
int Collectable::getId() const
|
||||
{
|
||||
return size / WORLD_TO_ISO_SCALE;
|
||||
return collectableId;
|
||||
}
|
||||
|
||||
void Collectable::preRenderUpdate()
|
||||
void Collectable::setSpriteAngle(float angle)
|
||||
{
|
||||
if (!sprite->isVisible())
|
||||
{
|
||||
auto closestPlayer = PlayerCollection::getInstance()->getClosestPlayer(*coordinates);
|
||||
closestPlayer->consume(points);
|
||||
setActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject::preRenderUpdate();
|
||||
// TODO
|
||||
}
|
||||
|
|
|
@ -1,40 +1,28 @@
|
|||
#ifndef HOLESOME_COLLECTABLE_HPP
|
||||
#define HOLESOME_COLLECTABLE_HPP
|
||||
|
||||
|
||||
#include <SFML/System/Vector2.hpp>
|
||||
#include "../game_object.h"
|
||||
#include "../player/player.hpp"
|
||||
#include "../../sprites/masked_sprite.hpp"
|
||||
#include "collectable_sim_parameters.hpp"
|
||||
|
||||
class Collectable : public GameObject
|
||||
{
|
||||
public:
|
||||
Collectable(int points);
|
||||
Collectable();
|
||||
|
||||
void setSprite(const std::string &spriteName);
|
||||
|
||||
void setRotation(float angle);
|
||||
void setSpriteAngle(float angle);
|
||||
|
||||
sf::Vector2f getSize() const;
|
||||
[[nodiscard]] int getDepth() const;
|
||||
|
||||
float getDepth() const;
|
||||
[[nodiscard]] int getId() const;
|
||||
|
||||
int getId() const
|
||||
{
|
||||
return collectableId;
|
||||
}
|
||||
|
||||
void preRenderUpdate() override;
|
||||
CollectableSimParameters simParameters = {};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -6,10 +6,9 @@
|
|||
struct CollectableConfig
|
||||
{
|
||||
std::string spriteName;
|
||||
int points = 0;
|
||||
|
||||
explicit CollectableConfig(std::string spriteName, int points)
|
||||
: spriteName(std::move(spriteName)), points(points)
|
||||
explicit CollectableConfig(std::string spriteName)
|
||||
: spriteName(std::move(spriteName))
|
||||
{}
|
||||
|
||||
CollectableConfig() = default;
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
std::shared_ptr<Collectable> CollectableFactory::createFromInLevelConfig(const CollectableInLevel &config)
|
||||
{
|
||||
auto collectableConfig = config.collectableConfig;
|
||||
auto collectable = std::make_shared<Collectable>(config.collectableConfig.points);
|
||||
auto collectable = std::make_shared<Collectable>();
|
||||
|
||||
collectable->coordinates->setGrid(config.position);
|
||||
collectable->coordinates->set(config.position);
|
||||
collectable->setSprite(collectableConfig.spriteName);
|
||||
|
||||
return collectable;
|
||||
|
|
15
src/game/collectables/collectable_sim_parameters.hpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
#ifndef HOLESOME_COLLECTABLE_SIM_PARAMETERS_HPP
|
||||
#define HOLESOME_COLLECTABLE_SIM_PARAMETERS_HPP
|
||||
|
||||
#include <box2d/box2d.h>
|
||||
|
||||
struct CollectableSimParameters
|
||||
{
|
||||
float angle = 0;
|
||||
float angularVelocity = 0;
|
||||
b2Vec2 linearVelocity = {0, 0};
|
||||
|
||||
CollectableSimParameters() = default;
|
||||
};
|
||||
|
||||
#endif //HOLESOME_COLLECTABLE_SIM_PARAMETERS_HPP
|
|
@ -1,10 +1,6 @@
|
|||
#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()
|
||||
{
|
||||
|
@ -29,57 +25,20 @@ void CollectablesCollection::createEmpty(int maxDepth)
|
|||
}
|
||||
}
|
||||
|
||||
void CollectablesCollection::remove(int collectableId)
|
||||
void CollectablesCollection::remove(const std::shared_ptr<Collectable> &collectable)
|
||||
{
|
||||
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;
|
||||
}
|
||||
updateCollectables();
|
||||
|
||||
// Move collectables to new depth collections if necessary
|
||||
|
||||
|
@ -92,31 +51,18 @@ void CollectablesCollection::update()
|
|||
// Then, move collectables to new depth collections
|
||||
for (auto &[depth, depthCollection]: depthCollections)
|
||||
{
|
||||
auto collectables = depthCollection->collectables;
|
||||
for (auto &collectable: collectables)
|
||||
for (auto &collectable: depthCollection->collectables)
|
||||
{
|
||||
int newDepth = std::floor(collectable->getDepth());
|
||||
if (newDepth == depth)
|
||||
auto newDepth = collectable->getDepth();
|
||||
if (newDepth != depth)
|
||||
{
|
||||
continue;
|
||||
depthCollection->remove(collectable);
|
||||
depthCollections[newDepth]->add(collectable);
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -130,3 +76,25 @@ void CollectablesCollection::draw(sf::RenderWindow *window)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CollectablesCollection::updateCollectables()
|
||||
{
|
||||
for (auto &[depth, depthCollection]: depthCollections)
|
||||
{
|
||||
for (auto &collectable: depthCollection->collectables)
|
||||
{
|
||||
collectable->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<CollectablesDepthCollection> CollectablesCollection::getDepthCollection(int depth)
|
||||
{
|
||||
if (depthCollections.find(depth) == depthCollections.end())
|
||||
{
|
||||
LOG(ERROR) << "Depth collection for depth " << depth << " does not exist! Returning empty collection ...";
|
||||
return std::make_shared<CollectablesDepthCollection>(depth);
|
||||
}
|
||||
|
||||
return depthCollections[depth];
|
||||
}
|
||||
|
|
|
@ -21,14 +21,16 @@ public:
|
|||
void draw(sf::RenderWindow *window) override;
|
||||
|
||||
void add(const std::shared_ptr<Collectable>& collectable);
|
||||
void remove(int collectableId);
|
||||
void remove(const std::shared_ptr<Collectable>& collectable);
|
||||
|
||||
std::shared_ptr<CollectablesDepthCollection> getDepthCollection(int depth);
|
||||
|
||||
private:
|
||||
static inline std::shared_ptr<CollectablesCollection> singletonInstance = nullptr;
|
||||
|
||||
std::map<int, std::shared_ptr<CollectablesDepthCollection>> depthCollections = {};
|
||||
|
||||
bool isValidDepth(int desiredDepth) const;
|
||||
void updateCollectables();
|
||||
};
|
||||
|
||||
|
||||
|
|
21
src/game/collectables/sim_collectable.hpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#ifndef HOLESOME_SIM_COLLECTABLE_HPP
|
||||
#define HOLESOME_SIM_COLLECTABLE_HPP
|
||||
|
||||
#include "collectable.hpp"
|
||||
#include <memory>
|
||||
|
||||
struct SimCollectable
|
||||
{
|
||||
std::shared_ptr<Collectable> collectable = nullptr;
|
||||
b2Body *body = nullptr;
|
||||
|
||||
SimCollectable(std::shared_ptr<Collectable> collectable, b2Body *body)
|
||||
{
|
||||
this->collectable = std::move(collectable);
|
||||
this->body = body;
|
||||
}
|
||||
|
||||
SimCollectable() = default;
|
||||
};
|
||||
|
||||
#endif //HOLESOME_SIM_COLLECTABLE_HPP
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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
|
|
@ -6,12 +6,6 @@
|
|||
#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))
|
||||
{
|
||||
|
@ -21,11 +15,11 @@ 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,7 +29,6 @@ void Game::run()
|
|||
update();
|
||||
InputMapper::getInstance()->processEvents();
|
||||
}
|
||||
|
||||
drawFrame();
|
||||
}
|
||||
|
||||
|
@ -60,27 +53,24 @@ void Game::drawFrame()
|
|||
}
|
||||
|
||||
window->display();
|
||||
frameCounter->addFrame();
|
||||
}
|
||||
|
||||
void Game::addGameObject(const std::shared_ptr<GameObject> &gameObject)
|
||||
void Game::addGameObject(const std::shared_ptr<GameObject>& gameObject)
|
||||
{
|
||||
gameObjectsBuffer.push_back(gameObject);
|
||||
}
|
||||
|
||||
void Game::update()
|
||||
{
|
||||
handleWinning();
|
||||
|
||||
// Add new game objects
|
||||
for (const auto &gameObject: gameObjectsBuffer)
|
||||
for (const auto& gameObject: gameObjectsBuffer)
|
||||
{
|
||||
gameObjects.push_back(gameObject);
|
||||
}
|
||||
gameObjectsBuffer.clear();
|
||||
|
||||
// Basic Updates
|
||||
for (const auto &gameObject: gameObjects)
|
||||
for (const auto& gameObject: gameObjects)
|
||||
{
|
||||
if (gameObject->getActive())
|
||||
{
|
||||
|
@ -107,14 +97,6 @@ void Game::update()
|
|||
}
|
||||
|
||||
InputMapper::getInstance()->updateIdentities();
|
||||
|
||||
for (const auto &gameObject: gameObjects)
|
||||
{
|
||||
if (gameObject->getActive())
|
||||
{
|
||||
gameObject->preRenderUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Game> Game::getInstance()
|
||||
|
@ -139,6 +121,11 @@ std::shared_ptr<Game> Game::constructInstance(const std::shared_ptr<sf::RenderWi
|
|||
return singletonInstance;
|
||||
}
|
||||
|
||||
void Game::registerView(TrackingView *view)
|
||||
{
|
||||
views.push_back(view);
|
||||
}
|
||||
|
||||
void Game::clearGameObjects()
|
||||
{
|
||||
gameObjects.clear();
|
||||
|
@ -153,99 +140,3 @@ 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();
|
||||
}
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
#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,10 +15,8 @@ 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);
|
||||
|
||||
void run();
|
||||
|
@ -31,18 +27,14 @@ public:
|
|||
|
||||
void setLevel(LevelConfig levelConfig);
|
||||
|
||||
void startCountdown(int durationInSeconds);
|
||||
|
||||
[[nodiscard]] bool isLevelLoaded() const;
|
||||
|
||||
void addGameObject(const std::shared_ptr<GameObject> &gameObject);
|
||||
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 = {};
|
||||
|
@ -50,18 +42,9 @@ private:
|
|||
|
||||
LevelConfig loadedLevelConfig = {};
|
||||
|
||||
std::shared_ptr<FrameCounter> frameCounter = std::make_shared<FrameCounter>();
|
||||
std::shared_ptr<Countdown> countdown = std::make_shared<Countdown>();
|
||||
|
||||
void drawFrame();
|
||||
|
||||
void update();
|
||||
|
||||
std::shared_ptr<Player> checkForWinner();
|
||||
|
||||
void handleWinning();
|
||||
|
||||
void showLoadingScreen(const std::string &message = "Loading...");
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -64,7 +64,6 @@ void GameObject::removeChild(const std::shared_ptr<GameObject> &child)
|
|||
if (it != children.end())
|
||||
{
|
||||
children.erase(it);
|
||||
child->coordinates->removeParent();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,16 +79,3 @@ void GameObject::physicsUpdate()
|
|||
child->physicsUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void GameObject::addDetachedChild(const std::shared_ptr<GameObject> &child)
|
||||
{
|
||||
children.push_back(child);
|
||||
}
|
||||
|
||||
void GameObject::preRenderUpdate()
|
||||
{
|
||||
for (auto &child: children)
|
||||
{
|
||||
child->preRenderUpdate();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,15 +20,12 @@ public:
|
|||
|
||||
virtual void physicsUpdate();
|
||||
|
||||
virtual void preRenderUpdate();
|
||||
|
||||
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; }
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
enum GameAction
|
||||
{
|
||||
CONFIRM,
|
||||
RUN
|
||||
CANCEL,
|
||||
MENU,
|
||||
|
||||
GROW,
|
||||
SHRINK
|
||||
};
|
||||
|
||||
#endif //HOLESOME_GAME_ACTION_HPP
|
||||
|
|
|
@ -52,8 +52,8 @@ 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)
|
||||
{
|
||||
Game::getInstance()->exit();
|
||||
return;
|
||||
|
@ -121,13 +121,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;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,8 +25,6 @@ public:
|
|||
|
||||
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;
|
||||
|
|
5
src/game/input_handler.cpp
Normal file
|
@ -0,0 +1,5 @@
|
|||
//
|
||||
// Created by max on 27.04.23.
|
||||
//
|
||||
|
||||
#include "input_handler.h"
|
14
src/game/input_handler.h
Normal 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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -7,35 +7,24 @@
|
|||
#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 = {};
|
||||
sf::Vector2f worldMapSize = {};
|
||||
std::vector<GridCoordinates> playerSpawnPoints = {};
|
||||
std::vector<CollectableInLevel> collectables = {};
|
||||
std::vector<sf::Color> skyColors = {};
|
||||
|
||||
LevelConfig(std::string name,
|
||||
int durationInSeconds,
|
||||
const sf::Vector2f &worldMapSize,
|
||||
const std::vector<GridCoordinates> &playerSpawnPoints,
|
||||
const std::vector<CollectableInLevel> &collectables,
|
||||
std::vector<sf::Color> skyColors,
|
||||
const TileMapConfig &tileMapConfig)
|
||||
const std::vector<CollectableInLevel> &collectables = {})
|
||||
: name(std::move(name)),
|
||||
durationInSeconds(durationInSeconds),
|
||||
playerSpawnPoints(playerSpawnPoints),
|
||||
tileMapConfig(tileMapConfig),
|
||||
skyColors(std::move(skyColors))
|
||||
worldMapSize(worldMapSize),
|
||||
playerSpawnPoints(playerSpawnPoints)
|
||||
{
|
||||
worldMapSize = tileMapConfig.getSize();
|
||||
|
||||
// Remove invalid collectables
|
||||
for (auto &collectable: collectables)
|
||||
for (auto &collectable : collectables)
|
||||
{
|
||||
if (collectable.isValid())
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -6,12 +6,7 @@
|
|||
#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"
|
||||
#include "../physics/hole/hole_depth_simulation.hpp"
|
||||
|
||||
void LevelLoader::loadLevel(const LevelConfig &levelConfig)
|
||||
{
|
||||
|
@ -21,45 +16,24 @@ void LevelLoader::loadLevel(const LevelConfig &levelConfig)
|
|||
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();
|
||||
PlayerCollection::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)
|
||||
// Add basic game objects
|
||||
if (DEVELOPER_MODE)
|
||||
{
|
||||
levelRenderer->addChild(std::make_shared<GridDebugLayer>(0, 50, 0, 50));
|
||||
game->addGameObject(std::make_shared<GridDebugLayer>(0, 50, 0, 50));
|
||||
}
|
||||
|
||||
levelRenderer->addChild(PlayerCollection::getInstance());
|
||||
game->addGameObject(std::make_shared<TrackingView>());
|
||||
game->addGameObject(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());
|
||||
game->addGameObject(collectablesCollection);
|
||||
|
||||
// Spawn collectibles
|
||||
for (auto const &collectableInfo: levelConfig.collectables)
|
||||
|
@ -67,10 +41,14 @@ void LevelLoader::loadLevel(const LevelConfig &levelConfig)
|
|||
spawnCollectable(collectableInfo);
|
||||
}
|
||||
|
||||
game->startCountdown(levelConfig.durationInSeconds);
|
||||
|
||||
// Must be last
|
||||
game->addGameObject(GlobalLayer::getInstance());
|
||||
// Add physics simulations
|
||||
game->addGameObject(MapSimulation::getInstance());
|
||||
// For every depth, add a hole depth simulation
|
||||
for (int depth = 0; depth < maxDepth; depth++)
|
||||
{
|
||||
auto depthCollection = collectablesCollection->getDepthCollection(depth);
|
||||
game->addGameObject(std::make_shared<HoleDepthSimulation>(depthCollection));
|
||||
}
|
||||
|
||||
LOG(INFO) << "Finished loading level '" << levelConfig.name << "'.";
|
||||
}
|
||||
|
@ -91,20 +69,6 @@ void LevelLoader::spawnCollectable(const CollectableInLevel &collectableInfo)
|
|||
{
|
||||
LOG(INFO) << "Spawning collectable '" << collectableInfo.name << "' ...";
|
||||
auto collectable = CollectableFactory::createFromInLevelConfig(collectableInfo);
|
||||
LOG(INFO) << "Has depth " << collectable->getDepth() << ".";
|
||||
LOG(DEBUG) << "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.";
|
||||
}
|
||||
|
|
|
@ -12,8 +12,6 @@ public:
|
|||
|
||||
static void loadLevel(const std::string &levelName);
|
||||
|
||||
static void cleanUp();
|
||||
|
||||
static void spawnCollectable(const CollectableInLevel &collectableInfo);
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
136
src/game/physics/hole/hole_depth_simulation.cpp
Normal file
|
@ -0,0 +1,136 @@
|
|||
#include "hole_depth_simulation.hpp"
|
||||
#include "../../../config.h"
|
||||
#include "../../player/player_collection.hpp"
|
||||
|
||||
HoleDepthSimulation::HoleDepthSimulation(std::shared_ptr<CollectablesDepthCollection> collectables) : collectables(
|
||||
std::move(collectables))
|
||||
{
|
||||
simWorld = std::make_shared<b2World>(WORLD_GRAVITY);
|
||||
}
|
||||
|
||||
void HoleDepthSimulation::physicsUpdate()
|
||||
{
|
||||
updateBodiesFromCollectionHistory();
|
||||
|
||||
preparePlayers();
|
||||
|
||||
updateGroundBodies();
|
||||
|
||||
simWorld->Step(FRAME_TIME.asSeconds(), HOLESIM_VELOCITY_ITERATIONS, HOLESIM_POSITION_ITERATIONS);
|
||||
|
||||
updateCollectables();
|
||||
}
|
||||
|
||||
void HoleDepthSimulation::updateBodiesFromCollectionHistory()
|
||||
{
|
||||
// Remove bodies that are no longer in the collection
|
||||
for (auto &collectable: collectables->recentlyRemoved)
|
||||
{
|
||||
auto simCollectable = simCollectables.at(collectable->getId());
|
||||
simWorld->DestroyBody(simCollectable.body);
|
||||
simCollectables.erase(collectable->getId());
|
||||
}
|
||||
|
||||
// Create bodies for new collectables
|
||||
for (auto &collectable: collectables->recentlyAdded)
|
||||
{
|
||||
simCollectables[collectable->getId()] = SimCollectable(collectable, createBody(collectable));
|
||||
}
|
||||
}
|
||||
|
||||
void HoleDepthSimulation::updateCollectables()
|
||||
{
|
||||
for (auto &[id, simCollectable]: simCollectables)
|
||||
{
|
||||
auto collectable = simCollectable.collectable;
|
||||
auto body = simCollectable.body;
|
||||
|
||||
auto position = body->GetPosition();
|
||||
collectable->coordinates->set(IsometricCoordinates{
|
||||
position.x,
|
||||
position.y,
|
||||
collectable->coordinates->isometric().depth
|
||||
});
|
||||
|
||||
collectable->simParameters.angle = body->GetAngle();
|
||||
collectable->simParameters.linearVelocity = body->GetLinearVelocity();
|
||||
collectable->simParameters.angularVelocity = body->GetAngularVelocity();
|
||||
|
||||
collectable->setSpriteAngle(collectable->simParameters.angle);
|
||||
}
|
||||
}
|
||||
|
||||
b2Body *HoleDepthSimulation::createBody(const std::shared_ptr<Collectable> &collectable)
|
||||
{
|
||||
b2BodyDef bodyDef;
|
||||
bodyDef.type = b2_dynamicBody;
|
||||
bodyDef.position.Set(collectable->coordinates->isometric().x, collectable->coordinates->isometric().y);
|
||||
bodyDef.linearDamping = HOLESIM_COLLECTABLE_LINEAR_DAMPING;
|
||||
bodyDef.angularDamping = HOLESIM_COLLECTABLE_ANGULAR_DAMPING;
|
||||
bodyDef.allowSleep = false;
|
||||
bodyDef.awake = true;
|
||||
auto body = simWorld->CreateBody(&bodyDef);
|
||||
|
||||
b2PolygonShape shape;
|
||||
shape.SetAsBox(1, 1);
|
||||
b2FixtureDef fixtureDef;
|
||||
fixtureDef.shape = &shape;
|
||||
fixtureDef.density = HOLESIM_COLLECTABLE_DENSITY;
|
||||
fixtureDef.friction = HOLESIM_COLLECTABLE_FRICTION;
|
||||
fixtureDef.restitution = HOLESIM_COLLECTABLE_RESTITUTION;
|
||||
body->CreateFixture(&fixtureDef);
|
||||
|
||||
// Update simulation parameters from existing collectable
|
||||
body->SetTransform(
|
||||
b2Vec2{
|
||||
collectable->coordinates->isometric().x,
|
||||
collectable->coordinates->isometric().y
|
||||
},
|
||||
collectable->simParameters.angle);
|
||||
body->SetLinearVelocity(collectable->simParameters.linearVelocity);
|
||||
body->SetAngularVelocity(collectable->simParameters.angularVelocity);
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
int HoleDepthSimulation::getDepth() const
|
||||
{
|
||||
return collectables->depth;
|
||||
}
|
||||
|
||||
void HoleDepthSimulation::preparePlayers()
|
||||
{
|
||||
for (const auto &player: PlayerCollection::getInstance()->getPlayers())
|
||||
{
|
||||
if (player->getDepth() == getDepth())
|
||||
{
|
||||
addToSim(player);
|
||||
} else
|
||||
{
|
||||
removeFromSim(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HoleDepthSimulation::addToSim(const std::shared_ptr<Player> &player)
|
||||
{
|
||||
int playerId = player->getPlayerId();
|
||||
if (simPlayers.find(playerId) == simPlayers.end())
|
||||
{
|
||||
simPlayers[playerId] = HoleSimPlayer(player);
|
||||
}
|
||||
}
|
||||
|
||||
void HoleDepthSimulation::removeFromSim(const std::shared_ptr<Player> &player)
|
||||
{
|
||||
int playerId = player->getPlayerId();
|
||||
if (simPlayers.find(playerId) != simPlayers.end())
|
||||
{
|
||||
simPlayers.erase(playerId);
|
||||
}
|
||||
}
|
||||
|
||||
void HoleDepthSimulation::updateGroundBodies()
|
||||
{
|
||||
|
||||
}
|
46
src/game/physics/hole/hole_depth_simulation.hpp
Normal file
|
@ -0,0 +1,46 @@
|
|||
#ifndef HOLESOME_HOLE_DEPTH_SIMULATION_HPP
|
||||
#define HOLESOME_HOLE_DEPTH_SIMULATION_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <box2d/box2d.h>
|
||||
#include <map>
|
||||
#include "../../collectables/collection/collectables_depth_collection.hpp"
|
||||
#include "hole_sim_player.hpp"
|
||||
#include "../../collectables/sim_collectable.hpp"
|
||||
|
||||
/**
|
||||
* @brief Simulates the hole(s) for the specific depth
|
||||
*/
|
||||
class HoleDepthSimulation : public GameObject
|
||||
{
|
||||
public:
|
||||
explicit HoleDepthSimulation(std::shared_ptr<CollectablesDepthCollection> collectables);
|
||||
|
||||
void physicsUpdate() override;
|
||||
|
||||
[[nodiscard]] int getDepth() const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<CollectablesDepthCollection> collectables;
|
||||
std::shared_ptr<b2World> simWorld;
|
||||
std::map<int, HoleSimPlayer> simPlayers = {}; // Player ID -> HoleSimPlayer
|
||||
|
||||
void updateBodiesFromCollectionHistory();
|
||||
|
||||
void updateCollectables();
|
||||
|
||||
std::map<int, SimCollectable> simCollectables = {}; // Collectable ID -> Body
|
||||
b2Body *createBody(const std::shared_ptr<Collectable> &collectable);
|
||||
|
||||
void preparePlayers();
|
||||
|
||||
void addToSim(const std::shared_ptr<Player> &player);
|
||||
|
||||
void removeFromSim(const std::shared_ptr<Player> &player);
|
||||
|
||||
void updateGroundBodies();
|
||||
};
|
||||
|
||||
|
||||
#endif //HOLESOME_HOLE_DEPTH_SIMULATION_HPP
|
18
src/game/physics/hole/hole_sim_player.cpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
#include "hole_sim_player.hpp"
|
||||
|
||||
void HoleSimPlayer::updateSimulationPosition()
|
||||
{
|
||||
xPosition = player->coordinates->isometric().x;
|
||||
width = player->getIsoHoleWidth();
|
||||
}
|
||||
|
||||
HoleSimPlayer::HoleSimPlayer(std::shared_ptr<Player> player)
|
||||
{
|
||||
this->player = std::move(player);
|
||||
updateSimulationPosition();
|
||||
}
|
||||
|
||||
HoleSimPlayer::HoleSimPlayer()
|
||||
{
|
||||
player = nullptr;
|
||||
}
|
23
src/game/physics/hole/hole_sim_player.hpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
#ifndef HOLESOME_HOLE_SIM_PLAYER_HPP
|
||||
#define HOLESOME_HOLE_SIM_PLAYER_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include "../../player/player.hpp"
|
||||
#include <box2d/box2d.h>
|
||||
|
||||
struct HoleSimPlayer
|
||||
{
|
||||
HoleSimPlayer();
|
||||
|
||||
std::shared_ptr<Player> player;
|
||||
float xPosition = 0;
|
||||
float width = 0;
|
||||
|
||||
explicit HoleSimPlayer(std::shared_ptr<Player> player);
|
||||
|
||||
void updateSimulationPosition();
|
||||
};
|
||||
|
||||
|
||||
#endif //HOLESOME_HOLE_SIM_PLAYER_HPP
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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};
|
||||
}
|
|
@ -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
|
|
@ -1,5 +1,4 @@
|
|||
#include "map_player.hpp"
|
||||
#include "map_simulation.hpp"
|
||||
|
||||
void MapPlayer::updateSimulationPosition()
|
||||
{
|
||||
|
@ -21,7 +20,7 @@ void MapPlayer::updateSimulationPosition()
|
|||
void MapPlayer::updatePlayerPosition() const
|
||||
{
|
||||
b2Vec2 playerPosition = body->GetPosition();
|
||||
player->coordinates->setWorld({playerPosition.x, playerPosition.y});
|
||||
player->coordinates->set(sf::Vector2f(playerPosition.x, playerPosition.y));
|
||||
}
|
||||
|
||||
void MapPlayer::updateShape()
|
||||
|
@ -29,8 +28,7 @@ void MapPlayer::updateShape()
|
|||
shapeRadius = player->getWorldRadius();
|
||||
|
||||
b2Fixture *oldFixture = body->GetFixtureList();
|
||||
if (oldFixture != nullptr)
|
||||
{
|
||||
if (oldFixture != nullptr) {
|
||||
body->DestroyFixture(oldFixture);
|
||||
}
|
||||
|
||||
|
@ -43,58 +41,4 @@ void MapPlayer::updateShape()
|
|||
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();
|
||||
}
|
||||
}
|
|
@ -14,7 +14,6 @@ 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)
|
||||
{
|
||||
|
@ -22,15 +21,10 @@ public:
|
|||
}
|
||||
|
||||
void updateSimulationPosition();
|
||||
void updatePlayer();
|
||||
|
||||
void updateShape();
|
||||
|
||||
private:
|
||||
|
||||
void updatePlayerPosition() const;
|
||||
|
||||
void updateCollidingWithPlayers();
|
||||
void updateShape();
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -26,35 +26,29 @@ void MapSimulation::physicsUpdate()
|
|||
|
||||
world->Step(FRAME_TIME.asSeconds(), MAPSIM_VELOCITY_ITERATIONS, MAPSIM_POSITION_ITERATIONS);
|
||||
|
||||
// Update players
|
||||
// Update player positions
|
||||
for (auto &mapPlayer: mapPlayersById)
|
||||
{
|
||||
mapPlayer.second->updatePlayer();
|
||||
mapPlayer.second->updatePlayerPosition();
|
||||
}
|
||||
}
|
||||
|
||||
void MapSimulation::resetMap(sf::Vector2<int> worldMapSize)
|
||||
void MapSimulation::resetMap(sf::Vector2f worldMapSize)
|
||||
{
|
||||
// Clear all players
|
||||
for (auto &[id, _]: mapPlayersById)
|
||||
for (auto &mapPlayer: mapPlayersById)
|
||||
{
|
||||
world->DestroyBody(mapPlayersById[id]->body);
|
||||
removePlayer(mapPlayer.second->player);
|
||||
}
|
||||
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
|
||||
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)
|
||||
|
@ -105,27 +99,3 @@ void MapSimulation::removePlayer(std::shared_ptr<Player> &player)
|
|||
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);
|
||||
}
|
||||
|
|
|
@ -16,15 +16,12 @@ public:
|
|||
|
||||
void physicsUpdate() override;
|
||||
|
||||
void resetMap(sf::Vector2<int> worldMapSize);
|
||||
void resetMap(sf::Vector2f worldMapSize);
|
||||
|
||||
static std::shared_ptr<MapSimulation> getInstance();
|
||||
|
||||
void addPlayer(const std::shared_ptr<Player>& player);
|
||||
|
||||
void removePlayer(int playerId);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<MapPlayer> getMapPlayerByBody(b2Body* body) const;
|
||||
|
||||
private:
|
||||
static inline std::shared_ptr<MapSimulation> singletonInstance = nullptr;
|
||||
|
|
|
@ -1,24 +1,20 @@
|
|||
#include "player.hpp"
|
||||
#include "../../logging/easylogging++.h"
|
||||
#include "player_collection.hpp"
|
||||
#include "../../typography/font_manager.hpp"
|
||||
#include "../physics/map/map_simulation.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
Player::Player(std::shared_ptr<InputIdentity> assignedInput, const std::string &skinRessourceName,
|
||||
GridCoordinates initCoordinates)
|
||||
: spawnPosition(initCoordinates), skinName(skinRessourceName)
|
||||
: spawnPosition(initCoordinates)
|
||||
{
|
||||
playerId = playerCreationCounter++;
|
||||
coordinates->setTranslated(spawnPosition);
|
||||
coordinates->set(spawnPosition);
|
||||
|
||||
input = std::move(assignedInput);
|
||||
|
||||
skinSprite = std::make_shared<VersatileSprite>(skinRessourceName, getIsoSize());
|
||||
addChildScreenOffset(skinSprite, IsometricCoordinates(-getIsoSize() / 2.f));
|
||||
|
||||
updateRadiusBasedOnPoints();
|
||||
|
||||
LOG(INFO) << "Player " << playerId << " created.";
|
||||
}
|
||||
|
||||
|
@ -40,12 +36,22 @@ void Player::update()
|
|||
return;
|
||||
}
|
||||
|
||||
if (!passiveMode)
|
||||
{
|
||||
performInteractiveUpdates();
|
||||
}
|
||||
auto moveDirection = input->direction.asIsometricVector();
|
||||
auto moveDelta = moveDirection * speed * FRAME_TIME.asSeconds();
|
||||
coordinates->move(moveDelta);
|
||||
|
||||
handlePlayerCollisions();
|
||||
if (input->isPerformingAction(GameAction::GROW))
|
||||
{
|
||||
setWorldRadius(radiusInWorld * (1 + PLAYER_PROPORTIONAL_SIZE_CHANGE_SPEED * FRAME_TIME.asSeconds()));
|
||||
} else if (input->isPerformingAction(GameAction::SHRINK))
|
||||
{
|
||||
auto newRadius = radiusInWorld * (1 - PLAYER_PROPORTIONAL_SIZE_CHANGE_SPEED * FRAME_TIME.asSeconds());
|
||||
if (newRadius <= DEFAULT_PLAYER_RADIUS)
|
||||
{
|
||||
newRadius = DEFAULT_PLAYER_RADIUS;
|
||||
}
|
||||
setWorldRadius(newRadius);
|
||||
}
|
||||
|
||||
GameObject::update();
|
||||
}
|
||||
|
@ -74,7 +80,7 @@ float Player::getWorldRadius() const
|
|||
sf::Vector2f Player::getIsoSize() const
|
||||
{
|
||||
// TODO: For some reason, the player is a little to narrow. This fixes it.
|
||||
const float fixFactor = sqrt(2);
|
||||
const float fixFactor = 1.4f;
|
||||
float width = radiusInWorld * 2.f * WORLD_TO_ISO_SCALE * fixFactor;
|
||||
return {width, width * ISOMETRIC_SKEW};
|
||||
}
|
||||
|
@ -89,152 +95,19 @@ void Player::setWorldRadius(float newWorldRadius)
|
|||
skinSprite->coordinates->setScreenOffset(IsometricCoordinates(-newSize / 2.f));
|
||||
}
|
||||
|
||||
int Player::getPoints() const
|
||||
Player::~Player()
|
||||
{
|
||||
return points;
|
||||
LOG(INFO) << "Player " << playerId << " destroyed.";
|
||||
}
|
||||
|
||||
void Player::updateRadiusBasedOnPoints()
|
||||
float Player::getIsoHoleWidth() const
|
||||
{
|
||||
int points = getPoints();
|
||||
float newWorldRadius = PLAYER_MIN_RADIUS + PLAYER_RADIUS_PER_LEVEL * points / 10.f;
|
||||
|
||||
setWorldRadius(newWorldRadius);
|
||||
// Assuming 10% of the player's size is border
|
||||
float holeFactor = .9f;
|
||||
return getIsoSize().x * holeFactor;
|
||||
}
|
||||
|
||||
void Player::consume(int consumedPoints)
|
||||
int Player::getDepth() const
|
||||
{
|
||||
setPoints(getPoints() + consumedPoints);
|
||||
LOG(INFO) << "Player " << playerId << " consumed " << consumedPoints << " points. Total: " << this->points;
|
||||
}
|
||||
|
||||
std::string Player::getSkinName() const
|
||||
{
|
||||
return skinName;
|
||||
}
|
||||
|
||||
std::shared_ptr<InputIdentity> Player::getInput() const
|
||||
{
|
||||
return input;
|
||||
}
|
||||
|
||||
void Player::setPassiveMode(bool newPassiveMode)
|
||||
{
|
||||
passiveMode = newPassiveMode;
|
||||
}
|
||||
|
||||
void Player::performInteractiveUpdates()
|
||||
{
|
||||
if (input->isPerformingAction(GameAction::RUN))
|
||||
{
|
||||
speed = PLAYER_RUN_SPEED;
|
||||
|
||||
// Subtract cost
|
||||
setPoints(points - PLAYER_RUN_SPEED_COST_PER_SECOND * FRAME_TIME.asSeconds());
|
||||
} else
|
||||
{
|
||||
speed = DEFAULT_PLAYER_SPEED;
|
||||
}
|
||||
|
||||
auto moveDirection = input->direction.asIsometricVector();
|
||||
auto moveDelta = moveDirection * speed * FRAME_TIME.asSeconds();
|
||||
coordinates->move(moveDelta);
|
||||
}
|
||||
|
||||
void Player::setCollidingPlayers(const std::vector<int> &collidingPlayerIds)
|
||||
{
|
||||
collidingPlayers.clear();
|
||||
for (auto collidingPlayerId: collidingPlayerIds)
|
||||
{
|
||||
collidingPlayers.push_back(PlayerCollection::getInstance()->getPlayerById(collidingPlayerId));
|
||||
}
|
||||
}
|
||||
|
||||
void Player::handlePlayerCollisions()
|
||||
{
|
||||
if (collidingPlayers.empty() || !isAlive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Count number of players that are bigger than this player by the required margin
|
||||
float numAttackingPlayers = 0;
|
||||
for (const auto &collidingPlayer: collidingPlayers)
|
||||
{
|
||||
if (collidingPlayer->getPoints() - getPoints() >= POINT_DELTA_FOR_ATTACK)
|
||||
{
|
||||
numAttackingPlayers++;
|
||||
}
|
||||
}
|
||||
|
||||
// Reduce points if necessary
|
||||
float damage = numAttackingPlayers * ATTACK_PER_SECOND * FRAME_TIME.asSeconds() *
|
||||
(points + 1.f + fabs(PLAYER_ALIVE_THRESHOLD));
|
||||
if (numAttackingPlayers > 0)
|
||||
{
|
||||
LOG(INFO) << "Player " << playerId << " is getting attacked by " << numAttackingPlayers << "players and lost "
|
||||
<< damage << " points.";
|
||||
}
|
||||
|
||||
setPoints(points - damage);
|
||||
}
|
||||
|
||||
bool Player::getAlive() const
|
||||
{
|
||||
return isAlive;
|
||||
}
|
||||
|
||||
void Player::setAlive(bool newAlive)
|
||||
{
|
||||
if (isAlive == newAlive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newAlive)
|
||||
{
|
||||
setPassiveMode(true);
|
||||
MapSimulation::getInstance()->removePlayer(getPlayerId());
|
||||
}
|
||||
isAlive = newAlive;
|
||||
}
|
||||
|
||||
void Player::draw(sf::RenderWindow *window)
|
||||
{
|
||||
if (isAlive)
|
||||
{
|
||||
GameObject::draw(window);
|
||||
return;
|
||||
}
|
||||
|
||||
// Print "You were consumed" message
|
||||
sf::Text text;
|
||||
text.setFont(*FontManager::getInstance()->getDefaultFont());
|
||||
text.setString("x-x");
|
||||
text.setCharacterSize(15);
|
||||
text.setFillColor(sf::Color::Red);
|
||||
text.setOutlineColor(sf::Color::Black);
|
||||
text.setOutlineThickness(2);
|
||||
text.setPosition(coordinates->isometric().x - text.getLocalBounds().width / 2.f,
|
||||
coordinates->isometric().y - text.getLocalBounds().height / 2.f);
|
||||
window->draw(text);
|
||||
}
|
||||
|
||||
void Player::setPoints(float newPoints)
|
||||
{
|
||||
points = newPoints;
|
||||
updateRadiusBasedOnPoints();
|
||||
|
||||
if (points > highestPoints)
|
||||
{
|
||||
highestPoints = points;
|
||||
} else if (points <= PLAYER_ALIVE_THRESHOLD)
|
||||
{
|
||||
setAlive(false);
|
||||
}
|
||||
}
|
||||
|
||||
int Player::getMaxPoints() const
|
||||
{
|
||||
return highestPoints;
|
||||
return ceil(coordinates->isometric().depth);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ public:
|
|||
Player(std::shared_ptr<InputIdentity> assignedInput, const std::string &skinRessourceName,
|
||||
GridCoordinates initCoordinates);
|
||||
|
||||
~Player();
|
||||
|
||||
void update() override;
|
||||
|
||||
[[nodiscard]] sf::Vector2f getTrackablePosition() const override;
|
||||
|
@ -29,53 +31,20 @@ public:
|
|||
|
||||
[[nodiscard]] float getWorldRadius() const;
|
||||
|
||||
[[nodiscard]] int getPoints() const;
|
||||
[[nodiscard]] float getIsoHoleWidth() const;
|
||||
|
||||
int getMaxPoints() const;
|
||||
|
||||
void consume(int consumedPoints);
|
||||
|
||||
std::string getSkinName() const;
|
||||
|
||||
void setAlive(bool newAlive);
|
||||
|
||||
[[nodiscard]] bool getAlive() const;
|
||||
|
||||
std::shared_ptr<InputIdentity> getInput() const;
|
||||
|
||||
void setPassiveMode(bool newPassiveMode);
|
||||
|
||||
void setCollidingPlayers(const std::vector<int>& collidingPlayerIds);
|
||||
|
||||
void draw(sf::RenderWindow *window) override;
|
||||
[[nodiscard]] int getDepth() const;
|
||||
|
||||
TranslatedCoordinates spawnPosition;
|
||||
private:
|
||||
std::shared_ptr<InputIdentity> input;
|
||||
float radiusInWorld = 0.5f; // In world units
|
||||
float radiusInWorld = DEFAULT_PLAYER_RADIUS;
|
||||
std::shared_ptr<VersatileSprite> skinSprite;
|
||||
std::vector<std::shared_ptr<Player>> collidingPlayers;
|
||||
|
||||
void setPoints(float newPoints);
|
||||
|
||||
bool passiveMode = true;
|
||||
bool isAlive = true;
|
||||
|
||||
std::string skinName;
|
||||
|
||||
float points = DEFAULT_PLAYER_POINTS;
|
||||
float highestPoints = DEFAULT_PLAYER_POINTS;
|
||||
|
||||
int playerId;
|
||||
static inline int playerCreationCounter = 0;
|
||||
|
||||
void setWorldRadius(float newWorldRadius);
|
||||
|
||||
void updateRadiusBasedOnPoints();
|
||||
|
||||
void performInteractiveUpdates();
|
||||
|
||||
void handlePlayerCollisions();
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
#include "../input/input_mapper.h"
|
||||
#include "../../texture_config.h"
|
||||
|
||||
PlayerCollection::PlayerCollection(int maxPlayerCount)
|
||||
: maxPlayerCount(maxPlayerCount)
|
||||
PlayerCollection::PlayerCollection()
|
||||
{
|
||||
// Set default spawn point
|
||||
setSpawnPoints({{0, 0}});
|
||||
|
@ -13,7 +12,7 @@ PlayerCollection::PlayerCollection(int maxPlayerCount)
|
|||
// Create player for existing input identities
|
||||
for (auto &inputIdentity: InputMapper::getInstance()->getInputIdentities())
|
||||
{
|
||||
spawnPlayer(inputIdentity, nullptr);
|
||||
spawnPlayer(inputIdentity);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,7 +28,8 @@ std::shared_ptr<PlayerCollection> PlayerCollection::getInstance()
|
|||
void PlayerCollection::addPlayer(const std::shared_ptr<Player> &player)
|
||||
{
|
||||
newPlayerBuffer.push_back(player);
|
||||
addDetachedChild(player);
|
||||
players.push_back(player);
|
||||
addChild(player);
|
||||
}
|
||||
|
||||
void PlayerCollection::clear()
|
||||
|
@ -37,18 +37,16 @@ void PlayerCollection::clear()
|
|||
newPlayerBuffer.clear();
|
||||
removedPlayerBuffer.clear();
|
||||
|
||||
nextSpawnPointIndex = 0;
|
||||
|
||||
// Fill in removed players
|
||||
for (auto &child: getChildren())
|
||||
for (auto &player: players)
|
||||
{
|
||||
auto player = std::dynamic_pointer_cast<Player>(child);
|
||||
if (player != nullptr)
|
||||
{
|
||||
removedPlayerBuffer.push_back(player);
|
||||
}
|
||||
}
|
||||
|
||||
players.clear();
|
||||
clearChildren();
|
||||
}
|
||||
|
||||
|
@ -70,8 +68,7 @@ void PlayerCollection::lateUpdate()
|
|||
{
|
||||
if (child != nullptr && !child->getActive())
|
||||
{
|
||||
removedPlayerBuffer.push_back(std::dynamic_pointer_cast<Player>(child));
|
||||
removeChild(child);
|
||||
removePlayer(std::dynamic_pointer_cast<Player>(child));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,6 +76,7 @@ void PlayerCollection::lateUpdate()
|
|||
void PlayerCollection::removePlayer(const std::shared_ptr<Player> &player)
|
||||
{
|
||||
removedPlayerBuffer.push_back(player);
|
||||
players.erase(std::remove(players.begin(), players.end(), player), players.end());
|
||||
removeChild(player);
|
||||
}
|
||||
|
||||
|
@ -98,143 +96,17 @@ void PlayerCollection::setSpawnPoints(std::vector<GridCoordinates> newSpawnPoint
|
|||
nextSpawnPointIndex = 0;
|
||||
}
|
||||
|
||||
void PlayerCollection::spawnPlayer(const std::shared_ptr<InputIdentity> &inputIdentity, std::string skin)
|
||||
void PlayerCollection::spawnPlayer(const std::shared_ptr<InputIdentity> &inputIdentity)
|
||||
{
|
||||
// Get proper Spawn point, if available
|
||||
auto spawn = spawnPoints[nextSpawnPointIndex];
|
||||
nextSpawnPointIndex = static_cast<int>((nextSpawnPointIndex + 1) % spawnPoints.size());
|
||||
|
||||
if (skin.empty())
|
||||
{
|
||||
skin = getNextSkin();
|
||||
}
|
||||
|
||||
auto player = std::make_shared<Player>(inputIdentity, skin, spawn);
|
||||
addPlayer(player);
|
||||
auto player = std::make_shared<Player>(inputIdentity, PLAYER_SKIN, spawn);
|
||||
PlayerCollection::getInstance()->addPlayer(player);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Player>> PlayerCollection::getPlayers() const
|
||||
{
|
||||
std::vector<std::shared_ptr<Player>> players = {};
|
||||
for (auto &child: getChildren())
|
||||
{
|
||||
auto player = std::dynamic_pointer_cast<Player>(child);
|
||||
if (player != nullptr)
|
||||
{
|
||||
players.push_back(player);
|
||||
}
|
||||
}
|
||||
return players;
|
||||
}
|
||||
|
||||
std::shared_ptr<Player> PlayerCollection::getPlayerById(int playerId) const
|
||||
{
|
||||
for (auto &player: getPlayers())
|
||||
{
|
||||
if (player->getPlayerId() == playerId)
|
||||
{
|
||||
return player;
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("Player with id " + std::to_string(playerId) + " not found");
|
||||
}
|
||||
|
||||
int PlayerCollection::getMaxPlayerCount() const
|
||||
{
|
||||
return maxPlayerCount;
|
||||
}
|
||||
|
||||
std::shared_ptr<Player> PlayerCollection::getClosestPlayer(const TranslatedCoordinates &point) const
|
||||
{
|
||||
std::shared_ptr<Player> closestPlayer = nullptr;
|
||||
float closestDistance = INFINITY;
|
||||
for (auto &player: getPlayers())
|
||||
{
|
||||
auto playerCenterGround = player->coordinates->world().toGroundCoordinates();
|
||||
auto pointCenterGround = point.world().toGroundCoordinates();
|
||||
// Normalize distance by player radius to get a value below 1 for something inside the player
|
||||
float distance = length(playerCenterGround - pointCenterGround) / player->getWorldRadius();
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestPlayer = player;
|
||||
closestDistance = distance;
|
||||
}
|
||||
}
|
||||
return closestPlayer;
|
||||
}
|
||||
|
||||
std::string PlayerCollection::getNextSkin()
|
||||
{
|
||||
int numberOfSkins = static_cast<int>(PLAYER_SKINS.size());
|
||||
|
||||
// Count how often each skin index is taken
|
||||
std::map<int, int> skinIndexCount;
|
||||
for (int i = 0; i < numberOfSkins; i++)
|
||||
{
|
||||
skinIndexCount[i] = 0;
|
||||
|
||||
for (auto &takenSkinIndex: takenSkinIndices)
|
||||
{
|
||||
if (takenSkinIndex == i)
|
||||
{
|
||||
skinIndexCount[i]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find skin index with lowest count
|
||||
int skinIndex = takenSkinIndices.size() % numberOfSkins;
|
||||
int lowestCount = takenSkinIndices.size();
|
||||
for (auto &pair: skinIndexCount)
|
||||
{
|
||||
if (pair.second < lowestCount)
|
||||
{
|
||||
lowestCount = pair.second;
|
||||
skinIndex = pair.first;
|
||||
}
|
||||
}
|
||||
|
||||
takenSkinIndices.push_back(skinIndex);
|
||||
return PLAYER_SKINS[skinIndex];
|
||||
}
|
||||
|
||||
void PlayerCollection::activatePlayers()
|
||||
{
|
||||
for (auto player: getPlayers())
|
||||
{
|
||||
player->setPassiveMode(false);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerCollection::deactivatePlayers()
|
||||
{
|
||||
for (auto player: getPlayers())
|
||||
{
|
||||
player->setPassiveMode(true);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerCollection::resetPlayers()
|
||||
{
|
||||
// Recreate players
|
||||
auto oldPlayers = getPlayers();
|
||||
clear();
|
||||
removedPlayerBuffer.clear();
|
||||
for (auto &player: oldPlayers)
|
||||
{
|
||||
spawnPlayer(player->getInput(), player->getSkinName());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Player>> PlayerCollection::getRemainingPlayers() const
|
||||
{
|
||||
std::vector<std::shared_ptr<Player>> remainingPlayers = {};
|
||||
for (auto &player: getPlayers())
|
||||
{
|
||||
if (player->getAlive())
|
||||
{
|
||||
remainingPlayers.push_back(player);
|
||||
}
|
||||
}
|
||||
return remainingPlayers;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
class PlayerCollection : public GameObject
|
||||
{
|
||||
public:
|
||||
explicit PlayerCollection(int maxPlayerCount = DEFAULT_MAX_PLAYER_NUMBER);
|
||||
PlayerCollection();
|
||||
|
||||
static std::shared_ptr<PlayerCollection> getInstance();
|
||||
|
||||
|
@ -18,45 +18,27 @@ public:
|
|||
|
||||
void removePlayer(const std::shared_ptr<Player>& player);
|
||||
|
||||
std::shared_ptr<Player> getClosestPlayer(const TranslatedCoordinates& point) const;
|
||||
|
||||
[[nodiscard]] std::vector<std::shared_ptr<Player>> getPlayers() const;
|
||||
|
||||
[[nodiscard]] std::shared_ptr<Player> getPlayerById(int playerId) const;
|
||||
|
||||
[[nodiscard]] std::vector<std::shared_ptr<Player>> getNewPlayers() const;
|
||||
|
||||
[[nodiscard]] std::vector<std::shared_ptr<Player>> getRemovedPlayers() const;
|
||||
|
||||
[[nodiscard]] std::vector<std::shared_ptr<Player>> getRemainingPlayers() const;
|
||||
|
||||
[[nodiscard]] int getMaxPlayerCount() const;
|
||||
|
||||
void lateUpdate() override;
|
||||
|
||||
void clear();
|
||||
|
||||
void resetPlayers();
|
||||
|
||||
void activatePlayers();
|
||||
void deactivatePlayers();
|
||||
|
||||
private:
|
||||
static inline std::shared_ptr<PlayerCollection> singletonInstance = nullptr;
|
||||
|
||||
std::vector<std::shared_ptr<Player>> players = {};
|
||||
std::vector<std::shared_ptr<Player>> newPlayerBuffer = {};
|
||||
std::vector<std::shared_ptr<Player>> removedPlayerBuffer = {};
|
||||
|
||||
std::vector<GridCoordinates> spawnPoints;
|
||||
int nextSpawnPointIndex = 0;
|
||||
|
||||
void spawnPlayer(const std::shared_ptr<InputIdentity> &inputIdentity, std::string skin = "");
|
||||
|
||||
std::vector<int> takenSkinIndices = {};
|
||||
|
||||
std::string getNextSkin();
|
||||
|
||||
int maxPlayerCount;
|
||||
void spawnPlayer(const std::shared_ptr<InputIdentity> &inputIdentity);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
#include <SFML/Graphics/Text.hpp>
|
||||
#include <iomanip>
|
||||
#include "countdown.hpp"
|
||||
#include "../../typography/font_manager.hpp"
|
||||
#include "../../config.h"
|
||||
|
||||
void Countdown::restart(int durationInSeconds)
|
||||
{
|
||||
this->durationInSeconds = durationInSeconds;
|
||||
timeElapsed = sf::Time::Zero;
|
||||
clock.restart();
|
||||
stopped = false;
|
||||
}
|
||||
|
||||
Countdown::Countdown(int durationInSeconds)
|
||||
{
|
||||
restart(durationInSeconds);
|
||||
}
|
||||
|
||||
void Countdown::stop()
|
||||
{
|
||||
stopped = true;
|
||||
}
|
||||
|
||||
bool Countdown::isFinished() const
|
||||
{
|
||||
return timeElapsed.asSeconds() >= durationInSeconds;
|
||||
}
|
||||
|
||||
void Countdown::update()
|
||||
{
|
||||
GameObject::update();
|
||||
|
||||
if (stopped)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
timeElapsed += clock.restart();
|
||||
}
|
||||
|
||||
void Countdown::draw(sf::RenderWindow *window)
|
||||
{
|
||||
GameObject::draw(window);
|
||||
|
||||
// Draw the countdown
|
||||
int timeLeft = durationInSeconds - timeElapsed.asSeconds();
|
||||
if (timeLeft <= 0)
|
||||
{
|
||||
timeLeft = 0;
|
||||
}
|
||||
|
||||
int minutes = timeLeft / 60;
|
||||
int seconds = timeLeft % 60;
|
||||
std::stringstream ss;
|
||||
ss << std::setfill('0') << std::setw(2) << minutes << ":" << std::setw(2) << seconds;
|
||||
auto timeLeftString = ss.str();
|
||||
|
||||
|
||||
auto font = FontManager::getInstance()->getDefaultFont();
|
||||
auto text = sf::Text(timeLeftString, *font, COUNTDOWN_FONT_SIZE);
|
||||
text.setFillColor(sf::Color::White);
|
||||
text.setPosition(1920 / 2.f, 5);
|
||||
|
||||
// Align top center
|
||||
sf::FloatRect textRect = text.getLocalBounds();
|
||||
text.setOrigin(textRect.left + textRect.width / 2.0f,
|
||||
textRect.top);
|
||||
window->draw(text);
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
#ifndef HOLESOME_COUNTDOWN_HPP
|
||||
#define HOLESOME_COUNTDOWN_HPP
|
||||
|
||||
|
||||
#include <SFML/System/Clock.hpp>
|
||||
#include "../game_object.h"
|
||||
|
||||
class Countdown : public GameObject
|
||||
{
|
||||
public:
|
||||
explicit Countdown(int durationInSeconds = 0);
|
||||
|
||||
void update() override;
|
||||
|
||||
void restart(int durationInSeconds);
|
||||
|
||||
void stop();
|
||||
|
||||
[[nodiscard]] bool isFinished() const;
|
||||
|
||||
void draw(sf::RenderWindow *window) override;
|
||||
|
||||
private:
|
||||
sf::Clock clock;
|
||||
int durationInSeconds = 0;
|
||||
sf::Time timeElapsed = sf::Time::Zero;
|
||||
bool stopped = false;
|
||||
};
|
||||
|
||||
|
||||
#endif //HOLESOME_COUNTDOWN_HPP
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "game/input/input_device_group.h"
|
||||
#include "game/input/button_config.hpp"
|
||||
#include "game/input/game_action_config.hpp"
|
||||
|
@ -18,68 +17,36 @@ const std::map<sf::Keyboard::Key, ButtonConfig> KEY_CONFIGS = {
|
|||
{sf::Keyboard::Down, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, HardDirection::DOWN)},
|
||||
{sf::Keyboard::Left, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, HardDirection::LEFT)},
|
||||
{sf::Keyboard::Right, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, HardDirection::RIGHT)},
|
||||
{sf::Keyboard::RShift, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, {GameAction::RUN})},
|
||||
{sf::Keyboard::RControl, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, {GameAction::RUN})},
|
||||
{sf::Keyboard::Numpad0, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, {GameAction::RUN})},
|
||||
{sf::Keyboard::Space, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, {GameAction::CONFIRM})},
|
||||
{sf::Keyboard::RControl, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, {GameAction::GROW})},
|
||||
{sf::Keyboard::Numpad0, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, {GameAction::SHRINK})},
|
||||
|
||||
{sf::Keyboard::W, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, HardDirection::UP)},
|
||||
{sf::Keyboard::S, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, HardDirection::DOWN)},
|
||||
{sf::Keyboard::A, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, HardDirection::LEFT)},
|
||||
{sf::Keyboard::D, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, HardDirection::RIGHT)},
|
||||
{sf::Keyboard::Q, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, {GameAction::CONFIRM})},
|
||||
{sf::Keyboard::E, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, {GameAction::CONFIRM})},
|
||||
{sf::Keyboard::LShift, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, {GameAction::RUN})},
|
||||
{sf::Keyboard::LControl, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, {GameAction::RUN})},
|
||||
{sf::Keyboard::Space, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, {GameAction::GROW})},
|
||||
{sf::Keyboard::Q, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, {GameAction::SHRINK})},
|
||||
|
||||
{sf::Keyboard::I, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::UP)},
|
||||
{sf::Keyboard::K, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::DOWN)},
|
||||
{sf::Keyboard::J, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::LEFT)},
|
||||
{sf::Keyboard::L, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::RIGHT)},
|
||||
{sf::Keyboard::U, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, {GameAction::CONFIRM})},
|
||||
{sf::Keyboard::O, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, {GameAction::CONFIRM})},
|
||||
{sf::Keyboard::RAlt, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, {GameAction::RUN})},
|
||||
{sf::Keyboard::B, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, {GameAction::RUN})}
|
||||
{sf::Keyboard::U, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, {GameAction::GROW})},
|
||||
{sf::Keyboard::O, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, {GameAction::SHRINK})}
|
||||
};
|
||||
|
||||
|
||||
// Gamepad buttons
|
||||
const std::map<int, ButtonConfig> GAMEPAD_BUTTON_CONFIGS = {
|
||||
{GamepadButton::EAST, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::CONFIRM})},
|
||||
{GamepadButton::SOUTH, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::CONFIRM})},
|
||||
{GamepadButton::RIGHT_SHOULDER, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::RUN})},
|
||||
{GamepadButton::LEFT_SHOULDER, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::RUN})},
|
||||
{GamepadButton::RIGHT_TRIGGER, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::RUN})},
|
||||
{GamepadButton::LEFT_TRIGGER, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::RUN})},
|
||||
{GamepadButton::NORTH, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::RUN})},
|
||||
{GamepadButton::WEST, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::RUN})}
|
||||
{GamepadButton::EAST, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::GROW})},
|
||||
{GamepadButton::SOUTH, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::SHRINK})}
|
||||
};
|
||||
|
||||
|
||||
// Actions
|
||||
const std::map<GameAction, GameActionConfig> GAME_ACTION_CONFIGS = {
|
||||
{GameAction::CONFIRM, GameActionConfig(InteractionMode::PRESS)},
|
||||
{GameAction::RUN, GameActionConfig(InteractionMode::HOLD)}
|
||||
};
|
||||
|
||||
|
||||
const std::map<InputDeviceGroup, std::string> DEVICE_GROUP_NAMES = {
|
||||
{InputDeviceGroup::KEYBOARD_WASD, "WASD"},
|
||||
{InputDeviceGroup::KEYBOARD_IJKL, "IJKL"},
|
||||
{InputDeviceGroup::KEYBOARD_ARROWS, "Arrow keys"},
|
||||
{InputDeviceGroup::GAMEPAD, "Gamepad"}
|
||||
};
|
||||
const std::map<InputDeviceGroup, std::string> DEVICE_GROUP_CONFIRM = {
|
||||
{InputDeviceGroup::KEYBOARD_WASD, "Q or E"},
|
||||
{InputDeviceGroup::KEYBOARD_IJKL, "U or O"},
|
||||
{InputDeviceGroup::KEYBOARD_ARROWS, "Space"},
|
||||
{InputDeviceGroup::GAMEPAD, "A"}
|
||||
};
|
||||
const std::map<InputDeviceGroup, std::string> DEVICE_GROUP_RUN = {
|
||||
{InputDeviceGroup::KEYBOARD_WASD, "L-Shift or -Ctrl"},
|
||||
{InputDeviceGroup::KEYBOARD_IJKL, "B or R-Alt"},
|
||||
{InputDeviceGroup::KEYBOARD_ARROWS, "R-Shift, -Ctrl or Pad-0"},
|
||||
{InputDeviceGroup::GAMEPAD, "Trigger or Shoulder"}
|
||||
{GameAction::GROW, GameActionConfig(InteractionMode::HOLD)},
|
||||
{GameAction::SHRINK, GameActionConfig(InteractionMode::HOLD)}
|
||||
};
|
||||
|
||||
#endif //HOLESOME_INPUT_CONFIG_H
|
||||
|
|
|
@ -8,64 +8,9 @@
|
|||
#define INITIAL_LEVEL "default"
|
||||
|
||||
std::map<std::string, LevelConfig> const all_levels = {
|
||||
{"default", LevelConfig("Default",
|
||||
120,
|
||||
{
|
||||
{0, 0},
|
||||
{18, 18},
|
||||
{0, 18},
|
||||
{18, 0}
|
||||
}, {
|
||||
CollectableInLevel("rose", {3, 5}),
|
||||
CollectableInLevel("rosebush", {4, 5}),
|
||||
CollectableInLevel("stone", {10, 6}),
|
||||
CollectableInLevel("bike", {2, 8}),
|
||||
CollectableInLevel("rose", {1, 2}),
|
||||
CollectableInLevel("small-tree", {4, 3}),
|
||||
CollectableInLevel("rose", {8, 3}),
|
||||
CollectableInLevel("lantern", {6, 7}),
|
||||
CollectableInLevel("rose", {5, 5}),
|
||||
CollectableInLevel("tram", {9, 5}),
|
||||
CollectableInLevel("rose", {0, 1})
|
||||
},
|
||||
{
|
||||
// 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
|
||||
},
|
||||
TileMapConfig("iso-tiles", {
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
|
||||
{4, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
|
||||
{4, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4},
|
||||
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}
|
||||
})
|
||||
)}
|
||||
{"default", LevelConfig("Default", {25, 25}, {{0, 0}}, {
|
||||
CollectableInLevel("box", {5, 5})
|
||||
})}
|
||||
};
|
||||
|
||||
#endif //HOLESOME_LEVELS_HPP
|
||||
|
|
|
@ -694,7 +694,7 @@ class ConfigurationTypeHelper : base::StaticClass {
|
|||
/// This bool represent whether or not to exit iterating through configurations.
|
||||
static inline void forEachConfigType(base::type::EnumType* startIndex, const std::function<bool(void)>& fn);
|
||||
};
|
||||
/// @brief Flags used while writing logs. This flags are setWorld by user
|
||||
/// @brief Flags used while writing logs. This flags are set by user
|
||||
enum class LoggingFlag : base::type::EnumType {
|
||||
/// @brief Makes sure we have new line for each container log entry
|
||||
NewLineForContainer = 1,
|
||||
|
@ -1701,7 +1701,7 @@ class Configuration : public Loggable {
|
|||
}
|
||||
|
||||
/// @brief Set string based configuration value
|
||||
/// @param value Value to setWorld. Values have to be std::string; For boolean values use "true", "false", for any integral values
|
||||
/// @param value Value to set. Values have to be std::string; For boolean values use "true", "false", for any integral values
|
||||
/// use them in quotes. They will be parsed when configuring
|
||||
inline void setValue(const std::string& value) {
|
||||
m_value = value;
|
||||
|
@ -1735,9 +1735,9 @@ class Configurations : public base::utils::RegistryWithPred<Configuration, Confi
|
|||
/// @brief Default constructor with empty repository
|
||||
Configurations(void);
|
||||
|
||||
/// @brief Constructor used to setWorld configurations using configuration file.
|
||||
/// @brief Constructor used to set configurations using configuration file.
|
||||
/// @param configurationFile Full path to configuration file
|
||||
/// @param useDefaultsForRemaining Lets you setWorld the remaining configurations to default.
|
||||
/// @param useDefaultsForRemaining Lets you set the remaining configurations to default.
|
||||
/// @param base If provided, this configuration will be based off existing repository that this argument is pointing to.
|
||||
/// @see parseFromFile(const std::string&, Configurations* base)
|
||||
/// @see setRemainingToDefault()
|
||||
|
@ -1750,7 +1750,7 @@ class Configurations : public base::utils::RegistryWithPred<Configuration, Confi
|
|||
/// @brief Parses configuration from file.
|
||||
/// @param configurationFile Full path to configuration file
|
||||
/// @param base Configurations to base new configuration repository off. This value is used when you want to use
|
||||
/// existing Configurations to base all the values and then setWorld rest of configuration via configuration file.
|
||||
/// existing Configurations to base all the values and then set rest of configuration via configuration file.
|
||||
/// @return True if successfully parsed, false otherwise. You may define 'ELPP_DEBUG_ASSERT_FAILURE' to make sure you
|
||||
/// do not proceed without successful parse.
|
||||
bool parseFromFile(const std::string& configurationFile, Configurations* base = nullptr);
|
||||
|
@ -1760,7 +1760,7 @@ class Configurations : public base::utils::RegistryWithPred<Configuration, Confi
|
|||
/// @detail This configuration string has same syntax as configuration file contents. Make sure all the necessary
|
||||
/// new line characters are provided.
|
||||
/// @param base Configurations to base new configuration repository off. This value is used when you want to use
|
||||
/// existing Configurations to base all the values and then setWorld rest of configuration via configuration text.
|
||||
/// existing Configurations to base all the values and then set rest of configuration via configuration text.
|
||||
/// @return True if successfully parsed, false otherwise. You may define 'ELPP_DEBUG_ASSERT_FAILURE' to make sure you
|
||||
/// do not proceed without successful parse.
|
||||
bool parseFromText(const std::string& configurationsString, Configurations* base = nullptr);
|
||||
|
@ -1783,9 +1783,9 @@ class Configurations : public base::utils::RegistryWithPred<Configuration, Confi
|
|||
/// @brief Sets value of configuration for specified level.
|
||||
///
|
||||
/// @detail Any existing configuration for specified level will be replaced. Also note that configuration types
|
||||
/// ConfigurationType::SubsecondPrecision and ConfigurationType::PerformanceTracking will be ignored if not setWorld for
|
||||
/// ConfigurationType::SubsecondPrecision and ConfigurationType::PerformanceTracking will be ignored if not set for
|
||||
/// Level::Global because these configurations are not dependant on level.
|
||||
/// @param level Level to setWorld configuration for (el::Level).
|
||||
/// @param level Level to set configuration for (el::Level).
|
||||
/// @param configurationType Type of configuration (el::ConfigurationType)
|
||||
/// @param value A string based value. Regardless of what the data type of configuration is, it will always be string
|
||||
/// from users' point of view. This is then parsed later to be used internally.
|
||||
|
@ -1795,7 +1795,7 @@ class Configurations : public base::utils::RegistryWithPred<Configuration, Confi
|
|||
void set(Level level, ConfigurationType configurationType, const std::string& value);
|
||||
|
||||
/// @brief Sets single configuration based on other single configuration.
|
||||
/// @see setWorld(Level level, ConfigurationType configurationType, const std::string& value)
|
||||
/// @see set(Level level, ConfigurationType configurationType, const std::string& value)
|
||||
void set(Configuration* conf);
|
||||
|
||||
inline Configuration* get(Level level, ConfigurationType configurationType) {
|
||||
|
@ -1806,7 +1806,7 @@ class Configurations : public base::utils::RegistryWithPred<Configuration, Confi
|
|||
/// @brief Sets configuration for all levels.
|
||||
/// @param configurationType Type of configuration
|
||||
/// @param value String based value
|
||||
/// @see Configurations::setWorld(Level level, ConfigurationType configurationType, const std::string& value)
|
||||
/// @see Configurations::set(Level level, ConfigurationType configurationType, const std::string& value)
|
||||
inline void setGlobally(ConfigurationType configurationType, const std::string& value) {
|
||||
setGlobally(configurationType, value, false);
|
||||
}
|
||||
|
@ -1819,7 +1819,7 @@ class Configurations : public base::utils::RegistryWithPred<Configuration, Confi
|
|||
|
||||
/// @brief Gets configuration file used in parsing this configurations.
|
||||
///
|
||||
/// @detail If this repository was setWorld manually or by text this returns empty string.
|
||||
/// @detail If this repository was set manually or by text this returns empty string.
|
||||
inline const std::string& configurationFile(void) const {
|
||||
return m_configurationFile;
|
||||
}
|
||||
|
@ -1827,11 +1827,11 @@ class Configurations : public base::utils::RegistryWithPred<Configuration, Confi
|
|||
/// @brief Sets configurations to "factory based" configurations.
|
||||
void setToDefault(void);
|
||||
|
||||
/// @brief Lets you setWorld the remaining configurations to default.
|
||||
/// @brief Lets you set the remaining configurations to default.
|
||||
///
|
||||
/// @detail By remaining, it means that the level/type a configuration does not exist for.
|
||||
/// This function is useful when you want to minimize chances of failures, e.g, if you have a configuration file that sets
|
||||
/// configuration for all the configurations except for Enabled or not, we use this so that ENABLED is setWorld to default i.e,
|
||||
/// configuration for all the configurations except for Enabled or not, we use this so that ENABLED is set to default i.e,
|
||||
/// true. If you dont do this explicitly (either by calling this function or by using second param in Constructor
|
||||
/// and try to access a value, an error is thrown
|
||||
void setRemainingToDefault(void);
|
||||
|
@ -1846,7 +1846,7 @@ class Configurations : public base::utils::RegistryWithPred<Configuration, Confi
|
|||
/// @param configurationFile Full path to configuration file
|
||||
/// @param sender Sender configurations pointer. Usually 'this' is used from calling class
|
||||
/// @param base Configurations to base new configuration repository off. This value is used when you want to use
|
||||
/// existing Configurations to base all the values and then setWorld rest of configuration via configuration file.
|
||||
/// existing Configurations to base all the values and then set rest of configuration via configuration file.
|
||||
/// @return True if successfully parsed, false otherwise. You may define '_STOP_ON_FIRSTELPP_ASSERTION' to make sure you
|
||||
/// do not proceed without successful parse.
|
||||
static bool parseFromFile(const std::string& configurationFile, Configurations* sender,
|
||||
|
@ -1860,7 +1860,7 @@ class Configurations : public base::utils::RegistryWithPred<Configuration, Confi
|
|||
/// @param configurationsString the configuration in plain text format
|
||||
/// @param sender Sender configurations pointer. Usually 'this' is used from calling class
|
||||
/// @param base Configurations to base new configuration repository off. This value is used when you want to use
|
||||
/// existing Configurations to base all the values and then setWorld rest of configuration via configuration text.
|
||||
/// existing Configurations to base all the values and then set rest of configuration via configuration text.
|
||||
/// @return True if successfully parsed, false otherwise.
|
||||
static bool parseFromText(const std::string& configurationsString, Configurations* sender,
|
||||
Configurations* base = nullptr);
|
||||
|
@ -1883,7 +1883,7 @@ class Configurations : public base::utils::RegistryWithPred<Configuration, Confi
|
|||
/// @brief Unsafely sets configuration if does not already exist
|
||||
void unsafeSetIfNotExist(Level level, ConfigurationType configurationType, const std::string& value);
|
||||
|
||||
/// @brief Thread unsafe setWorld
|
||||
/// @brief Thread unsafe set
|
||||
void unsafeSet(Level level, ConfigurationType configurationType, const std::string& value);
|
||||
|
||||
/// @brief Sets configurations for all levels including Level::Global if includeGlobalLevel is true
|
||||
|
@ -3839,7 +3839,7 @@ class Loggers : base::StaticClass {
|
|||
static std::vector<std::string>* populateAllLoggerIds(std::vector<std::string>* targetList);
|
||||
/// @brief Sets configurations from global configuration file.
|
||||
static void configureFromGlobal(const char* globalConfigurationFilePath);
|
||||
/// @brief Configures loggers using command line arg. Ensure you have already setWorld command line args,
|
||||
/// @brief Configures loggers using command line arg. Ensure you have already set command line args,
|
||||
/// @return False if invalid argument or argument with no value provided, true if attempted to configure logger.
|
||||
/// If true is returned that does not mean it has been configured successfully, it only means that it
|
||||
/// has attempted to configure logger using configuration file provided in argument
|
||||
|
|
20
src/main.cpp
|
@ -4,14 +4,11 @@
|
|||
#include "texture_config.h"
|
||||
#include "game/level/level_loader.hpp"
|
||||
#include "levels.hpp"
|
||||
#include "typography/font_manager.hpp"
|
||||
|
||||
void loadAllTextures();
|
||||
|
||||
void runGame();
|
||||
|
||||
void loadAllFonts();
|
||||
|
||||
INITIALIZE_EASYLOGGINGPP
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
|
@ -19,26 +16,17 @@ int main(int argc, char *argv[])
|
|||
START_EASYLOGGINGPP(argc, argv);
|
||||
|
||||
loadAllTextures();
|
||||
loadAllFonts();
|
||||
runGame();
|
||||
}
|
||||
|
||||
void loadAllFonts()
|
||||
{
|
||||
LOG(INFO) << "Loading fonts...";
|
||||
for (auto const &[key, path]: all_fonts)
|
||||
{
|
||||
FontManager::getInstance()->loadFont(key, path);
|
||||
}
|
||||
LOG(INFO) << "Finished loading fonts.";
|
||||
}
|
||||
|
||||
void runGame()
|
||||
{
|
||||
LOG(INFO) << "Starting game ...";
|
||||
auto game = GameFactory::createFullscreen(GAME_NAME);
|
||||
auto game = GameFactory::createWindowed("Holesome");
|
||||
|
||||
// Load initial level
|
||||
LevelLoader::loadLevel(INITIAL_LEVEL);
|
||||
|
||||
game->showJoinScreen();
|
||||
game->run();
|
||||
|
||||
InputMapper::getInstance().reset();
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
#include <iostream>
|
||||
#include <Eigen/Dense>
|
||||
#include "../coordinates/translated_coordinates.h"
|
||||
|
||||
enum CoordinateSystem
|
||||
{
|
||||
NONE,
|
||||
WORLD,
|
||||
DIAGONAL,
|
||||
ISOMETRIC
|
||||
};
|
||||
|
||||
CoordinateSystem inputCoordinateSystem()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::cout << "Enter coordinate system (w/d/i) or x to close: ";
|
||||
char coordinateSystemIndicator;
|
||||
std::cin >> coordinateSystemIndicator;
|
||||
|
||||
switch (coordinateSystemIndicator)
|
||||
{
|
||||
case 'x':
|
||||
std::cout << "Closing ..." << std::endl;
|
||||
return NONE;
|
||||
case 'w':
|
||||
std::cout << "World coordinates" << std::endl;
|
||||
return WORLD;
|
||||
case 'd':
|
||||
std::cout << "Diagonal world coordinates" << std::endl;
|
||||
return DIAGONAL;
|
||||
case 'i':
|
||||
std::cout << "Isometric coordinates" << std::endl;
|
||||
return ISOMETRIC;
|
||||
default:
|
||||
std::cout << "Invalid coordinate system. Try again ..." << std::endl;
|
||||
continue;
|
||||
}
|
||||
} catch (...)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<float> input3values(std::string prompt = "Enter 3 values: ")
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::vector<float> values(3);
|
||||
std::cout << prompt;
|
||||
std::cin >> values[0] >> values[1] >> values[2];
|
||||
return values;
|
||||
} catch (...)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TranslatedCoordinates worldToTranslated(std::vector<float> values)
|
||||
{
|
||||
return TranslatedCoordinates(WorldCoordinates{values[0], values[1], values[2]});
|
||||
}
|
||||
|
||||
TranslatedCoordinates diagonalToTranslated(std::vector<float> values)
|
||||
{
|
||||
return TranslatedCoordinates(DiagonalWorldCoordinates{values[0], values[1], values[2]});
|
||||
}
|
||||
|
||||
TranslatedCoordinates isometricToTranslated(std::vector<float> values)
|
||||
{
|
||||
auto world = CoordinateTransformer::isometricToWorld(IsometricCoordinates{values[0], values[1], values[2]});
|
||||
return TranslatedCoordinates(world);
|
||||
}
|
||||
|
||||
void printCoordinates(TranslatedCoordinates coordinates)
|
||||
{
|
||||
std::cout << "World coordinates (x, y, z): " << coordinates.world().x << ", " << coordinates.world().y << ", "
|
||||
<< coordinates.world().z << std::endl;
|
||||
std::cout << "Diagonal world coordinates (horizontal, vertical, depth): " << coordinates.diagonalWorld().horizontal << ", "
|
||||
<< coordinates.diagonalWorld().vertical << ", " << coordinates.diagonalWorld().depth
|
||||
<< std::endl;
|
||||
std::cout << "Isometric coordinates (x, y, depth): " << coordinates.isometric().x << ", "
|
||||
<< coordinates.isometric().y << ", " << coordinates.isometric().depth << std::endl;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
CoordinateSystem coordinateSystem = inputCoordinateSystem();
|
||||
TranslatedCoordinates translatedCoordinates = TranslatedCoordinates(WorldCoordinates{0, 0, 0});
|
||||
switch (coordinateSystem)
|
||||
{
|
||||
case NONE:
|
||||
return 0;
|
||||
case WORLD:
|
||||
translatedCoordinates = worldToTranslated(input3values("Enter world coordinates (x, y, z): "));
|
||||
break;
|
||||
case DIAGONAL:
|
||||
translatedCoordinates = diagonalToTranslated(
|
||||
input3values("Enter diagonal world coordinates (horizontal, vertical, depth): "));
|
||||
break;
|
||||
case ISOMETRIC:
|
||||
translatedCoordinates = isometricToTranslated(
|
||||
input3values("Enter isometric coordinates (x, y, depth): "));
|
||||
break;
|
||||
}
|
||||
printCoordinates(translatedCoordinates);
|
||||
|
||||
// Wait for enter, then clear screen
|
||||
std::cin.ignore();
|
||||
std::cin.get();
|
||||
std::cout << "\033[2J\033[1;1H";
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
#include <SFML/Graphics/RenderWindow.hpp>
|
||||
#include <SFML/Window/Event.hpp>
|
||||
#include <SFML/Graphics/CircleShape.hpp>
|
||||
#include <SFML/Graphics/RectangleShape.hpp>
|
||||
|
||||
void drawMainContent(sf::RenderWindow *window)
|
||||
{
|
||||
sf::RectangleShape rectangle(sf::Vector2f(800, 600));
|
||||
rectangle.setFillColor(sf::Color::Green);
|
||||
rectangle.setPosition(0, 0);
|
||||
window->draw(rectangle);
|
||||
|
||||
sf::CircleShape circle(50);
|
||||
circle.setFillColor(sf::Color::Red);
|
||||
circle.setPosition(150, 150);
|
||||
window->draw(circle);
|
||||
}
|
||||
|
||||
void drawMiniMapContent(sf::RenderWindow *window)
|
||||
{
|
||||
sf::RectangleShape rectangle(sf::Vector2f(196, 196));
|
||||
rectangle.setFillColor(sf::Color::White);
|
||||
rectangle.setPosition(2, 2);
|
||||
rectangle.setOutlineColor(sf::Color::Black);
|
||||
rectangle.setOutlineThickness(2);
|
||||
window->draw(rectangle);
|
||||
|
||||
sf::CircleShape circle(20);
|
||||
circle.setFillColor(sf::Color::Blue);
|
||||
circle.setPosition(80, 80);
|
||||
window->draw(circle);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// Create simple window
|
||||
sf::RenderWindow window(sf::VideoMode(800, 600), "Minimap example");
|
||||
|
||||
// Create minimap view
|
||||
sf::View minimapView(sf::FloatRect(0, 0, 200, 200));
|
||||
// Set viewport to the top right corner
|
||||
minimapView.setViewport(sf::FloatRect(0.75f, 0, 0.25f, 0.25f));
|
||||
|
||||
while (window.isOpen()) {
|
||||
window.clear(sf::Color::Black);
|
||||
|
||||
// Draw main content
|
||||
auto fullView = window.getDefaultView();
|
||||
window.setView(fullView);
|
||||
|
||||
drawMainContent(&window);
|
||||
|
||||
|
||||
// Draw minimap
|
||||
window.setView(minimapView);
|
||||
|
||||
drawMiniMapContent(&window);
|
||||
|
||||
|
||||
// Finished
|
||||
window.display();
|
||||
|
||||
// Handle close event
|
||||
sf::Event event{};
|
||||
while (window.pollEvent(event)) {
|
||||
if (event.type == sf::Event::Closed) {
|
||||
window.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,179 +0,0 @@
|
|||
#include "join_screen.hpp"
|
||||
#include "../config.h"
|
||||
#include "../typography/font_manager.hpp"
|
||||
#include "../game/player/player_collection.hpp"
|
||||
#include "../levels.hpp"
|
||||
#include "../game/level/level_loader.hpp"
|
||||
#include "../game/game.h"
|
||||
|
||||
JoinScreen::JoinScreen()
|
||||
{
|
||||
// Title text
|
||||
auto titleText = std::make_shared<sf::Text>();
|
||||
titleText->setString(GAME_NAME);
|
||||
titleText->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||
titleText->setCharacterSize(150);
|
||||
titleText->setStyle(sf::Text::Bold);
|
||||
titleText->setFillColor(sf::Color::White);
|
||||
titleText->setPosition(REFERENCE_SIZE.x / 2.f - titleText->getGlobalBounds().width / 2.f, 20);
|
||||
add(titleText);
|
||||
|
||||
// Create join-player text
|
||||
auto joinText = std::make_shared<sf::Text>();
|
||||
joinText->setString("Press button to join");
|
||||
joinText->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||
joinText->setCharacterSize(24);
|
||||
joinText->setFillColor(sf::Color::White);
|
||||
joinText->setPosition(REFERENCE_SIZE.x / 2.f - joinText->getGlobalBounds().width / 2.f, 200);
|
||||
add(joinText);
|
||||
}
|
||||
|
||||
void JoinScreen::update()
|
||||
{
|
||||
Screen::update();
|
||||
|
||||
// Did any player start game?
|
||||
auto players = PlayerCollection::getInstance()->getPlayers();
|
||||
for (auto &player: players)
|
||||
{
|
||||
auto input = player->getInput();
|
||||
if (input->isPerformingAction(GameAction::CONFIRM))
|
||||
{
|
||||
// Start game
|
||||
Game::getInstance()->generateNewLevel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update player list items
|
||||
if (players.size() == lastPlayerCount)
|
||||
{
|
||||
return;
|
||||
}
|
||||
lastPlayerCount = players.size();
|
||||
|
||||
playerList.clear();
|
||||
playerListObjects.clear();
|
||||
int index = 0;
|
||||
for (auto &player: players)
|
||||
{
|
||||
addPlayerAtIndex(player, index);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
void JoinScreen::draw(sf::RenderWindow *window)
|
||||
{
|
||||
Screen::draw(window);
|
||||
|
||||
// Draw player list items
|
||||
for (auto &item: playerList)
|
||||
{
|
||||
window->draw(*item);
|
||||
}
|
||||
|
||||
for (auto &item: playerListObjects)
|
||||
{
|
||||
item->draw(window);
|
||||
}
|
||||
}
|
||||
|
||||
void JoinScreen::addPlayerAtIndex(std::shared_ptr<Player> &player, int index)
|
||||
{
|
||||
int numberOfMaxPlayers = PlayerCollection::getInstance()->getMaxPlayerCount();
|
||||
int paddingLeftRight = 100;
|
||||
int paddingTop = 400;
|
||||
int relativePaddingPerColumn = 0.1;
|
||||
int columnWidth = (REFERENCE_SIZE.x - 2 * paddingLeftRight) / numberOfMaxPlayers;
|
||||
int paddingPerColumn = columnWidth * relativePaddingPerColumn;
|
||||
int contentWidth = columnWidth - paddingPerColumn;
|
||||
auto topLeft = sf::Vector2f(paddingLeftRight + columnWidth * index,
|
||||
paddingTop);
|
||||
|
||||
// Create player character
|
||||
auto skinSprite = std::make_shared<VersatileSprite>(player->getSkinName(),
|
||||
sf::Vector2f(contentWidth, contentWidth * ISOMETRIC_SKEW) *
|
||||
0.9f);
|
||||
skinSprite->coordinates->setIsometric(IsometricCoordinates(topLeft));
|
||||
playerListObjects.push_back(skinSprite);
|
||||
skinSprite->update();
|
||||
skinSprite->lateUpdate();
|
||||
skinSprite->preRenderUpdate();
|
||||
|
||||
// Show device group
|
||||
auto deviceGroupText = std::make_shared<sf::Text>();
|
||||
InputDeviceGroup deviceGroup = player->getInput()->deviceGroup;
|
||||
deviceGroupText->setString(DEVICE_GROUP_NAMES.at(deviceGroup));
|
||||
deviceGroupText->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||
deviceGroupText->setCharacterSize(24);
|
||||
deviceGroupText->setFillColor(sf::Color::White);
|
||||
deviceGroupText->setPosition(topLeft.x + contentWidth / 2.f * 0.9f - deviceGroupText->getGlobalBounds().width / 2.f,
|
||||
topLeft.y + skinSprite->getSize().y + 20);
|
||||
playerList.push_back(deviceGroupText);
|
||||
|
||||
// Show button to run
|
||||
auto runText = std::make_shared<sf::Text>();
|
||||
runText->setString("Press");
|
||||
runText->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||
runText->setCharacterSize(15);
|
||||
runText->setFillColor(sf::Color::White);
|
||||
runText->setPosition(topLeft.x + contentWidth / 2.f * 0.9f - runText->getGlobalBounds().width / 2.f,
|
||||
topLeft.y + skinSprite->getSize().y + 60);
|
||||
playerList.push_back(runText);
|
||||
|
||||
std::string buttonToRun = DEVICE_GROUP_RUN.at(deviceGroup);
|
||||
auto runButton = std::make_shared<sf::Text>();
|
||||
runButton->setString(buttonToRun);
|
||||
runButton->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||
|
||||
runButton->setCharacterSize(15);
|
||||
runButton->setFillColor(sf::Color::White);
|
||||
runButton->setPosition(topLeft.x + contentWidth / 2.f * 0.9f - runButton->getGlobalBounds().width / 2.f,
|
||||
topLeft.y + skinSprite->getSize().y + 80);
|
||||
playerList.push_back(runButton);
|
||||
|
||||
auto confirmText = std::make_shared<sf::Text>();
|
||||
confirmText->setString("to run");
|
||||
confirmText->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||
confirmText->setCharacterSize(15);
|
||||
confirmText->setFillColor(sf::Color::White);
|
||||
confirmText->setPosition(topLeft.x + contentWidth / 2.f * 0.9f - confirmText->getGlobalBounds().width / 2.f,
|
||||
topLeft.y + skinSprite->getSize().y + 100);
|
||||
playerList.push_back(confirmText);
|
||||
|
||||
|
||||
if (lastPlayerCount < MIN_PLAYER_COUNT)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Show button to press to start
|
||||
auto pressText = std::make_shared<sf::Text>();
|
||||
pressText->setString("Press");
|
||||
pressText->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||
pressText->setCharacterSize(24);
|
||||
pressText->setFillColor(sf::Color::Yellow);
|
||||
pressText->setPosition(topLeft.x + contentWidth / 2.f * 0.9f - pressText->getGlobalBounds().width / 2.f,
|
||||
topLeft.y + skinSprite->getSize().y + 170);
|
||||
playerList.push_back(pressText);
|
||||
|
||||
std::string buttonToStart = DEVICE_GROUP_CONFIRM.at(deviceGroup);
|
||||
auto buttonText = std::make_shared<sf::Text>();
|
||||
buttonText->setString(buttonToStart);
|
||||
buttonText->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||
buttonText->setCharacterSize(24);
|
||||
buttonText->setFillColor(sf::Color::Yellow);
|
||||
buttonText->setPosition(topLeft.x + contentWidth / 2.f * 0.9f - buttonText->getGlobalBounds().width / 2.f,
|
||||
topLeft.y + skinSprite->getSize().y + 200);
|
||||
playerList.push_back(buttonText);
|
||||
|
||||
auto toStartText = std::make_shared<sf::Text>();
|
||||
toStartText->setString("to start");
|
||||
toStartText->setFont(*FontManager::getInstance()->getDefaultFont());
|
||||
toStartText->setCharacterSize(24);
|
||||
toStartText->setFillColor(sf::Color::Yellow);
|
||||
toStartText->setPosition(topLeft.x + contentWidth / 2.f * 0.9f - toStartText->getGlobalBounds().width / 2.f,
|
||||
topLeft.y + skinSprite->getSize().y + 230);
|
||||
playerList.push_back(toStartText);
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
#ifndef HOLESOME_JOIN_SCREEN_HPP
|
||||
#define HOLESOME_JOIN_SCREEN_HPP
|
||||
|
||||
|
||||
#include "screen.hpp"
|
||||
#include "../game/player/player.hpp"
|
||||
#include <SFML/Graphics/Text.hpp>
|
||||
|
||||
class JoinScreen : public Screen
|
||||
{
|
||||
public:
|
||||
JoinScreen();
|
||||
|
||||
void update() override;
|
||||
|
||||
void draw(sf::RenderWindow *window) override;
|
||||
|
||||
private:
|
||||
int lastPlayerCount = -1;
|
||||
std::vector<std::shared_ptr<sf::Drawable>> playerList = {};
|
||||
std::vector<std::shared_ptr<GameObject>> playerListObjects = {};
|
||||
|
||||
void addPlayerAtIndex(std::shared_ptr<Player> &player, int index);
|
||||
};
|
||||
|
||||
|
||||
#endif //HOLESOME_JOIN_SCREEN_HPP
|