Compare commits

...

55 commits

Author SHA1 Message Date
28d2a9da91 Teaser videos 2023-07-11 02:38:08 +02:00
7cfe033668 Fullscreen by default 2023-07-11 02:38:02 +02:00
c912784a7d Fixed skin assignment 2023-07-11 02:25:31 +02:00
95562a9da7 Fixed attacks 2023-07-10 23:05:14 +02:00
ac435f8e4f Attack refined 2023-07-10 22:34:04 +02:00
36aa2fa245 Fixed player attacks 2023-07-10 21:54:35 +02:00
851e591c3c Implemented procedural world generation 2023-07-10 01:46:58 +02:00
f50a346e22 Fixed sprites getting stuck 2023-07-10 01:20:22 +02:00
7703128749 Fixed game flow from winning to restarting 2023-07-10 00:18:38 +02:00
15c9632941 Implemented join screen 2023-07-09 23:51:34 +02:00
24cc3cfd6b Implemented basic winner screen 2023-07-09 21:56:14 +02:00
241d8119ce Added consumption and countdown 2023-07-09 21:24:50 +02:00
b0a83a90ef Fixed collectable sizes 2023-07-09 13:10:18 +02:00
364f4c6423 Fixed occlusion 2023-07-09 04:06:29 +02:00
c49d937e20 A bunch of progress on collectable occlusion 2023-07-09 03:43:47 +02:00
Maximilian Giller
d009ef328c Some small perfomance improvements in regards to collectables 2023-07-06 01:16:47 +02:00
Maximilian Giller
16ddfaa942 Small improvement 2023-07-06 00:21:41 +02:00
Maximilian Giller
e8c9a14199 Implemented better relative sizing for tracking view 2023-07-05 15:22:20 +02:00
Maximilian Giller
b4eeb90ce9 Implemented handling for (usually) invalid state when creating ground 2023-07-05 15:21:45 +02:00
Maximilian Giller
762b163655 YES BABY! HOLESSSSSSSS~ Fixed the realised that the coordinate system for the simulation cannot be skewed. Now keeping the proper proportions in diagonal world coordinates 2023-07-05 11:36:28 +02:00
b457c33a72 FIRST WORKING HOLES!!!! WOW! 2023-07-05 01:06:57 +02:00
a5ceecd2a4 Implemented ground framework for collectables simulation 2023-07-03 01:16:44 +02:00
949412df8a Fixed diagonal world coordinates 2023-07-02 04:22:03 +02:00
1c98c75cf9 Removed temporary testing setup 2023-07-01 20:15:33 +02:00
e8319fd6b9 Implemented rotation for sprites, among other things 2023-07-01 20:06:53 +02:00
24963b2d0a Fixed bug in calculating sprite offset for collectables 2023-06-30 20:57:28 +02:00
23fb9d4fd5 Frame limit flag added to config 2023-06-30 20:38:41 +02:00
b5d7dfede9 Fixed and expanded coordinate system, and some more physics WIP 2023-06-29 21:26:30 +02:00
Maximilian Giller
ad5a6731a3 Sync commit 2023-06-28 16:57:13 +02:00
8034b9164d Sync commit 2023-06-28 01:13:10 +02:00
Maximilian Giller
cf6ab330c6 Added multiple spawn points to level 2023-06-22 23:33:45 +02:00
Maximilian Giller
a309976423 Removed unused files 2023-06-22 23:25:32 +02:00
Maximilian Giller
784ac3ba4a Implemented flag to block new players from joining 2023-06-22 23:25:15 +02:00
Maximilian Giller
6f49fecab4 Fixed aspect ratio on split screen views 2023-06-22 23:14:16 +02:00
Maximilian Giller
77dc2f0ceb Implemented border for splitscreen view 2023-06-22 23:01:49 +02:00
Maximilian Giller
4ecc53abb3 TODOS 2023-06-21 20:43:36 +02:00
Maximilian Giller
26561f5d13 Multiplayer viewports rendering now 2023-06-21 20:42:42 +02:00
Maximilian Giller
1aae9e6cb6 Basic multiplayer view scaffolding 2023-06-21 16:05:11 +02:00
62f69a7593 Added todos 2023-06-20 22:19:29 +02:00
b36fec6b3b Added todos 2023-06-20 22:18:35 +02:00
4a44a41c80 Implemented gradient sky 2023-06-20 21:54:59 +02:00
06a6b1b029 Basic skymap prototype 2023-06-18 17:20:35 +02:00
66cc4fcb5f Hiding debug render elements 2023-06-17 21:25:33 +02:00
aa288edd69 Implemented frame counter 2023-06-17 20:47:09 +02:00
27b6e1b324 First tile map properly working 2023-06-17 19:48:51 +02:00
120fdb0a88 Merge remote-tracking branch 'origin/feature-tiling' into feature-tiling
# Conflicts:
#	CMakeLists.txt
#	src/game/level/level_config.hpp
#	src/levels.hpp
#	src/texture_config.h
2023-06-15 23:25:56 +02:00
207a31d3d0 Initial attempts 2023-06-15 23:24:09 +02:00
a9bcdaeb63 Very minor progress on tiling 2023-06-15 19:00:55 +02:00
414f3b79fc Improved map wallout, fixed spawned player in wall glitch and now actually rendering collectables. Player handling has also been reworked 2023-06-13 21:59:50 +02:00
d3e6e35c9b Fixed exception thats thrown on destruction of game. Made game objects shared pointers 2023-06-13 19:04:38 +02:00
88f19ae5e4 Added Collectables collections and other scaffolding required for further development 2023-06-12 21:02:04 +02:00
12b73c00ba Properly implemented grid coordinates 2023-06-11 15:54:05 +02:00
4b96f4f9be Made player radius dynamic 2023-06-11 15:18:22 +02:00
74ca505b60 Player has proper size and a better placeholder graphic 2023-06-11 14:03:48 +02:00
7934d623da Initial attempts 2023-06-11 13:24:27 +02:00
142 changed files with 5256 additions and 560 deletions

1
.gitignore vendored
View file

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

View file

@ -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

View file

@ -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
View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 849 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

BIN
assets/fonts/pixel.ttf Normal file

Binary file not shown.

BIN
assets/grass-tiles.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
assets/grass_plus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
assets/hole.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

BIN
assets/isometric-tiles.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

BIN
assets/player/player-0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
assets/player/player-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
assets/player/player-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
assets/player/player-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
assets/player/player.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

18
src/collectables.hpp Normal file
View 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

View file

@ -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

View file

@ -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

View file

@ -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);
};

View file

@ -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))
{}
};

View file

@ -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);
}

View file

@ -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;
};

View file

@ -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));
}
}

View 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;
}

View 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

View 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();
}

View 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

View file

@ -1,13 +1,13 @@
#include "tracking_view.h"
#include "../../utilities/vector_utils.hpp"
#include "../player/player_collection.hpp"
TrackingView::TrackingView(TrackingViewOptions options) : options(options),
view(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);
}

View file

@ -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;
};

View file

@ -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

View 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();
}

View 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

View 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

View 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;
}

View 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

View 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

View 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);
}
}
}

View 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

View file

@ -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);
}

View file

@ -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

View file

@ -1,11 +0,0 @@
#include "environment_collectable.hpp"
EnvironmentCollectable::EnvironmentCollectable(GridCoordinates position)
{
coordinates->set(position);
}
void EnvironmentCollectable::draw(sf::RenderWindow *window)
{
}

View file

@ -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

View 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;
}

View 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

View file

@ -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();
}

View file

@ -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...");
};

View file

@ -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);
}

View file

@ -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();
}
}

View file

@ -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;

View file

@ -4,10 +4,7 @@
enum GameAction
{
CONFIRM,
CANCEL,
MENU,
JUMP
RUN
};
#endif //HOLESOME_GAME_ACTION_HPP

View file

@ -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;
}

View file

@ -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;

View file

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

View file

@ -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

View 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);
}

View 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

View file

@ -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();
}
};

View 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;
}

View 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

View file

@ -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.";
}

View file

@ -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);
};

View 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);
}

View 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

View 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);
}

View 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

View 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);
}

View 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

View 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;
}

View 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

View file

@ -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

View 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;
}
}
}

View 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

View 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

View 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

View 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

View 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};
}

View 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

View 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();
}

View 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

View 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);
}

View file

@ -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);
};

View file

@ -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

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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();
};

View 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;
}

View 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

View file

@ -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);
}

View file

@ -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

View 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);
}

View 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

View file

@ -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

View file

@ -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

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