Compare commits
55 commits
feature-gr
...
master
Author | SHA1 | Date | |
---|---|---|---|
28d2a9da91 | |||
7cfe033668 | |||
c912784a7d | |||
95562a9da7 | |||
ac435f8e4f | |||
36aa2fa245 | |||
851e591c3c | |||
f50a346e22 | |||
7703128749 | |||
15c9632941 | |||
24cc3cfd6b | |||
241d8119ce | |||
b0a83a90ef | |||
364f4c6423 | |||
c49d937e20 | |||
|
d009ef328c | ||
|
16ddfaa942 | ||
|
e8c9a14199 | ||
|
b4eeb90ce9 | ||
|
762b163655 | ||
b457c33a72 | |||
a5ceecd2a4 | |||
949412df8a | |||
1c98c75cf9 | |||
e8319fd6b9 | |||
24963b2d0a | |||
23fb9d4fd5 | |||
b5d7dfede9 | |||
|
ad5a6731a3 | ||
8034b9164d | |||
|
cf6ab330c6 | ||
|
a309976423 | ||
|
784ac3ba4a | ||
|
6f49fecab4 | ||
|
77dc2f0ceb | ||
|
4ecc53abb3 | ||
|
26561f5d13 | ||
|
1aae9e6cb6 | ||
62f69a7593 | |||
b36fec6b3b | |||
4a44a41c80 | |||
06a6b1b029 | |||
66cc4fcb5f | |||
aa288edd69 | |||
27b6e1b324 | |||
120fdb0a88 | |||
207a31d3d0 | |||
a9bcdaeb63 | |||
414f3b79fc | |||
d3e6e35c9b | |||
88f19ae5e4 | |||
12b73c00ba | |||
4b96f4f9be | |||
74ca505b60 | |||
7934d623da |
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
/build/
|
||||
/build-release/
|
||||
/.idea/
|
|
@ -29,8 +29,6 @@ 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
|
||||
|
@ -52,12 +50,10 @@ set(SOURCES
|
|||
src/game/camera/ITrackable.h
|
||||
src/game/input/input_identity.h
|
||||
src/utilities/magic_enum.hpp
|
||||
src/game/player/player_spawner.cpp
|
||||
src/game/player/player_spawner.hpp
|
||||
src/game/camera/tracking_area.h
|
||||
src/game/camera/tracking_view_options.hpp
|
||||
src/game/collectables/environment_collectable.cpp
|
||||
src/game/collectables/environment_collectable.hpp
|
||||
src/game/collectables/collectable.cpp
|
||||
src/game/collectables/collectable.hpp
|
||||
src/sprites/texture_manager.cpp
|
||||
src/sprites/texture_manager.hpp
|
||||
src/sprites/sprite_sheet.cpp
|
||||
|
@ -82,13 +78,67 @@ set(SOURCES
|
|||
src/game/input/button_config_factory.hpp
|
||||
src/game/input/game_action_config.hpp
|
||||
src/game/input/gamepad_buttons.hpp
|
||||
src/game/physics/map_simulation.cpp
|
||||
src/game/physics/map_simulation.hpp
|
||||
src/game/physics/map_player.hpp
|
||||
src/game/physics/map/map_simulation.cpp
|
||||
src/game/physics/map/map_simulation.hpp
|
||||
src/game/physics/map/map_player.hpp
|
||||
src/game/physics/map/map_player.cpp
|
||||
src/game/level/level_config.hpp
|
||||
src/game/level/level_loader.cpp
|
||||
src/game/level/level_loader.hpp
|
||||
src/levels.hpp)
|
||||
src/levels.hpp src/collectables.hpp
|
||||
src/game/collectables/collectable_config.hpp
|
||||
src/game/collectables/collection/collectables_collection.cpp
|
||||
src/game/collectables/collection/collectables_collection.hpp
|
||||
src/game/collectables/collection/collectables_depth_collection.cpp
|
||||
src/game/collectables/collection/collectables_depth_collection.hpp
|
||||
src/game/collectables/collectable_in_level.hpp
|
||||
src/game/collectables/collectable_factory.cpp
|
||||
src/game/collectables/collectable_factory.hpp
|
||||
src/game/player/player_collection.cpp
|
||||
src/game/player/player_collection.hpp
|
||||
src/levels.hpp
|
||||
src/sprites/tiling/tilemap.cpp
|
||||
src/sprites/tiling/tilemap.hpp
|
||||
src/sprites/tiling/tilemap_config.hpp
|
||||
src/sprites/tiling/tileset_config.hpp
|
||||
src/sprites/tiling/tileset.cpp
|
||||
src/sprites/tiling/tileset.hpp
|
||||
src/game/frame_counter.cpp
|
||||
src/game/frame_counter.hpp
|
||||
src/game/level/level_renderer.cpp
|
||||
src/game/level/level_renderer.hpp
|
||||
src/sprites/skymap/skymap.cpp
|
||||
src/sprites/skymap/skymap.hpp
|
||||
src/game/camera/multiplayer_view.cpp
|
||||
src/game/camera/multiplayer_view.hpp
|
||||
src/game/camera/multiplayer_borders.cpp
|
||||
src/game/camera/multiplayer_borders.hpp
|
||||
src/game/physics/holes/collectable_simulation.cpp
|
||||
src/game/physics/holes/collectable_simulation.hpp
|
||||
src/game/physics/holes/holes_simulation.cpp
|
||||
src/game/physics/holes/holes_simulation.hpp
|
||||
src/game/physics/holes/layouts/hole_description.hpp
|
||||
src/game/physics/holes/layouts/hole_layout.cpp
|
||||
src/game/physics/holes/layouts/hole_layout.hpp
|
||||
src/game/physics/holes/layouts/depth_hole_layout.hpp
|
||||
src/game/physics/holes/layouts/depth_hole_description.hpp
|
||||
src/game/physics/holes/ground/collectable_sim_ground_segment.hpp
|
||||
src/game/physics/holes/ground/collectable_sim_ground.cpp
|
||||
src/game/physics/holes/ground/collectable_sim_ground.hpp
|
||||
src/game/physics/body_adapter.cpp
|
||||
src/game/physics/body_adapter.hpp
|
||||
src/sprites/masked_sprite.cpp
|
||||
src/sprites/masked_sprite.hpp
|
||||
src/sprites/configs/masked_sprite_config.hpp
|
||||
src/sprites/masked_sprite_hole.hpp
|
||||
src/game/time/countdown.cpp
|
||||
src/game/time/countdown.hpp
|
||||
src/game/layer/global_layer.cpp
|
||||
src/game/layer/global_layer.hpp
|
||||
src/typography/font_manager.cpp
|
||||
src/typography/font_manager.hpp
|
||||
src/screens/winner_screen.cpp
|
||||
src/screens/winner_screen.hpp src/screens/screen.cpp src/screens/screen.hpp src/screens/join_screen.cpp src/screens/join_screen.hpp src/game/level/level_generator.cpp src/game/level/level_generator.hpp src/screens/loading_screen.cpp src/screens/loading_screen.hpp)
|
||||
|
||||
set(PHYSICS_00_SOURCES
|
||||
src/prototypes/physics_00.cpp)
|
||||
|
@ -96,12 +146,27 @@ set(PHYSICS_00_SOURCES
|
|||
set(MATH_00_SOURCES
|
||||
src/prototypes/math_00.cpp)
|
||||
|
||||
set(COORDINATES_00_SOURCES
|
||||
src/prototypes/coordinates_00.cpp
|
||||
src/coordinates/coordinates.h
|
||||
src/coordinates/coordinate_transformer.h
|
||||
src/coordinates/coordinate_transformer.cpp
|
||||
src/coordinates/translated_coordinates.h
|
||||
src/coordinates/translated_coordinates.cpp)
|
||||
|
||||
set(MINIMAP_00_SOURCES
|
||||
src/prototypes/minimap_00.cpp)
|
||||
|
||||
# Add an executable target
|
||||
add_executable(Holesome ${SOURCES})
|
||||
add_executable(Physics_00 ${PHYSICS_00_SOURCES})
|
||||
|
||||
add_executable(Math_00 ${MATH_00_SOURCES})
|
||||
|
||||
add_executable(Coordinates_00 ${COORDINATES_00_SOURCES})
|
||||
|
||||
add_executable(Minimap_00 ${MINIMAP_00_SOURCES})
|
||||
|
||||
# Link SFML and other libraries to your executable target
|
||||
target_link_libraries(Holesome sfml-graphics sfml-audio)
|
||||
target_link_libraries(Holesome Eigen3::Eigen)
|
||||
|
@ -111,6 +176,10 @@ 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 holes! But not the kind you have seen before ...
|
||||
Holesome is about currentLayoutHoles! 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 holes in a fun way
|
||||
- **Physics**: Core gameplay element to make the objects fall into the currentLayoutHoles 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 holes could be controlled by AI, which makes singleplayer games more exciting
|
||||
- **AI**: Some currentLayoutHoles could be controlled by AI, which makes singleplayer games more exciting
|
||||
- **Online Multiplayer**: Play with friends online
|
||||
|
||||
## Project Setup
|
||||
|
@ -68,3 +68,10 @@ 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,3 +1,28 @@
|
|||
# Holesome - ToDos
|
||||
|
||||
- [ ] Try button mapping for other controllers
|
||||
## 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
|
||||
|
|
BIN
assets/collectables/bike.png
Normal file
After Width: | Height: | Size: 849 B |
BIN
assets/collectables/lantern.png
Normal file
After Width: | Height: | Size: 630 B |
BIN
assets/collectables/rose.png
Normal file
After Width: | Height: | Size: 517 B |
BIN
assets/collectables/rosebush.png
Normal file
After Width: | Height: | Size: 578 B |
BIN
assets/collectables/small-tree.png
Normal file
After Width: | Height: | Size: 835 B |
BIN
assets/collectables/stone.png
Normal file
After Width: | Height: | Size: 617 B |
BIN
assets/collectables/tram.png
Normal file
After Width: | Height: | Size: 967 B |
BIN
assets/edge.png
Before Width: | Height: | Size: 19 KiB |
BIN
assets/fonts/pixel.ttf
Normal file
BIN
assets/grass-tiles.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
assets/grass_plus.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
assets/hole.png
Normal file
After Width: | Height: | Size: 313 KiB |
BIN
assets/isometric-tiles.png
Normal file
After Width: | Height: | Size: 4 KiB |
BIN
assets/player/player-0.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
assets/player/player-1.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/player/player-2.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
assets/player/player-3.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
assets/player/player.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
18
src/collectables.hpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
#ifndef HOLESOME_COLLECTABLES_HPP
|
||||
#define HOLESOME_COLLECTABLES_HPP
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include "game/collectables/collectable_config.hpp"
|
||||
|
||||
std::map<std::string, CollectableConfig> const all_collectables = {
|
||||
{"rose", CollectableConfig("rose", 1)},
|
||||
{"rosebush", CollectableConfig("rosebush", 3)},
|
||||
{"stone", CollectableConfig("stone", 5)},
|
||||
{"bike", CollectableConfig("bike", 15)},
|
||||
{"small-tree", CollectableConfig("small-tree", 20)},
|
||||
{"tram", CollectableConfig("tram", 50)},
|
||||
{"lantern", CollectableConfig("lantern", 10)}
|
||||
};
|
||||
|
||||
#endif //HOLESOME_COLLECTABLES_HPP
|
63
src/config.h
|
@ -2,42 +2,91 @@
|
|||
#define HOLESOME_CONFIG_H
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <box2d/box2d.h>
|
||||
|
||||
#define DEVELOPER_MODE true
|
||||
#define DEVELOPER_MODE false
|
||||
|
||||
#define GAME_NAME "Holesome"
|
||||
#define CLOSE_GAME_BTN sf::Keyboard::Escape
|
||||
|
||||
// Player
|
||||
#define DEFAULT_PLAYER_SPEED 5.f // World units per second
|
||||
#define DEFAULT_PLAYER_RADIUS .5f // In World units
|
||||
#define DEFAULT_PLAYER_POINTS 0
|
||||
#define PLAYER_MIN_RADIUS 0.5f // World units
|
||||
#define PLAYER_RADIUS_PER_LEVEL 0.25f
|
||||
#define DEFAULT_MAX_PLAYER_NUMBER 4
|
||||
#define MIN_PLAYER_COUNT 2
|
||||
#define POINT_DELTA_FOR_ATTACK 20
|
||||
#define ATTACK_PER_SECOND 1.f
|
||||
#define PLAYER_ALIVE_THRESHOLD (-10)
|
||||
#define PLAYER_RUN_SPEED (DEFAULT_PLAYER_SPEED * 2.f)
|
||||
#define PLAYER_RUN_SPEED_COST_PER_SECOND 10.f
|
||||
|
||||
// World
|
||||
#define WORLD_GRAVITY b2Vec2(0.f, -9.8f)
|
||||
#define SKY_HEIGHT_SCALE 5.f
|
||||
#define CONSIDER_COLLECTABLE_DEPTH_MOVEMENT false // Might cost performance and is currently not in use
|
||||
|
||||
// FPS
|
||||
#define FRAME_RATE 60
|
||||
#define FRAME_TIME sf::Time(sf::seconds(1.0f / FRAME_RATE))
|
||||
#define FRAME_LIMIT_ENABLED true
|
||||
|
||||
// Window settings
|
||||
#define ANTIALIASINGLEVEL 8
|
||||
#define KEY_REPEAT_ENABLED false
|
||||
#define REFERENCE_SIZE sf::Vector2f(1920, 1080)
|
||||
|
||||
// Graphic settings
|
||||
#define ISOMETRIC_SKEW 0.3f
|
||||
#define ISOMETRIC_SKEW (16.f/32.f)
|
||||
#define MOVEMENT_SKEW sf::Vector2f(1.f, 1/ISOMETRIC_SKEW/2.f)
|
||||
#define WORLD_TO_ISO_SCALE 50.0f // 50.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(300, 300)
|
||||
#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_VIEW_SIZE_PADDING sf::Vector2f(0.5f, 0.5f)
|
||||
#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
|
||||
|
||||
// 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
|
||||
|
||||
// Directions
|
||||
#define DIRECTION_HARD_ACTIVATION_THRESHOLD 0.1f
|
||||
|
||||
// DEBUG
|
||||
#define DB_ISOPLANE_CORNER_RADIUS 2
|
||||
#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,11 +1,23 @@
|
|||
#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>() <<
|
||||
1, -1, 0,
|
||||
-ISOMETRIC_SKEW, -ISOMETRIC_SKEW, -1,
|
||||
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
|
||||
).finished();
|
||||
|
||||
|
@ -14,21 +26,66 @@ IsometricCoordinates CoordinateTransformer::worldToIsometric(WorldCoordinates wo
|
|||
Eigen::Vector3f worldCoordinatesVector;
|
||||
worldCoordinatesVector << worldCoordinates.x, worldCoordinates.y, worldCoordinates.z;
|
||||
|
||||
Eigen::Vector3f isoCoordinatesVector = worldToIsometricMatrix * worldCoordinatesVector * WORLD_TO_ISO_SCALE;
|
||||
Eigen::Vector3f isoCoordinatesVector = worldToIsometricMatrix * worldCoordinatesVector;
|
||||
|
||||
return IsometricCoordinates(
|
||||
return {
|
||||
isoCoordinatesVector.x(), // x
|
||||
isoCoordinatesVector.y(), // y
|
||||
isoCoordinatesVector.z() // depth
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
WorldCoordinates CoordinateTransformer::isometricToWorld(IsometricCoordinates isometricCoordinates)
|
||||
{
|
||||
Eigen::Vector3f isoCoordinatesVector;
|
||||
isoCoordinatesVector << isometricCoordinates.x, isometricCoordinates.y, isometricCoordinates.depth;
|
||||
Eigen::Vector3f isoCoordinatesVector;
|
||||
isoCoordinatesVector << isometricCoordinates.x,
|
||||
isometricCoordinates.y,
|
||||
isometricCoordinates.depth;
|
||||
|
||||
Eigen::Vector3f worldCoordinatesVector = worldToIsometricMatrix.inverse() * isoCoordinatesVector / WORLD_TO_ISO_SCALE;
|
||||
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;
|
||||
|
||||
return {
|
||||
worldCoordinatesVector.x(), // x
|
||||
|
|
|
@ -8,10 +8,15 @@ 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,6 +2,9 @@
|
|||
#define HOLESOME_COORDINATES_H
|
||||
|
||||
#include <SFML/System/Vector2.hpp>
|
||||
#include "../utilities/vector_utils.hpp"
|
||||
|
||||
class DiagonalWorldCoordinates;
|
||||
|
||||
struct WorldCoordinates
|
||||
{
|
||||
|
@ -10,7 +13,7 @@ struct WorldCoordinates
|
|||
WorldCoordinates(float x, float y, float z = 0) : x(x), y(y), z(z)
|
||||
{}
|
||||
|
||||
WorldCoordinates(sf::Vector2f vector) : x(vector.x), y(vector.y), z(0)
|
||||
explicit WorldCoordinates(sf::Vector2f vector) : x(vector.x), y(vector.y), z(0)
|
||||
{}
|
||||
|
||||
float x;
|
||||
|
@ -54,6 +57,11 @@ struct WorldCoordinates
|
|||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
[[nodiscard]] sf::Vector2f toGroundCoordinates() const
|
||||
{
|
||||
return {x, y};
|
||||
}
|
||||
};
|
||||
|
||||
struct IsometricCoordinates
|
||||
|
@ -62,7 +70,7 @@ struct IsometricCoordinates
|
|||
float y;
|
||||
float depth; // Bigger means further back. Can be used for accurate rendering order.
|
||||
|
||||
sf::Vector2f toScreen() const
|
||||
[[nodiscard]] sf::Vector2f toScreen() const
|
||||
{
|
||||
return {x, y};
|
||||
}
|
||||
|
@ -72,7 +80,20 @@ 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)
|
||||
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)
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -80,6 +101,14 @@ struct GridCoordinates
|
|||
{
|
||||
float x;
|
||||
float y;
|
||||
|
||||
GridCoordinates() = default;
|
||||
|
||||
GridCoordinates(float x, float y) : x(x), y(y)
|
||||
{}
|
||||
|
||||
GridCoordinates(int x, int y) : x(static_cast<float>(x)), y(static_cast<float>(y))
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
#include <utility>
|
||||
|
||||
WorldCoordinates TranslatedCoordinates::world() const {
|
||||
WorldCoordinates TranslatedCoordinates::world() const
|
||||
{
|
||||
auto coordinates = worldCoordinates;
|
||||
if (parent != nullptr)
|
||||
{
|
||||
|
@ -12,40 +13,52 @@ WorldCoordinates TranslatedCoordinates::world() const {
|
|||
return coordinates;
|
||||
}
|
||||
|
||||
IsometricCoordinates TranslatedCoordinates::isometric() const {
|
||||
return coordTransformer->worldToIsometric(world());
|
||||
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 * worldToGridFactor, referenceWordCoordinates.y * worldToGridFactor};
|
||||
return {referenceWordCoordinates.x - 0.5f, referenceWordCoordinates.y - 0.5f};
|
||||
}
|
||||
|
||||
void TranslatedCoordinates::set(WorldCoordinates newWorldCoordinates) {
|
||||
void TranslatedCoordinates::setWorld(WorldCoordinates newWorldCoordinates)
|
||||
{
|
||||
this->worldCoordinates = newWorldCoordinates;
|
||||
}
|
||||
|
||||
void TranslatedCoordinates::set(IsometricCoordinates newIsometricCoordinates) {
|
||||
this->worldCoordinates = coordTransformer->isometricToWorld(newIsometricCoordinates);
|
||||
void TranslatedCoordinates::setIsometric(IsometricCoordinates newIsometricCoordinates)
|
||||
{
|
||||
this->worldCoordinates = CoordinateTransformer::isometricToWorld(newIsometricCoordinates);
|
||||
}
|
||||
|
||||
void TranslatedCoordinates::set(const TranslatedCoordinates& newCoordinates) {
|
||||
void TranslatedCoordinates::setTranslated(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({deltaWorldCoordinates.x, deltaWorldCoordinates.y, 0});
|
||||
move(WorldCoordinates {deltaWorldCoordinates.x, deltaWorldCoordinates.y, 0});
|
||||
}
|
||||
|
||||
void TranslatedCoordinates::setParent(std::shared_ptr<TranslatedCoordinates> parent, WorldCoordinates offset)
|
||||
|
@ -54,7 +67,50 @@ void TranslatedCoordinates::setParent(std::shared_ptr<TranslatedCoordinates> par
|
|||
this->worldCoordinates = offset;
|
||||
}
|
||||
|
||||
void TranslatedCoordinates::set(GridCoordinates newGridCoordinates)
|
||||
void TranslatedCoordinates::setGrid(GridCoordinates newGridCoordinates)
|
||||
{
|
||||
this->worldCoordinates = {newGridCoordinates.x / worldToGridFactor, newGridCoordinates.y / worldToGridFactor, 0};
|
||||
this->worldCoordinates = {newGridCoordinates.x + 0.5f, newGridCoordinates.y + 0.5f, 0};
|
||||
}
|
||||
|
||||
void TranslatedCoordinates::setWorldOffset(WorldCoordinates offset)
|
||||
{
|
||||
this->worldCoordinates = offset;
|
||||
}
|
||||
|
||||
void TranslatedCoordinates::setScreenOffset(IsometricCoordinates offset)
|
||||
{
|
||||
setWorldOffset(CoordinateTransformer::isometricToWorld(offset));
|
||||
}
|
||||
|
||||
TranslatedCoordinates::TranslatedCoordinates(GridCoordinates gridCoordinates)
|
||||
{
|
||||
setGrid(gridCoordinates);
|
||||
}
|
||||
|
||||
DiagonalWorldCoordinates TranslatedCoordinates::diagonalWorld() const
|
||||
{
|
||||
return CoordinateTransformer::worldToDiagonal(world());
|
||||
}
|
||||
|
||||
void TranslatedCoordinates::setDiagonal(DiagonalWorldCoordinates newDiagonalWorldCoordinates)
|
||||
{
|
||||
this->worldCoordinates = CoordinateTransformer::diagonalToWorld(newDiagonalWorldCoordinates);
|
||||
}
|
||||
|
||||
TranslatedCoordinates::TranslatedCoordinates(DiagonalWorldCoordinates diagonalWorldCoordinates)
|
||||
{
|
||||
setDiagonal(diagonalWorldCoordinates);
|
||||
}
|
||||
|
||||
void TranslatedCoordinates::removeParent()
|
||||
{
|
||||
// Uncomment, if global position should be preserved
|
||||
// auto globalWorldCoordinates = this->world();
|
||||
// this->worldCoordinates = globalWorldCoordinates;
|
||||
this->parent = nullptr;
|
||||
}
|
||||
|
||||
TranslatedCoordinates::TranslatedCoordinates(IsometricCoordinates isometricCoordinates)
|
||||
{
|
||||
setIsometric(isometricCoordinates);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
//
|
||||
// Created by max on 27.04.23.
|
||||
//
|
||||
|
||||
#ifndef HOLESOME_TRANSLATED_COORDINATES_H
|
||||
#define HOLESOME_TRANSLATED_COORDINATES_H
|
||||
|
||||
|
@ -9,26 +5,31 @@
|
|||
#include "coordinates.h"
|
||||
#include "coordinate_transformer.h"
|
||||
|
||||
#define INITIAL_WORLD_TO_GRID_FACTOR 0.25f
|
||||
|
||||
class TranslatedCoordinates
|
||||
{
|
||||
public:
|
||||
explicit TranslatedCoordinates(WorldCoordinates worldCoordinates);
|
||||
explicit TranslatedCoordinates(DiagonalWorldCoordinates diagonalWorldCoordinates);
|
||||
explicit TranslatedCoordinates(IsometricCoordinates isometricCoordinates);
|
||||
explicit TranslatedCoordinates(GridCoordinates gridCoordinates);
|
||||
|
||||
WorldCoordinates world() const;
|
||||
[[nodiscard]] WorldCoordinates world() const;
|
||||
|
||||
IsometricCoordinates isometric() const;
|
||||
[[nodiscard]] IsometricCoordinates isometric() const;
|
||||
|
||||
GridCoordinates grid() const;
|
||||
[[nodiscard]] GridCoordinates grid() const;
|
||||
|
||||
void set(WorldCoordinates newWorldCoordinates);
|
||||
[[nodiscard]] DiagonalWorldCoordinates diagonalWorld() const;
|
||||
|
||||
void set(const TranslatedCoordinates& newCoordinates);
|
||||
void setWorld(WorldCoordinates newWorldCoordinates);
|
||||
|
||||
void set(IsometricCoordinates newIsometricCoordinates);
|
||||
void setTranslated(const TranslatedCoordinates& newCoordinates);
|
||||
|
||||
void set(GridCoordinates newGridCoordinates);
|
||||
void setIsometric(IsometricCoordinates newIsometricCoordinates);
|
||||
|
||||
void setDiagonal(DiagonalWorldCoordinates newDiagonalWorldCoordinates);
|
||||
|
||||
void setGrid(GridCoordinates newGridCoordinates);
|
||||
|
||||
void move(WorldCoordinates deltaWorldCoordinates);
|
||||
|
||||
|
@ -36,12 +37,19 @@ 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;
|
||||
const float worldToGridFactor = INITIAL_WORLD_TO_GRID_FACTOR;
|
||||
const std::shared_ptr<CoordinateTransformer> coordTransformer = std::make_shared<CoordinateTransformer>();
|
||||
WorldCoordinates worldCoordinates{};
|
||||
|
||||
std::shared_ptr<TranslatedCoordinates> parent = nullptr;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ GridDebugLayer::GridDebugLayer(int minX, int maxX, int minY, int maxY)
|
|||
color = sf::Color::Blue;
|
||||
}
|
||||
|
||||
auto gameObject = std::make_shared<CircleObject>(DB_ISOPLANE_CORNER_RADIUS, color);
|
||||
auto gameObject = std::make_shared<CircleObject>(DB_CIRCLE_RADIUS, color);
|
||||
addChildWorldOffset(gameObject, WorldCoordinates(x, y));
|
||||
}
|
||||
}
|
||||
|
|
65
src/game/camera/multiplayer_borders.cpp
Normal file
|
@ -0,0 +1,65 @@
|
|||
#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;
|
||||
}
|
26
src/game/camera/multiplayer_borders.hpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
#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
|
150
src/game/camera/multiplayer_view.cpp
Normal file
|
@ -0,0 +1,150 @@
|
|||
#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();
|
||||
}
|
40
src/game/camera/multiplayer_view.hpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
#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
|
|
@ -1,13 +1,13 @@
|
|||
#include "tracking_view.h"
|
||||
#include "../../utilities/vector_utils.hpp"
|
||||
#include "../player/player_collection.hpp"
|
||||
|
||||
TrackingView::TrackingView(TrackingViewOptions options) : options(options),
|
||||
view(nullptr),
|
||||
hasViewChanged(false)
|
||||
view(new sf::View(options.initialCenter,
|
||||
options.minViewSize)),
|
||||
trackables({})
|
||||
{
|
||||
trackables = std::vector<ITrackable *>();
|
||||
marker = new CircleObject(2, sf::Color::Yellow);
|
||||
Game::getInstance()->registerView(this);
|
||||
marker = new CircleObject(DB_CIRCLE_RADIUS, sf::Color::Yellow);
|
||||
}
|
||||
|
||||
TrackingView::~TrackingView()
|
||||
|
@ -18,33 +18,13 @@ TrackingView::~TrackingView()
|
|||
|
||||
void TrackingView::lateUpdate()
|
||||
{
|
||||
// Initialize if necessary
|
||||
if (view == nullptr)
|
||||
{
|
||||
initializeView();
|
||||
}
|
||||
|
||||
processTrackableStates();
|
||||
|
||||
if (!trackables.empty())
|
||||
{
|
||||
followTrackables();
|
||||
}
|
||||
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)
|
||||
|
@ -65,13 +45,13 @@ void TrackingView::setSize(sf::Vector2f newSize)
|
|||
didAspectRationChange = true;
|
||||
}
|
||||
|
||||
if (options.softResizeSpeed != 0 && !didAspectRationChange) {
|
||||
if (options.softResizeSpeed != 0 && !didAspectRationChange)
|
||||
{
|
||||
// Smooth out transition to new size
|
||||
newSize = size + (newSize - size) * options.softResizeSpeed * FRAME_TIME.asSeconds();
|
||||
}
|
||||
|
||||
view->setSize(newSize);
|
||||
hasViewChanged = true;
|
||||
}
|
||||
|
||||
sf::Vector2f TrackingView::getSize() const
|
||||
|
@ -79,10 +59,11 @@ sf::Vector2f TrackingView::getSize() const
|
|||
return view->getSize();
|
||||
}
|
||||
|
||||
sf::Vector2f TrackingView::getWindowSize() const
|
||||
sf::Vector2f TrackingView::getSizeInWindow() const
|
||||
{
|
||||
auto size = Game::getInstance()->window->getSize();
|
||||
return {static_cast<float>(size.x), static_cast<float>(size.y)};
|
||||
auto viewPort = view->getViewport();
|
||||
return {static_cast<float>(size.x * viewPort.width), static_cast<float>(size.y * viewPort.height)};
|
||||
}
|
||||
|
||||
sf::Vector2f TrackingView::getCenter() const
|
||||
|
@ -93,9 +74,9 @@ sf::Vector2f TrackingView::getCenter() const
|
|||
void TrackingView::followTrackables()
|
||||
{
|
||||
auto trackingPoint = getTrackingArea().getCenter();
|
||||
if (DEVELOPER_MODE)
|
||||
if (DB_TRACKING_VIEW_CENTER)
|
||||
{
|
||||
marker->coordinates->set(IsometricCoordinates(trackingPoint));
|
||||
marker->coordinates->setIsometric(IsometricCoordinates(trackingPoint));
|
||||
}
|
||||
|
||||
// Calculate distance to target to check how to handle it
|
||||
|
@ -103,7 +84,7 @@ void TrackingView::followTrackables()
|
|||
auto vectorToTarget = trackingPoint - currentCenter;
|
||||
auto distanceToTarget = length(vectorToTarget);
|
||||
|
||||
if (distanceToTarget <= getRadius(options.freeMoveThreshold))
|
||||
if (distanceToTarget <= getFreeMovementRadius())
|
||||
{
|
||||
// Nothing to do
|
||||
return;
|
||||
|
@ -115,7 +96,7 @@ void TrackingView::followTrackables()
|
|||
auto deltaToDesiredView = normalize(vectorToTarget);
|
||||
|
||||
// Reduce delta to edge of free-move area to make it less jaring
|
||||
deltaToDesiredView *= distanceToTarget - getRadius(options.freeMoveThreshold);
|
||||
deltaToDesiredView *= distanceToTarget - getFreeMovementRadius();
|
||||
|
||||
moveCenter(deltaToDesiredView);
|
||||
}
|
||||
|
@ -129,12 +110,11 @@ void TrackingView::moveCenter(sf::Vector2<float> delta)
|
|||
}
|
||||
|
||||
view->move(delta);
|
||||
hasViewChanged = true;
|
||||
}
|
||||
|
||||
void TrackingView::draw(sf::RenderWindow *window)
|
||||
{
|
||||
if (!DEVELOPER_MODE)
|
||||
if (!DB_TRACKING_VIEW_CENTER)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -142,15 +122,21 @@ void TrackingView::draw(sf::RenderWindow *window)
|
|||
marker->draw(window);
|
||||
}
|
||||
|
||||
void TrackingView::addTrackable(ITrackable *trackable)
|
||||
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()
|
||||
{
|
||||
// Remove trackables that have ended tracking
|
||||
std::remove_if(trackables.begin(), trackables.end(), [](ITrackable *trackable)
|
||||
std::remove_if(trackables.begin(), trackables.end(), [](std::shared_ptr<ITrackable> trackable)
|
||||
{
|
||||
return trackable->getTrackableState() == TrackableState::END_TRACKING;
|
||||
});
|
||||
|
@ -158,6 +144,11 @@ void TrackingView::processTrackableStates()
|
|||
|
||||
TrackingArea TrackingView::getTrackingArea() const
|
||||
{
|
||||
if (trackables.empty())
|
||||
{
|
||||
return TrackingArea();
|
||||
}
|
||||
|
||||
auto initialTrackable = trackables[0];
|
||||
TrackingArea area = {
|
||||
initialTrackable->getTrackablePosition() - initialTrackable->getTrackableSize() / 2.f,
|
||||
|
@ -203,7 +194,7 @@ void TrackingView::adjustSizeToTrackables()
|
|||
newViewSize = restrainToBounds(newViewSize);
|
||||
|
||||
// Extend view to match aspect ratio
|
||||
auto windowSize = getWindowSize();
|
||||
auto windowSize = getSizeInWindow();
|
||||
auto aspectRatio = windowSize.x / windowSize.y;
|
||||
if (newViewSize.x / newViewSize.y > aspectRatio)
|
||||
{
|
||||
|
@ -242,32 +233,49 @@ sf::Vector2f TrackingView::restrainToBounds(sf::Vector2f viewSize) const
|
|||
sf::Vector2f TrackingView::applyPadding(sf::Vector2f viewSize) const
|
||||
{
|
||||
auto padding = options.viewSizePadding;
|
||||
if (padding.x <= 1)
|
||||
if (!options.isAbsoluteViewSizePadding)
|
||||
{
|
||||
padding.x *= viewSize.x;
|
||||
}
|
||||
if (padding.y <= 1)
|
||||
{
|
||||
padding.y *= viewSize.y;
|
||||
}
|
||||
return viewSize + padding;
|
||||
}
|
||||
|
||||
float TrackingView::getRadius(float threshold) const
|
||||
float TrackingView::getFreeMovementRadius() const
|
||||
{
|
||||
if (threshold <= 0)
|
||||
if (options.freeMoveThreshold <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (threshold > 1)
|
||||
if (options.isAbsoluteFreeMoveThreshold)
|
||||
{
|
||||
return threshold;
|
||||
return options.freeMoveThreshold;
|
||||
}
|
||||
|
||||
auto windowSize = getWindowSize();
|
||||
auto windowSize = getSizeInWindow();
|
||||
float smallestSide = std::min(windowSize.x, windowSize.y);
|
||||
|
||||
// Only half of the side, since we are calculating radius
|
||||
return smallestSide / 2.f * threshold;
|
||||
return smallestSide / 2.f * options.freeMoveThreshold;
|
||||
}
|
||||
|
||||
void TrackingView::removeTrackable(const std::shared_ptr<ITrackable> &trackable)
|
||||
{
|
||||
trackables.erase(
|
||||
std::remove_if(trackables.begin(), trackables.end(), [&trackable](const std::shared_ptr<ITrackable> &t)
|
||||
{
|
||||
return t == trackable;
|
||||
}), trackables.end());
|
||||
}
|
||||
|
||||
void TrackingView::setViewport(sf::Vector2f center, sf::Vector2f size)
|
||||
{
|
||||
// TODO: Resize content for viewport
|
||||
view->setViewport(sf::FloatRect(center.x, center.y, size.x, size.y));
|
||||
}
|
||||
|
||||
void TrackingView::setViewForWindow()
|
||||
{
|
||||
Game::getInstance()->window->setView(*this->view);
|
||||
}
|
||||
|
|
|
@ -17,12 +17,17 @@ public:
|
|||
|
||||
~TrackingView();
|
||||
|
||||
|
||||
void draw(sf::RenderWindow *window) override;
|
||||
|
||||
void lateUpdate() override;
|
||||
|
||||
void addTrackable(ITrackable *trackable);
|
||||
void addTrackable(const std::shared_ptr<ITrackable>& trackable);
|
||||
|
||||
void removeTrackable(const std::shared_ptr<ITrackable> &trackable);
|
||||
|
||||
void setViewport(sf::Vector2f center, sf::Vector2f size);
|
||||
|
||||
void setViewForWindow();
|
||||
|
||||
sf::Vector2f getSize() const;
|
||||
|
||||
|
@ -30,14 +35,11 @@ public:
|
|||
|
||||
private:
|
||||
sf::View *view{};
|
||||
bool hasViewChanged{};
|
||||
TrackingViewOptions options;
|
||||
std::vector<ITrackable *> trackables;
|
||||
std::vector<std::shared_ptr<ITrackable>> trackables;
|
||||
|
||||
CircleObject *marker;
|
||||
|
||||
void initializeView();
|
||||
|
||||
void followTrackables();
|
||||
|
||||
void moveCenter(sf::Vector2<float> delta);
|
||||
|
@ -54,9 +56,9 @@ private:
|
|||
|
||||
sf::Vector2f restrainToBounds(sf::Vector2f viewSize) const;
|
||||
|
||||
float getRadius(float threshold) const;
|
||||
float getFreeMovementRadius() const;
|
||||
|
||||
sf::Vector2f getWindowSize() const;
|
||||
sf::Vector2f getSizeInWindow() const;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -7,10 +7,9 @@
|
|||
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.
|
||||
|
@ -31,12 +30,14 @@ 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.
|
||||
* Value >1 to set pixel padding.
|
||||
* Value between 0 and 1 to set relative padding.
|
||||
* If isAbsoluteViewSizePadding is set to true, padding will be added to view size, is multiplied with tracking size and added.
|
||||
*/
|
||||
sf::Vector2f viewSizePadding = DEF_TV_VIEW_SIZE_PADDING;
|
||||
bool isAbsoluteViewSizePadding = DEF_TV_IS_ABSOLUTE_VIEW_SIZE_PADDING;
|
||||
};
|
||||
|
||||
#endif //HOLESOME_TRACKING_VIEW_OPTIONS_HPP
|
||||
|
|
58
src/game/collectables/collectable.cpp
Normal file
|
@ -0,0 +1,58 @@
|
|||
#include "collectable.hpp"
|
||||
#include "../../sprites/versatile_sprite.hpp"
|
||||
#include "../../config.h"
|
||||
#include "../input/input_mapper.h"
|
||||
#include "../physics/holes/holes_simulation.hpp"
|
||||
#include "collection/collectables_collection.hpp"
|
||||
#include "../player/player_collection.hpp"
|
||||
|
||||
Collectable::Collectable(int points)
|
||||
: points(points)
|
||||
{
|
||||
collectableId = collectableCount;
|
||||
collectableCount++;
|
||||
}
|
||||
|
||||
void Collectable::setRotation(float angle)
|
||||
{
|
||||
// Assume that the sprite is the first child
|
||||
auto sprite = std::dynamic_pointer_cast<VersatileSprite>(getChildren()[0]);
|
||||
sprite->setRotation(angle);
|
||||
}
|
||||
|
||||
float Collectable::getDepth() const
|
||||
{
|
||||
return coordinates->isometric().depth;
|
||||
}
|
||||
|
||||
void Collectable::setSprite(const std::string &spriteName)
|
||||
{
|
||||
// Create versatile sprite
|
||||
auto spriteObject = std::make_shared<VersatileSprite>(spriteName);
|
||||
size = spriteObject->getSize() * COLLECTABLE_SCALE;
|
||||
spriteObject->setSize(size);
|
||||
addChildScreenOffset(spriteObject, IsometricCoordinates(-size / 2.f));
|
||||
|
||||
sprite = spriteObject;
|
||||
|
||||
// Set half size offset of coordinates
|
||||
coordinates->move(IsometricCoordinates(0, -size.x / 2.f, 0));
|
||||
}
|
||||
|
||||
sf::Vector2f Collectable::getSize() const
|
||||
{
|
||||
return size / WORLD_TO_ISO_SCALE;
|
||||
}
|
||||
|
||||
void Collectable::preRenderUpdate()
|
||||
{
|
||||
if (!sprite->isVisible())
|
||||
{
|
||||
auto closestPlayer = PlayerCollection::getInstance()->getClosestPlayer(*coordinates);
|
||||
closestPlayer->consume(points);
|
||||
setActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject::preRenderUpdate();
|
||||
}
|
41
src/game/collectables/collectable.hpp
Normal file
|
@ -0,0 +1,41 @@
|
|||
#ifndef HOLESOME_COLLECTABLE_HPP
|
||||
#define HOLESOME_COLLECTABLE_HPP
|
||||
|
||||
|
||||
#include "../game_object.h"
|
||||
#include "../player/player.hpp"
|
||||
#include "../../sprites/masked_sprite.hpp"
|
||||
|
||||
class Collectable : public GameObject
|
||||
{
|
||||
public:
|
||||
Collectable(int points);
|
||||
|
||||
void setSprite(const std::string &spriteName);
|
||||
|
||||
void setRotation(float angle);
|
||||
|
||||
sf::Vector2f getSize() const;
|
||||
|
||||
float getDepth() const;
|
||||
|
||||
int getId() const
|
||||
{
|
||||
return collectableId;
|
||||
}
|
||||
|
||||
void preRenderUpdate() override;
|
||||
|
||||
private:
|
||||
int collectableId = 0;
|
||||
static inline int collectableCount = 0;
|
||||
std::shared_ptr<Sprite> sprite;
|
||||
|
||||
sf::Vector2f size;
|
||||
int points = 0;
|
||||
|
||||
std::shared_ptr<Player> consumedBy = nullptr;
|
||||
};
|
||||
|
||||
|
||||
#endif //HOLESOME_COLLECTABLE_HPP
|
23
src/game/collectables/collectable_config.hpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
#ifndef HOLESOME_COLLECTABLE_CONFIG_HPP
|
||||
#define HOLESOME_COLLECTABLE_CONFIG_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
struct CollectableConfig
|
||||
{
|
||||
std::string spriteName;
|
||||
int points = 0;
|
||||
|
||||
explicit CollectableConfig(std::string spriteName, int points)
|
||||
: spriteName(std::move(spriteName)), points(points)
|
||||
{}
|
||||
|
||||
CollectableConfig() = default;
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return !spriteName.empty();
|
||||
}
|
||||
};
|
||||
|
||||
#endif //HOLESOME_COLLECTABLE_CONFIG_HPP
|
12
src/game/collectables/collectable_factory.cpp
Normal file
|
@ -0,0 +1,12 @@
|
|||
#include "collectable_factory.hpp"
|
||||
|
||||
std::shared_ptr<Collectable> CollectableFactory::createFromInLevelConfig(const CollectableInLevel &config)
|
||||
{
|
||||
auto collectableConfig = config.collectableConfig;
|
||||
auto collectable = std::make_shared<Collectable>(config.collectableConfig.points);
|
||||
|
||||
collectable->coordinates->setGrid(config.position);
|
||||
collectable->setSprite(collectableConfig.spriteName);
|
||||
|
||||
return collectable;
|
||||
}
|
15
src/game/collectables/collectable_factory.hpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
#ifndef HOLESOME_COLLECTABLE_FACTORY_HPP
|
||||
#define HOLESOME_COLLECTABLE_FACTORY_HPP
|
||||
|
||||
|
||||
#include "collectable_in_level.hpp"
|
||||
#include "collectable.hpp"
|
||||
|
||||
class CollectableFactory
|
||||
{
|
||||
public:
|
||||
static std::shared_ptr<Collectable> createFromInLevelConfig(const CollectableInLevel &config);
|
||||
};
|
||||
|
||||
|
||||
#endif //HOLESOME_COLLECTABLE_FACTORY_HPP
|
35
src/game/collectables/collectable_in_level.hpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
#ifndef HOLESOME_COLLECTABLE_IN_LEVEL_HPP
|
||||
#define HOLESOME_COLLECTABLE_IN_LEVEL_HPP
|
||||
|
||||
#include "../../coordinates/coordinates.h"
|
||||
#include "collectable_config.hpp"
|
||||
#include "../../logging/easylogging++.h"
|
||||
#include "../../collectables.hpp"
|
||||
|
||||
struct CollectableInLevel
|
||||
{
|
||||
std::string name;
|
||||
GridCoordinates position;
|
||||
CollectableConfig collectableConfig;
|
||||
|
||||
CollectableInLevel(const std::string &name, GridCoordinates position)
|
||||
: name(name),
|
||||
position(position)
|
||||
{
|
||||
if (all_collectables.find(name) == all_collectables.end())
|
||||
{
|
||||
LOG(ERROR) << "Collectable config " << name << " not found. Skipping collectable.";
|
||||
this->name = ""; // Make this invalid
|
||||
return;
|
||||
}
|
||||
|
||||
this->collectableConfig = all_collectables.at(name);
|
||||
}
|
||||
|
||||
bool isValid() const
|
||||
{
|
||||
return !name.empty();
|
||||
}
|
||||
};
|
||||
|
||||
#endif //HOLESOME_COLLECTABLE_IN_LEVEL_HPP
|
132
src/game/collectables/collection/collectables_collection.cpp
Normal file
|
@ -0,0 +1,132 @@
|
|||
#include "collectables_collection.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include "collectables_depth_collection.hpp"
|
||||
#include "../../../logging/easylogging++.h"
|
||||
#include "../../../config.h"
|
||||
#include "../../physics/holes/holes_simulation.hpp"
|
||||
|
||||
std::shared_ptr<CollectablesCollection> CollectablesCollection::getInstance()
|
||||
{
|
||||
if (singletonInstance == nullptr)
|
||||
{
|
||||
singletonInstance = std::make_shared<CollectablesCollection>();
|
||||
}
|
||||
return singletonInstance;
|
||||
}
|
||||
|
||||
void CollectablesCollection::createEmpty(int maxDepth)
|
||||
{
|
||||
// Remove previous collections
|
||||
depthCollections.clear();
|
||||
|
||||
LOG(INFO) << "Creating empty collectables collection with a max depth of " << maxDepth << " ...";
|
||||
// Create new collections
|
||||
for (int d = 0; d < maxDepth; d++)
|
||||
{
|
||||
auto depthCollection = std::make_shared<CollectablesDepthCollection>(d);
|
||||
depthCollections[d] = depthCollection;
|
||||
}
|
||||
}
|
||||
|
||||
void CollectablesCollection::remove(int collectableId)
|
||||
{
|
||||
HolesSimulation::getInstance()->removeCollectable(collectableId);
|
||||
|
||||
std::shared_ptr<Collectable> collectable = nullptr;
|
||||
for (auto &child: getChildren())
|
||||
{
|
||||
collectable = std::dynamic_pointer_cast<Collectable>(child);
|
||||
if (collectable->getId() == collectableId)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (collectable == nullptr)
|
||||
{
|
||||
LOG(ERROR) << "No collectables left to remove.";
|
||||
return;
|
||||
}
|
||||
|
||||
depthCollections[collectable->getDepth()]->remove(collectable);
|
||||
removeChild(collectable);
|
||||
}
|
||||
|
||||
void CollectablesCollection::add(const std::shared_ptr<Collectable> &collectable)
|
||||
{
|
||||
depthCollections[collectable->getDepth()]->add(collectable);
|
||||
addDetachedChild(collectable);
|
||||
}
|
||||
|
||||
void CollectablesCollection::update()
|
||||
{
|
||||
GameObject::update();
|
||||
|
||||
// Remove inactive collectables
|
||||
auto collectables = getChildren();
|
||||
for (auto &child: collectables)
|
||||
{
|
||||
auto collectable = std::dynamic_pointer_cast<Collectable>(child);
|
||||
if (collectable->getActive())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
remove(collectable->getId());
|
||||
}
|
||||
|
||||
if (!CONSIDER_COLLECTABLE_DEPTH_MOVEMENT)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Move collectables to new depth collections if necessary
|
||||
|
||||
// First, clear history of all depth collections
|
||||
for (auto &[depth, depthCollection]: depthCollections)
|
||||
{
|
||||
depthCollection->clearHistory();
|
||||
}
|
||||
|
||||
// Then, move collectables to new depth collections
|
||||
for (auto &[depth, depthCollection]: depthCollections)
|
||||
{
|
||||
auto collectables = depthCollection->collectables;
|
||||
for (auto &collectable: collectables)
|
||||
{
|
||||
int newDepth = std::floor(collectable->getDepth());
|
||||
if (newDepth == depth)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isValidDepth(newDepth))
|
||||
{
|
||||
LOG(ERROR) << "Collectable " << collectable->getId() << " has invalid depth " << newDepth
|
||||
<< "! Keeping it at previous depth " << depth << " ...";
|
||||
continue;
|
||||
}
|
||||
|
||||
depthCollection->remove(collectable);
|
||||
depthCollections[newDepth]->add(collectable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CollectablesCollection::isValidDepth(int desiredDepth) const
|
||||
{ return desiredDepth >= 0 && desiredDepth < (int) depthCollections.size(); }
|
||||
|
||||
void CollectablesCollection::draw(sf::RenderWindow *window)
|
||||
{
|
||||
// Render collectables in reverse order of depth
|
||||
int maxDepth = (int) depthCollections.size();
|
||||
for (int depth = maxDepth - 1; depth >= 0; depth--)
|
||||
{
|
||||
auto depthCollection = depthCollections.at(depth);
|
||||
for (auto &collectable: depthCollection->collectables)
|
||||
{
|
||||
collectable->draw(window);
|
||||
}
|
||||
}
|
||||
}
|
35
src/game/collectables/collection/collectables_collection.hpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
#ifndef HOLESOME_COLLECTABLES_COLLECTION_HPP
|
||||
#define HOLESOME_COLLECTABLES_COLLECTION_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include "collectables_depth_collection.hpp"
|
||||
|
||||
/**
|
||||
* @brief Collection of all collections for each depth.
|
||||
*/
|
||||
class CollectablesCollection : public GameObject
|
||||
{
|
||||
public:
|
||||
static std::shared_ptr<CollectablesCollection> getInstance();
|
||||
|
||||
void createEmpty(int maxDepth);
|
||||
|
||||
void update() override;
|
||||
|
||||
void draw(sf::RenderWindow *window) override;
|
||||
|
||||
void add(const std::shared_ptr<Collectable>& collectable);
|
||||
void remove(int collectableId);
|
||||
|
||||
private:
|
||||
static inline std::shared_ptr<CollectablesCollection> singletonInstance = nullptr;
|
||||
|
||||
std::map<int, std::shared_ptr<CollectablesDepthCollection>> depthCollections = {};
|
||||
|
||||
bool isValidDepth(int desiredDepth) const;
|
||||
};
|
||||
|
||||
|
||||
#endif //HOLESOME_COLLECTABLES_COLLECTION_HPP
|
|
@ -0,0 +1,23 @@
|
|||
#include "collectables_depth_collection.hpp"
|
||||
|
||||
CollectablesDepthCollection::CollectablesDepthCollection(int depth)
|
||||
: depth(depth)
|
||||
{}
|
||||
|
||||
void CollectablesDepthCollection::clearHistory()
|
||||
{
|
||||
recentlyAdded.clear();
|
||||
recentlyRemoved.clear();
|
||||
}
|
||||
|
||||
void CollectablesDepthCollection::add(const std::shared_ptr<Collectable> &collectable)
|
||||
{
|
||||
collectables.insert(collectable);
|
||||
recentlyAdded.insert(collectable);
|
||||
}
|
||||
|
||||
void CollectablesDepthCollection::remove(const std::shared_ptr<Collectable> &collectable)
|
||||
{
|
||||
recentlyRemoved.insert(collectable);
|
||||
collectables.erase(collectable);
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef HOLESOME_COLLECTABLES_DEPTH_COLLECTION_HPP
|
||||
#define HOLESOME_COLLECTABLES_DEPTH_COLLECTION_HPP
|
||||
|
||||
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include "../collectable.hpp"
|
||||
|
||||
/**
|
||||
* @brief Collection of collectables for a specific depth.
|
||||
*/
|
||||
struct CollectablesDepthCollection
|
||||
{
|
||||
int depth;
|
||||
std::set<std::shared_ptr<Collectable>> collectables = {};
|
||||
std::set<std::shared_ptr<Collectable>> recentlyRemoved = {};
|
||||
std::set<std::shared_ptr<Collectable>> recentlyAdded = {};
|
||||
|
||||
explicit CollectablesDepthCollection(int depth);
|
||||
|
||||
void clearHistory();
|
||||
|
||||
void add(const std::shared_ptr<Collectable>& collectable);
|
||||
void remove(const std::shared_ptr<Collectable>& collectable);
|
||||
};
|
||||
|
||||
|
||||
#endif //HOLESOME_COLLECTABLES_DEPTH_COLLECTION_HPP
|
|
@ -1,11 +0,0 @@
|
|||
#include "environment_collectable.hpp"
|
||||
|
||||
EnvironmentCollectable::EnvironmentCollectable(GridCoordinates position)
|
||||
{
|
||||
coordinates->set(position);
|
||||
}
|
||||
|
||||
void EnvironmentCollectable::draw(sf::RenderWindow *window)
|
||||
{
|
||||
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
#ifndef HOLESOME_ENVIRONMENT_COLLECTABLE_HPP
|
||||
#define HOLESOME_ENVIRONMENT_COLLECTABLE_HPP
|
||||
|
||||
|
||||
#include "../game_object.h"
|
||||
|
||||
class EnvironmentCollectable : public GameObject
|
||||
{
|
||||
public:
|
||||
EnvironmentCollectable(GridCoordinates position);
|
||||
|
||||
void draw(sf::RenderWindow *window) override;
|
||||
};
|
||||
|
||||
|
||||
#endif //HOLESOME_ENVIRONMENT_COLLECTABLE_HPP
|
36
src/game/frame_counter.cpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#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;
|
||||
|
||||
}
|
25
src/game/frame_counter.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#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
|
|
@ -4,33 +4,28 @@
|
|||
|
||||
#include "game.h"
|
||||
#include "level/level_loader.hpp"
|
||||
#include "physics/map_simulation.hpp"
|
||||
#include "../debug/grid_debug_layer.h"
|
||||
#include "player/player_spawner.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)),
|
||||
gameObjects(),
|
||||
views(),
|
||||
loadedLevelConfig()
|
||||
Game::Game(std::shared_ptr<sf::RenderWindow> window) : window(std::move(window))
|
||||
{
|
||||
}
|
||||
|
||||
Game::~Game()
|
||||
{
|
||||
for (auto &gameObject: gameObjects)
|
||||
{
|
||||
delete gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
void Game::run()
|
||||
{
|
||||
sf::Clock clock;
|
||||
sf::Time TimeSinceLastUpdate = sf::seconds(0);
|
||||
frameCounter->reset();
|
||||
|
||||
LOG(INFO) << "Game loop started ...";
|
||||
while (window->isOpen())
|
||||
{
|
||||
|
||||
InputMapper::getInstance()->processEvents();
|
||||
TimeSinceLastUpdate += clock.restart();
|
||||
while (TimeSinceLastUpdate >= FRAME_TIME)
|
||||
|
@ -40,8 +35,11 @@ void Game::run()
|
|||
update();
|
||||
InputMapper::getInstance()->processEvents();
|
||||
}
|
||||
|
||||
drawFrame();
|
||||
}
|
||||
|
||||
LOG(INFO) << "Game closing ...";
|
||||
}
|
||||
|
||||
void Game::exit()
|
||||
|
@ -62,17 +60,27 @@ void Game::drawFrame()
|
|||
}
|
||||
|
||||
window->display();
|
||||
frameCounter->addFrame();
|
||||
}
|
||||
|
||||
void Game::addGameObject(GameObject *gameObject)
|
||||
void Game::addGameObject(const std::shared_ptr<GameObject> &gameObject)
|
||||
{
|
||||
gameObjects.push_back(gameObject);
|
||||
gameObjectsBuffer.push_back(gameObject);
|
||||
}
|
||||
|
||||
void Game::update()
|
||||
{
|
||||
handleWinning();
|
||||
|
||||
// Add new game objects
|
||||
for (const auto &gameObject: gameObjectsBuffer)
|
||||
{
|
||||
gameObjects.push_back(gameObject);
|
||||
}
|
||||
gameObjectsBuffer.clear();
|
||||
|
||||
// Basic Updates
|
||||
for (auto &gameObject: gameObjects)
|
||||
for (const auto &gameObject: gameObjects)
|
||||
{
|
||||
if (gameObject->getActive())
|
||||
{
|
||||
|
@ -81,7 +89,7 @@ void Game::update()
|
|||
}
|
||||
|
||||
// Late updates
|
||||
for (auto &gameObject: gameObjects)
|
||||
for (const auto &gameObject: gameObjects)
|
||||
{
|
||||
if (gameObject->getActive())
|
||||
{
|
||||
|
@ -89,11 +97,23 @@ void Game::update()
|
|||
}
|
||||
}
|
||||
|
||||
// Physics updates
|
||||
for (const auto &gameObject: gameObjects)
|
||||
{
|
||||
if (gameObject->getActive())
|
||||
{
|
||||
gameObject->physicsUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
InputMapper::getInstance()->updateIdentities();
|
||||
|
||||
if (isLevelLoaded())
|
||||
for (const auto &gameObject: gameObjects)
|
||||
{
|
||||
MapSimulation::getInstance()->updateSimulation();
|
||||
if (gameObject->getActive())
|
||||
{
|
||||
gameObject->preRenderUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,17 +139,8 @@ 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()
|
||||
{
|
||||
for (auto &gameObject: gameObjects)
|
||||
{
|
||||
delete gameObject;
|
||||
}
|
||||
gameObjects.clear();
|
||||
}
|
||||
|
||||
|
@ -142,3 +153,99 @@ 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,6 +8,8 @@
|
|||
#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;
|
||||
|
||||
|
@ -15,11 +17,11 @@ class TrackingView;
|
|||
class Game
|
||||
{
|
||||
public:
|
||||
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);
|
||||
static std::shared_ptr<Game> constructInstance(const std::shared_ptr<sf::RenderWindow> &window);
|
||||
|
||||
~Game();
|
||||
static std::shared_ptr<Game> getInstance();
|
||||
|
||||
explicit Game(std::shared_ptr<sf::RenderWindow> window);
|
||||
|
||||
void run();
|
||||
|
||||
|
@ -29,23 +31,37 @@ public:
|
|||
|
||||
void setLevel(LevelConfig levelConfig);
|
||||
|
||||
bool isLevelLoaded() const;
|
||||
void startCountdown(int durationInSeconds);
|
||||
|
||||
void addGameObject(GameObject *gameObject);
|
||||
[[nodiscard]] bool isLevelLoaded() const;
|
||||
|
||||
void registerView(TrackingView *view);
|
||||
void addGameObject(const std::shared_ptr<GameObject> &gameObject);
|
||||
|
||||
std::shared_ptr<sf::RenderWindow> window;
|
||||
std::vector<TrackingView*> views;
|
||||
|
||||
void showJoinScreen();
|
||||
|
||||
void generateNewLevel();
|
||||
|
||||
private:
|
||||
static inline std::shared_ptr<Game> singletonInstance = nullptr;
|
||||
std::vector<GameObject *> gameObjects;
|
||||
std::vector<std::shared_ptr<GameObject>> gameObjects = {};
|
||||
std::vector<std::shared_ptr<GameObject>> gameObjectsBuffer = {};
|
||||
|
||||
LevelConfig loadedLevelConfig;
|
||||
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,7 +3,8 @@
|
|||
#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());
|
||||
|
@ -11,13 +12,16 @@ 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;
|
||||
}
|
||||
|
@ -30,7 +34,8 @@ 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;
|
||||
|
@ -38,7 +43,11 @@ sf::ContextSettings GameFactory::getAdditionalSettings() {
|
|||
return settings;
|
||||
}
|
||||
|
||||
void GameFactory::applyAdditionalWindowConfig(sf::RenderWindow *window) {
|
||||
void GameFactory::applyAdditionalWindowConfig(sf::RenderWindow *window)
|
||||
{
|
||||
if (FRAME_LIMIT_ENABLED)
|
||||
{
|
||||
window->setFramerateLimit(FRAME_RATE);
|
||||
}
|
||||
window->setKeyRepeatEnabled(KEY_REPEAT_ENABLED);
|
||||
}
|
||||
|
|
|
@ -57,3 +57,39 @@ void GameObject::addChild(const std::shared_ptr<GameObject> &child)
|
|||
{
|
||||
addChildWorldOffset(child, {0, 0});
|
||||
}
|
||||
|
||||
void GameObject::removeChild(const std::shared_ptr<GameObject> &child)
|
||||
{
|
||||
auto it = std::find(children.begin(), children.end(), child);
|
||||
if (it != children.end())
|
||||
{
|
||||
children.erase(it);
|
||||
child->coordinates->removeParent();
|
||||
}
|
||||
}
|
||||
|
||||
void GameObject::clearChildren()
|
||||
{
|
||||
children.clear();
|
||||
}
|
||||
|
||||
void GameObject::physicsUpdate()
|
||||
{
|
||||
for (auto &child: children)
|
||||
{
|
||||
child->physicsUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void GameObject::addDetachedChild(const std::shared_ptr<GameObject> &child)
|
||||
{
|
||||
children.push_back(child);
|
||||
}
|
||||
|
||||
void GameObject::preRenderUpdate()
|
||||
{
|
||||
for (auto &child: children)
|
||||
{
|
||||
child->preRenderUpdate();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,13 +18,20 @@ public:
|
|||
|
||||
virtual void lateUpdate();
|
||||
|
||||
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);
|
||||
std::vector<std::shared_ptr<GameObject>> getChildren() const { return children; }
|
||||
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; }
|
||||
|
||||
std::shared_ptr<TranslatedCoordinates> coordinates;
|
||||
|
||||
|
|
|
@ -4,10 +4,7 @@
|
|||
enum GameAction
|
||||
{
|
||||
CONFIRM,
|
||||
CANCEL,
|
||||
MENU,
|
||||
|
||||
JUMP
|
||||
RUN
|
||||
};
|
||||
|
||||
#endif //HOLESOME_GAME_ACTION_HPP
|
||||
|
|
|
@ -52,8 +52,8 @@ void InputMapper::processEvents()
|
|||
|
||||
void InputMapper::handleKeyPress(sf::Event::KeyEvent event)
|
||||
{
|
||||
// Close game on Escape or Q in DEV Mode
|
||||
if (DEVELOPER_MODE && event.code == sf::Keyboard::Escape)
|
||||
// Close game on Special button
|
||||
if (event.code == CLOSE_GAME_BTN)
|
||||
{
|
||||
Game::getInstance()->exit();
|
||||
return;
|
||||
|
@ -121,8 +121,13 @@ 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);
|
||||
}
|
||||
|
||||
return newIdentity;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@ 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;
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
//
|
||||
// Created by max on 27.04.23.
|
||||
//
|
||||
|
||||
#include "input_handler.h"
|
|
@ -1,14 +0,0 @@
|
|||
//
|
||||
// Created by max on 27.04.23.
|
||||
//
|
||||
|
||||
#ifndef HOLESOME_INPUT_HANDLER_H
|
||||
#define HOLESOME_INPUT_HANDLER_H
|
||||
|
||||
|
||||
class InputHandler {
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif //HOLESOME_INPUT_HANDLER_H
|
39
src/game/layer/global_layer.cpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
#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);
|
||||
}
|
27
src/game/layer/global_layer.hpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
#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
|
|
@ -3,23 +3,51 @@
|
|||
|
||||
|
||||
#include <SFML/System/Vector2.hpp>
|
||||
#include <utility>
|
||||
#include "../../coordinates/coordinates.h"
|
||||
#include "../../logging/easylogging++.h"
|
||||
#include "../collectables/collectable_in_level.hpp"
|
||||
#include <vector>
|
||||
#include "../../sprites/tiling/tilemap_config.hpp"
|
||||
#include "../../sprites/sprite_factory.hpp"
|
||||
|
||||
struct LevelConfig
|
||||
{
|
||||
std::string name;
|
||||
sf::Vector2f worldMapSize = {};
|
||||
std::vector<sf::Vector2f> playerSpawnPoints = {};
|
||||
int durationInSeconds = 0;
|
||||
sf::Vector2i worldMapSize = {};
|
||||
TileMapConfig tileMapConfig = {};
|
||||
std::vector<GridCoordinates> playerSpawnPoints = {};
|
||||
std::vector<CollectableInLevel> collectables = {};
|
||||
std::vector<sf::Color> skyColors = {};
|
||||
|
||||
LevelConfig(std::string name, const sf::Vector2f &worldMapSize,
|
||||
const std::vector<sf::Vector2f> &playerSpawnPoints)
|
||||
: name(std::move(name)), worldMapSize(worldMapSize), playerSpawnPoints(playerSpawnPoints)
|
||||
{ }
|
||||
LevelConfig(std::string name,
|
||||
int durationInSeconds,
|
||||
const std::vector<GridCoordinates> &playerSpawnPoints,
|
||||
const std::vector<CollectableInLevel> &collectables,
|
||||
std::vector<sf::Color> skyColors,
|
||||
const TileMapConfig &tileMapConfig)
|
||||
: name(std::move(name)),
|
||||
durationInSeconds(durationInSeconds),
|
||||
playerSpawnPoints(playerSpawnPoints),
|
||||
tileMapConfig(tileMapConfig),
|
||||
skyColors(std::move(skyColors))
|
||||
{
|
||||
worldMapSize = tileMapConfig.getSize();
|
||||
|
||||
// Remove invalid collectables
|
||||
for (auto &collectable: collectables)
|
||||
{
|
||||
if (collectable.isValid())
|
||||
{
|
||||
this->collectables.push_back(collectable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LevelConfig() = default;
|
||||
|
||||
bool isValid() const {
|
||||
bool isValid() const
|
||||
{
|
||||
return !name.empty();
|
||||
}
|
||||
};
|
||||
|
|
134
src/game/level/level_generator.cpp
Normal file
|
@ -0,0 +1,134 @@
|
|||
#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;
|
||||
}
|
21
src/game/level/level_generator.hpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#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
|
|
@ -1,27 +1,76 @@
|
|||
#include "level_loader.hpp"
|
||||
#include "../game.h"
|
||||
#include "../physics/map_simulation.hpp"
|
||||
#include "../physics/map/map_simulation.hpp"
|
||||
#include "../../debug/grid_debug_layer.h"
|
||||
#include "../player/player_spawner.hpp"
|
||||
#include "../../levels.hpp"
|
||||
#include "../collectables/collection/collectables_collection.hpp"
|
||||
#include "../collectables/collectable_factory.hpp"
|
||||
#include "../player/player_collection.hpp"
|
||||
#include "level_renderer.hpp"
|
||||
#include "../../sprites/skymap/skymap.hpp"
|
||||
#include "../camera/multiplayer_view.hpp"
|
||||
#include "../physics/holes/holes_simulation.hpp"
|
||||
#include "../physics/holes/layouts/hole_layout.hpp"
|
||||
#include "../layer/global_layer.hpp"
|
||||
|
||||
void LevelLoader::loadLevel(LevelConfig levelConfig)
|
||||
void LevelLoader::loadLevel(const LevelConfig &levelConfig)
|
||||
{
|
||||
Game::getInstance()->clearGameObjects();
|
||||
auto game = Game::getInstance();
|
||||
game->clearGameObjects();
|
||||
|
||||
LOG(INFO) << "Loading level '" << levelConfig.name << "' ...";
|
||||
|
||||
Game::getInstance()->setLevel(levelConfig);
|
||||
MapSimulation::getInstance()->resetMap(levelConfig.worldMapSize);
|
||||
game->setLevel(levelConfig);
|
||||
InputMapper::getInstance()->allowNewInputIdentities = false;
|
||||
|
||||
// Add basic game objects
|
||||
if (DEVELOPER_MODE)
|
||||
MapSimulation::getInstance()->resetMap(levelConfig.worldMapSize);
|
||||
HolesSimulation::getInstance()->clear();
|
||||
GlobalLayer::getInstance()->clear();
|
||||
HoleLayout::getInstance()->clear();
|
||||
MultiplayerView::getInstance()->clear();
|
||||
|
||||
|
||||
// Add views
|
||||
game->addGameObject(MultiplayerView::getInstance());
|
||||
|
||||
// Add rendered level objects
|
||||
std::shared_ptr<LevelRenderer> levelRenderer = std::make_shared<LevelRenderer>();
|
||||
game->addGameObject(levelRenderer);
|
||||
|
||||
levelRenderer->addChild(std::make_shared<Skymap>(levelConfig.skyColors, levelConfig.worldMapSize));
|
||||
levelRenderer->addChild(SpriteFactory::createTileMap(levelConfig.tileMapConfig));
|
||||
|
||||
if (DB_WORLD_GRID_RENDER)
|
||||
{
|
||||
Game::getInstance()->addGameObject(new GridDebugLayer(0, 50, 0, 50));
|
||||
levelRenderer->addChild(std::make_shared<GridDebugLayer>(0, 50, 0, 50));
|
||||
}
|
||||
|
||||
Game::getInstance()->addGameObject(new TrackingView());
|
||||
Game::getInstance()->addGameObject(new PlayerSpawner(levelConfig.playerSpawnPoints));
|
||||
levelRenderer->addChild(PlayerCollection::getInstance());
|
||||
PlayerCollection::getInstance()->setSpawnPoints(levelConfig.playerSpawnPoints);
|
||||
PlayerCollection::getInstance()->resetPlayers();
|
||||
PlayerCollection::getInstance()->activatePlayers();
|
||||
|
||||
// Prepare collectables framework
|
||||
auto maxDepth = (int) levelConfig.worldMapSize.x * 2;
|
||||
auto collectablesCollection = CollectablesCollection::getInstance();
|
||||
collectablesCollection->createEmpty(maxDepth);
|
||||
levelRenderer->addChild(collectablesCollection);
|
||||
|
||||
// Add physics simulations
|
||||
game->addGameObject(MapSimulation::getInstance());
|
||||
game->addGameObject(HolesSimulation::getInstance());
|
||||
game->addGameObject(HoleLayout::getInstance());
|
||||
|
||||
// Spawn collectibles
|
||||
for (auto const &collectableInfo: levelConfig.collectables)
|
||||
{
|
||||
spawnCollectable(collectableInfo);
|
||||
}
|
||||
|
||||
game->startCountdown(levelConfig.durationInSeconds);
|
||||
|
||||
// Must be last
|
||||
game->addGameObject(GlobalLayer::getInstance());
|
||||
|
||||
LOG(INFO) << "Finished loading level '" << levelConfig.name << "'.";
|
||||
}
|
||||
|
@ -29,11 +78,33 @@ void LevelLoader::loadLevel(LevelConfig levelConfig)
|
|||
void LevelLoader::loadLevel(const std::string &levelName)
|
||||
{
|
||||
// Does level exist?
|
||||
if (!LEVELS.contains(levelName))
|
||||
if (!all_levels.contains(levelName))
|
||||
{
|
||||
LOG(ERROR) << "Level '" << levelName << "' not found. Could not load it.";
|
||||
throw std::invalid_argument("Could not load level.");
|
||||
}
|
||||
|
||||
LevelLoader::loadLevel(LEVELS.at(levelName));
|
||||
LevelLoader::loadLevel(all_levels.at(levelName));
|
||||
}
|
||||
|
||||
void LevelLoader::spawnCollectable(const CollectableInLevel &collectableInfo)
|
||||
{
|
||||
LOG(INFO) << "Spawning collectable '" << collectableInfo.name << "' ...";
|
||||
auto collectable = CollectableFactory::createFromInLevelConfig(collectableInfo);
|
||||
LOG(INFO) << "Has depth " << collectable->getDepth() << ".";
|
||||
CollectablesCollection::getInstance()->add(collectable);
|
||||
HolesSimulation::getInstance()->addCollectable(collectable);
|
||||
}
|
||||
|
||||
void LevelLoader::cleanUp()
|
||||
{
|
||||
auto game = Game::getInstance();
|
||||
game->clearGameObjects();
|
||||
game->setLevel(LevelConfig());
|
||||
HolesSimulation::getInstance()->clear();
|
||||
PlayerCollection::getInstance()->resetPlayers();
|
||||
GlobalLayer::getInstance()->clear();
|
||||
HoleLayout::getInstance()->clear();
|
||||
|
||||
LOG(INFO) << "Cleaned up level.";
|
||||
}
|
||||
|
|
|
@ -8,9 +8,13 @@
|
|||
class LevelLoader
|
||||
{
|
||||
public:
|
||||
static void loadLevel(LevelConfig levelConfig);
|
||||
static void loadLevel(const LevelConfig& levelConfig);
|
||||
|
||||
static void loadLevel(const std::string &levelName);
|
||||
|
||||
static void cleanUp();
|
||||
|
||||
static void spawnCollectable(const CollectableInLevel &collectableInfo);
|
||||
};
|
||||
|
||||
|
||||
|
|
15
src/game/level/level_renderer.cpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
#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);
|
||||
}
|
14
src/game/level/level_renderer.hpp
Normal file
|
@ -0,0 +1,14 @@
|
|||
#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
|
78
src/game/physics/body_adapter.cpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
#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);
|
||||
}
|
37
src/game/physics/body_adapter.hpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#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
|
52
src/game/physics/holes/collectable_simulation.cpp
Normal file
|
@ -0,0 +1,52 @@
|
|||
#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);
|
||||
}
|
37
src/game/physics/holes/collectable_simulation.hpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#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
|
193
src/game/physics/holes/ground/collectable_sim_ground.cpp
Normal file
|
@ -0,0 +1,193 @@
|
|||
#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;
|
||||
}
|
43
src/game/physics/holes/ground/collectable_sim_ground.hpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
#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
|
|
@ -0,0 +1,22 @@
|
|||
#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
|
63
src/game/physics/holes/holes_simulation.cpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
#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;
|
||||
}
|
||||
}
|
||||
}
|
29
src/game/physics/holes/holes_simulation.hpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
#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
|
15
src/game/physics/holes/layouts/depth_hole_description.hpp
Normal file
|
@ -0,0 +1,15 @@
|
|||
#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
|
38
src/game/physics/holes/layouts/depth_hole_layout.hpp
Normal file
|
@ -0,0 +1,38 @@
|
|||
#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
|
44
src/game/physics/holes/layouts/hole_description.hpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
#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
|
50
src/game/physics/holes/layouts/hole_layout.cpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
#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};
|
||||
}
|
30
src/game/physics/holes/layouts/hole_layout.hpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
#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
|
100
src/game/physics/map/map_player.cpp
Normal file
|
@ -0,0 +1,100 @@
|
|||
#include "map_player.hpp"
|
||||
#include "map_simulation.hpp"
|
||||
|
||||
void MapPlayer::updateSimulationPosition()
|
||||
{
|
||||
auto coordinates = player->coordinates->world();
|
||||
b2Vec2 playerPosition = b2Vec2(coordinates.x, coordinates.y);
|
||||
|
||||
// Calculate velocity that theoretically needs to be applied to the body, to get to the same position
|
||||
b2Vec2 delta = playerPosition - body->GetPosition();
|
||||
b2Vec2 velocity = {delta.x * FRAME_RATE, delta.y * FRAME_RATE};
|
||||
|
||||
body->SetLinearVelocity(velocity);
|
||||
|
||||
if (player->getWorldRadius() != shapeRadius)
|
||||
{
|
||||
updateShape();
|
||||
}
|
||||
}
|
||||
|
||||
void MapPlayer::updatePlayerPosition() const
|
||||
{
|
||||
b2Vec2 playerPosition = body->GetPosition();
|
||||
player->coordinates->setWorld({playerPosition.x, playerPosition.y});
|
||||
}
|
||||
|
||||
void MapPlayer::updateShape()
|
||||
{
|
||||
shapeRadius = player->getWorldRadius();
|
||||
|
||||
b2Fixture *oldFixture = body->GetFixtureList();
|
||||
if (oldFixture != nullptr)
|
||||
{
|
||||
body->DestroyFixture(oldFixture);
|
||||
}
|
||||
|
||||
b2CircleShape shape;
|
||||
shape.m_radius = shapeRadius;
|
||||
shape.m_p.Set(0, 0);
|
||||
|
||||
b2FixtureDef fixtureDef;
|
||||
fixtureDef.shape = &shape;
|
||||
fixtureDef.density = 1.0f;
|
||||
|
||||
body->CreateFixture(&fixtureDef);
|
||||
}
|
||||
|
||||
void MapPlayer::updateCollidingWithPlayers()
|
||||
{
|
||||
collidingWithPlayers.clear();
|
||||
|
||||
for (b2ContactEdge *contactEdge = body->GetContactList(); contactEdge != nullptr; contactEdge = contactEdge->next)
|
||||
{
|
||||
b2Contact *contact = contactEdge->contact;
|
||||
if (!contact->IsTouching())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
b2Fixture *fixtureA = contact->GetFixtureA();
|
||||
b2Fixture *fixtureB = contact->GetFixtureB();
|
||||
|
||||
if (fixtureA->IsSensor() || fixtureB->IsSensor())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
b2Body *bodyA = fixtureA->GetBody();
|
||||
b2Body *bodyB = fixtureB->GetBody();
|
||||
|
||||
if (bodyA->GetType() != b2_dynamicBody || bodyB->GetType() != b2_dynamicBody)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
std::shared_ptr<MapPlayer> mapPlayerA = MapSimulation::getInstance()->getMapPlayerByBody(bodyA);
|
||||
std::shared_ptr<MapPlayer> mapPlayerB = MapSimulation::getInstance()->getMapPlayerByBody(bodyB);
|
||||
|
||||
if (mapPlayerA == nullptr || mapPlayerB == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mapPlayerA->player->getPlayerId() == player->getPlayerId())
|
||||
{
|
||||
collidingWithPlayers.push_back(mapPlayerB->player->getPlayerId());
|
||||
} else
|
||||
{
|
||||
collidingWithPlayers.push_back(mapPlayerA->player->getPlayerId());
|
||||
}
|
||||
}
|
||||
|
||||
player->setCollidingPlayers(collidingWithPlayers);
|
||||
}
|
||||
|
||||
void MapPlayer::updatePlayer()
|
||||
{
|
||||
updatePlayerPosition();
|
||||
updateCollidingWithPlayers();
|
||||
}
|
37
src/game/physics/map/map_player.hpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
#ifndef HOLESOME_MAP_PLAYER_HPP
|
||||
#define HOLESOME_MAP_PLAYER_HPP
|
||||
|
||||
|
||||
#include <box2d/box2d.h>
|
||||
|
||||
#include <utility>
|
||||
#include "../../player/player.hpp"
|
||||
#include "../../../config.h"
|
||||
|
||||
class MapPlayer
|
||||
{
|
||||
public:
|
||||
std::shared_ptr<Player> player;
|
||||
b2Body *body;
|
||||
float shapeRadius = 0;
|
||||
std::vector<int> collidingWithPlayers = {};
|
||||
|
||||
MapPlayer(std::shared_ptr<Player> player, b2Body *body) : player(std::move(player)), body(body)
|
||||
{
|
||||
updateShape();
|
||||
}
|
||||
|
||||
void updateSimulationPosition();
|
||||
void updatePlayer();
|
||||
|
||||
void updateShape();
|
||||
|
||||
private:
|
||||
|
||||
void updatePlayerPosition() const;
|
||||
|
||||
void updateCollidingWithPlayers();
|
||||
};
|
||||
|
||||
|
||||
#endif //HOLESOME_MAP_PLAYER_HPP
|
131
src/game/physics/map/map_simulation.cpp
Normal file
|
@ -0,0 +1,131 @@
|
|||
#include "map_simulation.hpp"
|
||||
#include "../../../config.h"
|
||||
#include "../../player/player_collection.hpp"
|
||||
|
||||
MapSimulation::MapSimulation()
|
||||
{
|
||||
mapPlayersById = std::map<int, std::shared_ptr<MapPlayer>>();
|
||||
}
|
||||
|
||||
std::shared_ptr<MapSimulation> MapSimulation::getInstance()
|
||||
{
|
||||
if (singletonInstance == nullptr)
|
||||
{
|
||||
singletonInstance = std::make_shared<MapSimulation>();
|
||||
}
|
||||
return singletonInstance;
|
||||
}
|
||||
|
||||
void MapSimulation::physicsUpdate()
|
||||
{
|
||||
// Update simulation positions
|
||||
for (auto &mapPlayer: mapPlayersById)
|
||||
{
|
||||
mapPlayer.second->updateSimulationPosition();
|
||||
}
|
||||
|
||||
world->Step(FRAME_TIME.asSeconds(), MAPSIM_VELOCITY_ITERATIONS, MAPSIM_POSITION_ITERATIONS);
|
||||
|
||||
// Update players
|
||||
for (auto &mapPlayer: mapPlayersById)
|
||||
{
|
||||
mapPlayer.second->updatePlayer();
|
||||
}
|
||||
}
|
||||
|
||||
void MapSimulation::resetMap(sf::Vector2<int> worldMapSize)
|
||||
{
|
||||
// Clear all players
|
||||
for (auto &[id, _]: mapPlayersById)
|
||||
{
|
||||
world->DestroyBody(mapPlayersById[id]->body);
|
||||
}
|
||||
mapPlayersById.clear();
|
||||
|
||||
// No gravity, since this a top-down view of the map
|
||||
world = std::make_shared<b2World>(b2Vec2(0.0f, 0.0f));
|
||||
|
||||
// Create map borders
|
||||
constructSquareObstacle(-MAPSIM_WALL_THICKNESS, -MAPSIM_WALL_THICKNESS,
|
||||
0, worldMapSize.y + MAPSIM_WALL_THICKNESS); // Bottom left
|
||||
constructSquareObstacle(0, -MAPSIM_WALL_THICKNESS,
|
||||
worldMapSize.x, 0); // Bottom right
|
||||
constructSquareObstacle(worldMapSize.x, -MAPSIM_WALL_THICKNESS,
|
||||
worldMapSize.x + MAPSIM_WALL_THICKNESS,
|
||||
worldMapSize.y + MAPSIM_WALL_THICKNESS); // Top right
|
||||
constructSquareObstacle(0, worldMapSize.y,
|
||||
worldMapSize.x, worldMapSize.y + MAPSIM_WALL_THICKNESS); // Top left
|
||||
}
|
||||
|
||||
void MapSimulation::constructSquareObstacle(float minX, float minY, float maxX, float maxY)
|
||||
{
|
||||
b2BodyDef bodyDef;
|
||||
bodyDef.type = b2_staticBody;
|
||||
bodyDef.position.Set((maxX + minX) / 2.f, (maxY + minY) / 2.f);
|
||||
b2Body *body = world->CreateBody(&bodyDef);
|
||||
|
||||
b2PolygonShape shape;
|
||||
shape.SetAsBox((maxX - minX) / 2.f, (maxY - minY) / 2.f);
|
||||
|
||||
b2FixtureDef fixtureDef;
|
||||
fixtureDef.shape = &shape;
|
||||
fixtureDef.density = 1.0f;
|
||||
|
||||
body->CreateFixture(&fixtureDef);
|
||||
}
|
||||
|
||||
void MapSimulation::addPlayer(const std::shared_ptr<Player> &player)
|
||||
{
|
||||
b2BodyDef bodyDef;
|
||||
bodyDef.type = b2_dynamicBody;
|
||||
bodyDef.position.Set(player->spawnPosition.world().x, player->spawnPosition.world().y);
|
||||
b2Body *body = world->CreateBody(&bodyDef);
|
||||
mapPlayersById[player->getPlayerId()] = std::make_shared<MapPlayer>(player, body);
|
||||
}
|
||||
|
||||
void MapSimulation::update()
|
||||
{
|
||||
// Update players from player collection
|
||||
// New player
|
||||
for (auto &player: PlayerCollection::getInstance()->getNewPlayers())
|
||||
{
|
||||
addPlayer(player);
|
||||
}
|
||||
// Removed players
|
||||
for (auto &player: PlayerCollection::getInstance()->getRemovedPlayers())
|
||||
{
|
||||
removePlayer(player);
|
||||
}
|
||||
}
|
||||
|
||||
void MapSimulation::removePlayer(std::shared_ptr<Player> &player)
|
||||
{
|
||||
// Remove body from simulation
|
||||
int playerId = player->getPlayerId();
|
||||
world->DestroyBody(mapPlayersById[playerId]->body);
|
||||
mapPlayersById.erase(playerId);
|
||||
}
|
||||
|
||||
std::shared_ptr<MapPlayer> MapSimulation::getMapPlayerByBody(b2Body *body) const
|
||||
{
|
||||
for (auto &mapPlayer: mapPlayersById)
|
||||
{
|
||||
if (mapPlayer.second->body == body)
|
||||
{
|
||||
return mapPlayer.second;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void MapSimulation::removePlayer(int playerId)
|
||||
{
|
||||
// Still in simulation?
|
||||
if (mapPlayersById.find(playerId) == mapPlayersById.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
world->DestroyBody(mapPlayersById[playerId]->body);
|
||||
mapPlayersById.erase(playerId);
|
||||
}
|
|
@ -4,22 +4,27 @@
|
|||
#include <memory>
|
||||
#include <box2d/box2d.h>
|
||||
#include <SFML/System/Vector2.hpp>
|
||||
#include "../player/player.hpp"
|
||||
#include "../../player/player.hpp"
|
||||
#include "map_player.hpp"
|
||||
|
||||
class MapSimulation
|
||||
class MapSimulation : public GameObject
|
||||
{
|
||||
public:
|
||||
MapSimulation();
|
||||
|
||||
void updateSimulation();
|
||||
void update() override;
|
||||
|
||||
void resetMap(sf::Vector2f worldMapSize);
|
||||
void physicsUpdate() override;
|
||||
|
||||
void resetMap(sf::Vector2<int> worldMapSize);
|
||||
|
||||
static std::shared_ptr<MapSimulation> getInstance();
|
||||
|
||||
void addPlayer(Player *player);
|
||||
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;
|
||||
|
@ -29,6 +34,7 @@ private:
|
|||
|
||||
void constructSquareObstacle(float minX, float minY, float maxX, float maxY);
|
||||
|
||||
void removePlayer(std::shared_ptr<Player> &player);
|
||||
};
|
||||
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
#ifndef HOLESOME_MAP_PLAYER_HPP
|
||||
#define HOLESOME_MAP_PLAYER_HPP
|
||||
|
||||
|
||||
#include <box2d/box2d.h>
|
||||
#include "../player/player.hpp"
|
||||
#include "../../config.h"
|
||||
|
||||
struct MapPlayer
|
||||
{
|
||||
Player *player;
|
||||
b2Body *body;
|
||||
|
||||
MapPlayer(Player *player, b2Body *body) : player(player), body(body)
|
||||
{}
|
||||
|
||||
void updateSimulationPosition() const
|
||||
{
|
||||
auto coordinates = player->coordinates->world();
|
||||
b2Vec2 playerPosition = b2Vec2(coordinates.x, coordinates.y);
|
||||
|
||||
// Calculate velocity that theoretically needs to be applied to the body, to get to the same position
|
||||
b2Vec2 delta = playerPosition - body->GetPosition();
|
||||
b2Vec2 velocity = {delta.x * FRAME_RATE, delta.y * FRAME_RATE};
|
||||
|
||||
body->SetLinearVelocity(velocity);
|
||||
}
|
||||
|
||||
void updatePlayerPosition() const
|
||||
{
|
||||
b2Vec2 playerPosition = body->GetPosition();
|
||||
player->coordinates->set(sf::Vector2f(playerPosition.x, playerPosition.y));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#endif //HOLESOME_MAP_PLAYER_HPP
|
|
@ -1,82 +0,0 @@
|
|||
#include "map_simulation.hpp"
|
||||
#include "../../config.h"
|
||||
|
||||
MapSimulation::MapSimulation()
|
||||
{
|
||||
mapPlayersById = std::map<int, std::shared_ptr<MapPlayer>>();
|
||||
}
|
||||
|
||||
std::shared_ptr<MapSimulation> MapSimulation::getInstance()
|
||||
{
|
||||
if (singletonInstance == nullptr)
|
||||
{
|
||||
singletonInstance = std::make_shared<MapSimulation>();
|
||||
}
|
||||
return singletonInstance;
|
||||
}
|
||||
|
||||
void MapSimulation::updateSimulation()
|
||||
{
|
||||
// Update simulation positions
|
||||
for (auto &mapPlayer: mapPlayersById)
|
||||
{
|
||||
mapPlayer.second->updateSimulationPosition();
|
||||
}
|
||||
|
||||
world->Step(FRAME_TIME.asSeconds(), MAPSIM_VELOCITY_ITERATIONS, MAPSIM_POSITION_ITERATIONS);
|
||||
|
||||
// Update player positions
|
||||
for (auto &mapPlayer: mapPlayersById)
|
||||
{
|
||||
mapPlayer.second->updatePlayerPosition();
|
||||
}
|
||||
}
|
||||
|
||||
void MapSimulation::resetMap(sf::Vector2f worldMapSize)
|
||||
{
|
||||
// No gravity, since this a top-down view of the map
|
||||
world = std::make_shared<b2World>(b2Vec2(0.0f, 0.0f));
|
||||
mapPlayersById.clear();
|
||||
|
||||
// Create map borders
|
||||
constructSquareObstacle(-1, 0, 0, worldMapSize.y);
|
||||
constructSquareObstacle(0, -1, worldMapSize.x, 0);
|
||||
constructSquareObstacle(worldMapSize.x, 0, worldMapSize.x + 1, worldMapSize.y);
|
||||
constructSquareObstacle(0, worldMapSize.y, worldMapSize.x, worldMapSize.y + 1);
|
||||
}
|
||||
|
||||
void MapSimulation::constructSquareObstacle(float minX, float minY, float maxX, float maxY)
|
||||
{
|
||||
b2BodyDef bodyDef;
|
||||
bodyDef.type = b2_staticBody;
|
||||
bodyDef.position.Set((maxX + minX) / 2.f, (maxY + minY) / 2.f);
|
||||
b2Body *body = world->CreateBody(&bodyDef);
|
||||
|
||||
b2PolygonShape shape;
|
||||
shape.SetAsBox((maxX - minX) / 2.f, (maxY - minY) / 2.f);
|
||||
|
||||
b2FixtureDef fixtureDef;
|
||||
fixtureDef.shape = &shape;
|
||||
fixtureDef.density = 1.0f;
|
||||
|
||||
body->CreateFixture(&fixtureDef);
|
||||
}
|
||||
|
||||
void MapSimulation::addPlayer(Player *player)
|
||||
{
|
||||
b2BodyDef bodyDef;
|
||||
bodyDef.type = b2_dynamicBody;
|
||||
bodyDef.position.Set(player->coordinates->world().x, player->coordinates->world().y);
|
||||
b2Body *body = world->CreateBody(&bodyDef);
|
||||
|
||||
b2CircleShape shape;
|
||||
shape.m_radius = player->getWorldRadius();
|
||||
shape.m_p.Set(0, 0);
|
||||
|
||||
b2FixtureDef fixtureDef;
|
||||
fixtureDef.shape = &shape;
|
||||
fixtureDef.density = 1.0f;
|
||||
|
||||
body->CreateFixture(&fixtureDef);
|
||||
mapPlayersById[player->getPlayerId()] = std::make_shared<MapPlayer>(player, body);
|
||||
}
|
|
@ -1,20 +1,25 @@
|
|||
#include "player.hpp"
|
||||
#include "../../config.h"
|
||||
#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,
|
||||
sf::Vector2f spawnPosition)
|
||||
GridCoordinates initCoordinates)
|
||||
: spawnPosition(initCoordinates), skinName(skinRessourceName)
|
||||
{
|
||||
playerId = playerCreationCounter++;
|
||||
this->spawnPosition = spawnPosition;
|
||||
coordinates->set(spawnPosition);
|
||||
|
||||
coordinates->setTranslated(spawnPosition);
|
||||
input = std::move(assignedInput);
|
||||
|
||||
auto radiusInIso = getIsoRadius();
|
||||
auto sprite = std::make_shared<VersatileSprite>(skinRessourceName, sf::Vector2f{radiusInIso * 2.f, radiusInIso * 2.f});
|
||||
addChildScreenOffset(sprite, {-radiusInIso, -radiusInIso});
|
||||
skinSprite = std::make_shared<VersatileSprite>(skinRessourceName, getIsoSize());
|
||||
addChildScreenOffset(skinSprite, IsometricCoordinates(-getIsoSize() / 2.f));
|
||||
|
||||
updateRadiusBasedOnPoints();
|
||||
|
||||
LOG(INFO) << "Player " << playerId << " created.";
|
||||
}
|
||||
|
||||
sf::Vector2f Player::getTrackablePosition() const
|
||||
|
@ -24,8 +29,7 @@ sf::Vector2f Player::getTrackablePosition() const
|
|||
|
||||
sf::Vector2f Player::getTrackableSize() const
|
||||
{
|
||||
auto isoRadius = getIsoRadius();
|
||||
return {isoRadius * 2.f, isoRadius * 2.f};
|
||||
return getIsoSize();
|
||||
}
|
||||
|
||||
void Player::update()
|
||||
|
@ -36,15 +40,13 @@ void Player::update()
|
|||
return;
|
||||
}
|
||||
|
||||
auto moveDirection = input->direction.asIsometricVector();
|
||||
auto moveDelta = moveDirection * speed * FRAME_TIME.asSeconds();
|
||||
coordinates->move(moveDelta);
|
||||
|
||||
if (input->isPerformingAction(GameAction::JUMP))
|
||||
if (!passiveMode)
|
||||
{
|
||||
coordinates->move({0, 0, 10});
|
||||
performInteractiveUpdates();
|
||||
}
|
||||
|
||||
handlePlayerCollisions();
|
||||
|
||||
GameObject::update();
|
||||
}
|
||||
|
||||
|
@ -64,12 +66,175 @@ int Player::getPlayerId() const
|
|||
return playerId;
|
||||
}
|
||||
|
||||
float Player::getIsoRadius() const
|
||||
{
|
||||
return radiusInWorld * WORLD_TO_ISO_SCALE;
|
||||
}
|
||||
|
||||
float Player::getWorldRadius() const
|
||||
{
|
||||
return radiusInWorld;
|
||||
}
|
||||
|
||||
sf::Vector2f Player::getIsoSize() const
|
||||
{
|
||||
// TODO: For some reason, the player is a little to narrow. This fixes it.
|
||||
const float fixFactor = sqrt(2);
|
||||
float width = radiusInWorld * 2.f * WORLD_TO_ISO_SCALE * fixFactor;
|
||||
return {width, width * ISOMETRIC_SKEW};
|
||||
}
|
||||
|
||||
void Player::setWorldRadius(float newWorldRadius)
|
||||
{
|
||||
radiusInWorld = newWorldRadius;
|
||||
|
||||
// Update skin
|
||||
auto newSize = getIsoSize();
|
||||
skinSprite->setSize(newSize);
|
||||
skinSprite->coordinates->setScreenOffset(IsometricCoordinates(-newSize / 2.f));
|
||||
}
|
||||
|
||||
int Player::getPoints() const
|
||||
{
|
||||
return points;
|
||||
}
|
||||
|
||||
void Player::updateRadiusBasedOnPoints()
|
||||
{
|
||||
int points = getPoints();
|
||||
float newWorldRadius = PLAYER_MIN_RADIUS + PLAYER_RADIUS_PER_LEVEL * points / 10.f;
|
||||
|
||||
setWorldRadius(newWorldRadius);
|
||||
}
|
||||
|
||||
void Player::consume(int consumedPoints)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -11,34 +11,71 @@ class Player : public GameObject, public ITrackable
|
|||
{
|
||||
public:
|
||||
Player(std::shared_ptr<InputIdentity> assignedInput, const std::string &skinRessourceName,
|
||||
sf::Vector2f initCoordinates);
|
||||
|
||||
~Player();
|
||||
GridCoordinates initCoordinates);
|
||||
|
||||
void update() override;
|
||||
|
||||
sf::Vector2f getTrackablePosition() const override;
|
||||
[[nodiscard]] sf::Vector2f getTrackablePosition() const override;
|
||||
|
||||
sf::Vector2f getTrackableSize() const override;
|
||||
[[nodiscard]] sf::Vector2f getTrackableSize() const override;
|
||||
|
||||
TrackableState getTrackableState() const override;
|
||||
[[nodiscard]] TrackableState getTrackableState() const override;
|
||||
|
||||
float speed = DEFAULT_PLAYER_SPEED;
|
||||
|
||||
int getPlayerId() const;
|
||||
[[nodiscard]] sf::Vector2f getIsoSize() const;
|
||||
|
||||
float getWorldRadius() const;
|
||||
[[nodiscard]] int getPlayerId() const;
|
||||
|
||||
[[nodiscard]] float getWorldRadius() const;
|
||||
|
||||
[[nodiscard]] int getPoints() 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;
|
||||
|
||||
TranslatedCoordinates spawnPosition;
|
||||
private:
|
||||
std::shared_ptr<InputIdentity> input;
|
||||
float radiusInWorld = DEFAULT_PLAYER_RADIUS;
|
||||
float radiusInWorld = 0.5f; // In world units
|
||||
std::shared_ptr<VersatileSprite> skinSprite;
|
||||
std::vector<std::shared_ptr<Player>> collidingPlayers;
|
||||
|
||||
sf::Vector2f spawnPosition;
|
||||
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;
|
||||
|
||||
float getIsoRadius() const;
|
||||
void setWorldRadius(float newWorldRadius);
|
||||
|
||||
void updateRadiusBasedOnPoints();
|
||||
|
||||
void performInteractiveUpdates();
|
||||
|
||||
void handlePlayerCollisions();
|
||||
};
|
||||
|
||||
|
||||
|
|
240
src/game/player/player_collection.cpp
Normal file
|
@ -0,0 +1,240 @@
|
|||
#include "player_collection.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include "../input/input_mapper.h"
|
||||
#include "../../texture_config.h"
|
||||
|
||||
PlayerCollection::PlayerCollection(int maxPlayerCount)
|
||||
: maxPlayerCount(maxPlayerCount)
|
||||
{
|
||||
// Set default spawn point
|
||||
setSpawnPoints({{0, 0}});
|
||||
|
||||
// Create player for existing input identities
|
||||
for (auto &inputIdentity: InputMapper::getInstance()->getInputIdentities())
|
||||
{
|
||||
spawnPlayer(inputIdentity, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<PlayerCollection> PlayerCollection::getInstance()
|
||||
{
|
||||
if (singletonInstance == nullptr)
|
||||
{
|
||||
singletonInstance = std::make_shared<PlayerCollection>();
|
||||
}
|
||||
return singletonInstance;
|
||||
}
|
||||
|
||||
void PlayerCollection::addPlayer(const std::shared_ptr<Player> &player)
|
||||
{
|
||||
newPlayerBuffer.push_back(player);
|
||||
addDetachedChild(player);
|
||||
}
|
||||
|
||||
void PlayerCollection::clear()
|
||||
{
|
||||
newPlayerBuffer.clear();
|
||||
removedPlayerBuffer.clear();
|
||||
|
||||
nextSpawnPointIndex = 0;
|
||||
|
||||
// Fill in removed players
|
||||
for (auto &child: getChildren())
|
||||
{
|
||||
auto player = std::dynamic_pointer_cast<Player>(child);
|
||||
if (player != nullptr)
|
||||
{
|
||||
removedPlayerBuffer.push_back(player);
|
||||
}
|
||||
}
|
||||
|
||||
clearChildren();
|
||||
}
|
||||
|
||||
void PlayerCollection::lateUpdate()
|
||||
{
|
||||
GameObject::lateUpdate();
|
||||
|
||||
newPlayerBuffer.clear();
|
||||
removedPlayerBuffer.clear();
|
||||
|
||||
// Create player for new input identities
|
||||
for (auto &inputIdentity: InputMapper::getInstance()->newInputIdentities)
|
||||
{
|
||||
spawnPlayer(inputIdentity);
|
||||
}
|
||||
|
||||
// Remove players, that are not active anymore
|
||||
for (auto &child: getChildren())
|
||||
{
|
||||
if (child != nullptr && !child->getActive())
|
||||
{
|
||||
removedPlayerBuffer.push_back(std::dynamic_pointer_cast<Player>(child));
|
||||
removeChild(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerCollection::removePlayer(const std::shared_ptr<Player> &player)
|
||||
{
|
||||
removedPlayerBuffer.push_back(player);
|
||||
removeChild(player);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Player>> PlayerCollection::getNewPlayers() const
|
||||
{
|
||||
return newPlayerBuffer;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Player>> PlayerCollection::getRemovedPlayers() const
|
||||
{
|
||||
return removedPlayerBuffer;
|
||||
}
|
||||
|
||||
void PlayerCollection::setSpawnPoints(std::vector<GridCoordinates> newSpawnPoints)
|
||||
{
|
||||
this->spawnPoints = std::move(newSpawnPoints);
|
||||
nextSpawnPointIndex = 0;
|
||||
}
|
||||
|
||||
void PlayerCollection::spawnPlayer(const std::shared_ptr<InputIdentity> &inputIdentity, std::string skin)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
63
src/game/player/player_collection.hpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
#ifndef HOLESOME_PLAYER_COLLECTION_HPP
|
||||
#define HOLESOME_PLAYER_COLLECTION_HPP
|
||||
|
||||
|
||||
#include "../game_object.h"
|
||||
#include "player.hpp"
|
||||
|
||||
class PlayerCollection : public GameObject
|
||||
{
|
||||
public:
|
||||
explicit PlayerCollection(int maxPlayerCount = DEFAULT_MAX_PLAYER_NUMBER);
|
||||
|
||||
static std::shared_ptr<PlayerCollection> getInstance();
|
||||
|
||||
void setSpawnPoints(std::vector<GridCoordinates> newSpawnPoints);
|
||||
|
||||
void addPlayer(const std::shared_ptr<Player>& player);
|
||||
|
||||
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>> 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;
|
||||
};
|
||||
|
||||
|
||||
#endif //HOLESOME_PLAYER_COLLECTION_HPP
|
|
@ -1,39 +0,0 @@
|
|||
#include "player_spawner.hpp"
|
||||
#include "player.hpp"
|
||||
#include "../../texture_config.h"
|
||||
#include "../physics/map_simulation.hpp"
|
||||
|
||||
PlayerSpawner::PlayerSpawner(const std::vector<sf::Vector2f> &spawnPoints)
|
||||
{
|
||||
this->spawnPoints = spawnPoints;
|
||||
|
||||
// Create player for existing input identities
|
||||
for (auto &inputIdentity: InputMapper::getInstance()->getInputIdentities())
|
||||
{
|
||||
spawnPlayer(inputIdentity);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerSpawner::update()
|
||||
{
|
||||
// Create player for new input identities
|
||||
for (auto &inputIdentity: InputMapper::getInstance()->newInputIdentities)
|
||||
{
|
||||
spawnPlayer(inputIdentity);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerSpawner::spawnPlayer(const std::shared_ptr<InputIdentity> &inputIdentity)
|
||||
{
|
||||
// Get proper Spawn point, if available
|
||||
sf::Vector2f spawn = spawnPoints[nextSpawnPointIndex];
|
||||
nextSpawnPointIndex = static_cast<int>((nextSpawnPointIndex + 1) % spawnPoints.size());
|
||||
|
||||
auto player = new Player(inputIdentity, PLAYER_SKIN, spawn);
|
||||
|
||||
MapSimulation::getInstance()->addPlayer(player);
|
||||
Game::getInstance()->addGameObject(player);
|
||||
|
||||
// TODO: Better view handling
|
||||
Game::getInstance()->views[0]->addTrackable(player);
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
#ifndef HOLESOME_PLAYER_SPAWNER_HPP
|
||||
#define HOLESOME_PLAYER_SPAWNER_HPP
|
||||
|
||||
|
||||
#include "../game_object.h"
|
||||
#include "../game.h"
|
||||
|
||||
class PlayerSpawner : public GameObject
|
||||
{
|
||||
public:
|
||||
PlayerSpawner(const std::vector<sf::Vector2f>& spawnPoints);
|
||||
|
||||
void update() override;
|
||||
|
||||
void spawnPlayer(const std::shared_ptr<InputIdentity> &inputIdentity);
|
||||
|
||||
private:
|
||||
std::vector<sf::Vector2f> spawnPoints;
|
||||
int nextSpawnPointIndex = 0;
|
||||
};
|
||||
|
||||
|
||||
#endif //HOLESOME_PLAYER_SPAWNER_HPP
|
70
src/game/time/countdown.cpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
#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);
|
||||
}
|
31
src/game/time/countdown.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#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,6 +3,7 @@
|
|||
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "game/input/input_device_group.h"
|
||||
#include "game/input/button_config.hpp"
|
||||
#include "game/input/game_action_config.hpp"
|
||||
|
@ -17,31 +18,68 @@ 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::RControl, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, {GameAction::JUMP})},
|
||||
{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::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::Space, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, {GameAction::JUMP})},
|
||||
{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::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::B, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, {GameAction::JUMP})}
|
||||
{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})}
|
||||
};
|
||||
|
||||
|
||||
// Gamepad buttons
|
||||
const std::map<int, ButtonConfig> GAMEPAD_BUTTON_CONFIGS = {
|
||||
{GamepadButton::SOUTH, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::JUMP})}
|
||||
{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})}
|
||||
};
|
||||
|
||||
|
||||
// Actions
|
||||
const std::map<GameAction, GameActionConfig> GAME_ACTION_CONFIGS = {
|
||||
{GameAction::JUMP, GameActionConfig(InteractionMode::PRESS)}
|
||||
{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"}
|
||||
};
|
||||
|
||||
#endif //HOLESOME_INPUT_CONFIG_H
|
||||
|
|
|
@ -7,8 +7,65 @@
|
|||
|
||||
#define INITIAL_LEVEL "default"
|
||||
|
||||
std::map<std::string, LevelConfig> const LEVELS = {
|
||||
{"default", LevelConfig("Default", {25, 25}, {{1, 1}})}
|
||||
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}
|
||||
})
|
||||
)}
|
||||
};
|
||||
|
||||
#endif //HOLESOME_LEVELS_HPP
|
||||
|
|