From 15c96329413122f7e7eb1c09857861771eabf2ad Mon Sep 17 00:00:00 2001 From: Maximilian Giller Date: Sun, 9 Jul 2023 23:51:34 +0200 Subject: [PATCH] Implemented join screen --- CMakeLists.txt | 2 +- assets/collectables/lantern.png | Bin 0 -> 630 bytes assets/edge.png | Bin 19388 -> 0 bytes assets/grass-tiles.png | Bin 0 -> 3684 bytes assets/player/player-0.png | Bin 0 -> 2496 bytes assets/player/player-1.png | Bin 0 -> 2770 bytes assets/player/player-2.png | Bin 0 -> 2866 bytes assets/player/player-3.png | Bin 0 -> 2890 bytes assets/player/player.png | Bin 0 -> 1464 bytes src/collectables.hpp | 5 +- src/config.h | 3 + src/game/game.cpp | 11 ++ src/game/game.h | 3 + src/game/input/game_action.hpp | 7 +- src/game/level/level_loader.cpp | 5 +- src/game/player/player.cpp | 30 +++++- src/game/player/player.hpp | 12 +++ src/game/player/player_collection.cpp | 68 +++++++++++- src/game/player/player_collection.hpp | 7 ++ src/input_config.h | 57 ++++++---- src/levels.hpp | 2 +- src/main.cpp | 6 +- src/screens/join_screen.cpp | 147 ++++++++++++++++++++++++++ src/screens/join_screen.hpp | 27 +++++ src/screens/winner_screen.cpp | 2 +- src/texture_config.h | 25 +++-- 26 files changed, 369 insertions(+), 50 deletions(-) create mode 100644 assets/collectables/lantern.png delete mode 100644 assets/edge.png create mode 100644 assets/grass-tiles.png create mode 100644 assets/player/player-0.png create mode 100644 assets/player/player-1.png create mode 100644 assets/player/player-2.png create mode 100644 assets/player/player-3.png create mode 100644 assets/player/player.png create mode 100644 src/screens/join_screen.cpp create mode 100644 src/screens/join_screen.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 90cb5fe..669ae61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -138,7 +138,7 @@ set(SOURCES 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/winner_screen.hpp src/screens/screen.cpp src/screens/screen.hpp src/screens/join_screen.cpp src/screens/join_screen.hpp) set(PHYSICS_00_SOURCES src/prototypes/physics_00.cpp) diff --git a/assets/collectables/lantern.png b/assets/collectables/lantern.png new file mode 100644 index 0000000000000000000000000000000000000000..472538a572ff87c48cabef58dfa692ae524377c2 GIT binary patch literal 630 zcmV-+0*U>JP)0004jX+uL$b5ch_ zAW20-HZeIiHZ3wPF#rH4k#&*JD@0Kg$3HV@{A`G1VKHe~&4^JG8;cP`VKGJ-Y-Zm4 zQuE{W-bk`lwo{bY__g3KV5_W^B2uztO>CH+^Jb<-GUs;g`P}cl=XCG6U=5~}v|c*^ z=}b-w_hDPQ#7V4V5t`#Ra7k} zJSjZAmQzjM5$;fu;i&MLuuGE*3g4M}q{Oc#CrbQnvK9=CfwfiGk@&@tU#ujxw6MG! zXF9d2l<#A{q&b$EpOe@l-i=NGKRz5xG0qrX2Bj}v8E!oE%Ze)b1I7GVvXZOFtg?>A z3JH?raEZDq)ngLlqQ_*USU3Csx37A9$WyA`?1Q>z!+7rn+cp&PhVgY|7=>e~d4kJK zB|m!z!ylsOE1L5F?3-|YRna25aIpnlk7`&87o{3;Ydj8br_d6D_8VwiD)p?q&&dM> zH{`3XVQ(M$-+go1$k=ZbLCS;wurlwfg7z*(L z(iIBT@nJ|Ji!mZg;0qoMa7kJD|0QK*jNrm>Bf4*KD#Qh_D#T_g4a0F%;i$qPukiBe z(*tESHUKYMaFx2S@&~Vp$evOBT zto6PB&dl1@U3FF0RkeF|@9v3ER{V$r{{bEV03gXoOQ^mdKfiA_u+Z;&+h+MX000Nz zrLOIwYV1zp;A9WBvN0!d@pLdJG54?n0{|XNh3Psqdc1CuZ>;brKtbftAg*rq^Yb)PkxzluQoks{ofhzt^Ycw=E@FFjES+e*W+w!TZ$lyytmu+@m9C{Os;Y zZSSt_{BB-ff1R6*nUZLFuS2tA{>?-1nPB6J#Y6LzE!GzX{_4#B`k8S33m(lEqIht@ z-V?v_8*<{--q&+Nqv$i$=K2mMO!gwg)bYMEbN4@UN_%N~fCAN4d(U-xWYJa)f~YR0U~ zO)-mB3KPFwxZ(^k+ZSZtMfkNjH=Nbp-_8H{GPV88JUQRt-MHEa)?{^}oVjT!A(rzT zZ;H7bX|Crj()8UnG!DQ0!Y3@H!F*c(a+#2gK^Z_2sd@{7}%UEg>2jVWAQhVwY#S4Y3c0Pk6N z(tG=JT7+G!{J>F;MBbNOHYe0m?vg)H_xJzJ#VyJ|nKG zcW)QNh%c`LVbL0y7NuD@){b%6-tuDqslB8t zuWYcn;dEQE3LB@ADeM

=dxh;3GnDDH%YU9CYZfobyDW-F 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 YFQ?C>NY2NA$N&HU07*qoM6N<$f<3!J=>Px# literal 0 HcmV?d00001 diff --git a/assets/player/player-2.png b/assets/player/player-2.png new file mode 100644 index 0000000000000000000000000000000000000000..cdf9abca784710e62358af8aa464ede0d8948aa9 GIT binary patch literal 2866 zcmV-23(fS2P)CH+^Jb<-GUs;g`P}cl=XCG6U=5~}v|c*^ z=}b-w_hDPQ#7V4V5t`#Ra7k} zJSjZAmQzjM5$;fu;i&MLuuGE*3g4M}q{Oc#CrbQnvK9=CfwfiGk@&@tU#ujxw6MG! zXF9d2l<#A{q&b$EpOe@l-i=NGKRz5xG0qrX2Bj}v8E!oE%Ze)b1I7GVvXZOFtg?>A z3JH?raEZDq)ngLlqQ_*USU3Csx37A9$WyA`?1Q>z!+7rn+cp&PhVgY|7=>e~d4kJK zB|m!z!ylsOE1L5F?3-|YRna25aIpnlk7`&87o{3;Ydj8br_d6D_8VwiD)p?q&&dM> zH{`3XVQ(M$#a$uD+1n=dMPlY@B8H zQOS6y!9`KjHa6D>XE}FKpsp-k5=PtSb7!6$cYPQ#E;OJhipv|D>$k(n9|SlAb`g;0 z?eEBNWvyG````KPxhHvG$D$}A;F}wp>pwX!=&)QA^rMWQSH_L&H{(3d@9XS+ExU+6 z4DiQQ&LZGJq&#;u3Q4$}=lP%S)%Q#*MNzD5Y_1=j2l&z@0hn>HDMu^yo z6benHry1|Wwx2G+Dvg;*s9X}pI{@-P51krrT)+8jp67oGJon&Va`}E1cqethM@l3y zKb<0yfux9}h)%dPjpv%cEmFl}tE-!6*c#V|A!MN!?@Tt5jrI?yhX zm7X&9VkW7q!f40I)R^i`RbbZKuXb~G`vu~;Rv5WNpyc%ZTv0fG1W+h7Wky^G*gi^4 zgMri2&zModa-QcyXX=ev@FoX`TbXBlw3XRNr!008hT9n;sgG`N8nfEzB4VCvNzt;v z_U;K<%BYJZbn+gW#Prb%=3ZxL$xPka=(f9PSwJMA+gU{;`>_WCNK8-x{%OruOb+wXi4eh`HU>{R>wr26isUS zVxr&QAKH8Ktd)06Kgp~zV%n{akD#^^565(MZ0C>4G_b0`j$7;|VAb>9I}0TNV788C z6;&n$Yz`TTCSLa4X@Zc@X?N`8#-ZK4p>fVSw7Wkl+ezscBM-RM>L6r6J-2l;cCQ!( z1ex)>#n6vw=WV}6xIh>Z5=3WyUY8Qi|M5;T6JUos0=Uy3+x&@xT#5rDc3rS}y0bU* z#>|8<+Jh>itL@bQZ6YM>AC%+>F?$DN+b^>}6Wvy`Jwt@+-!F#NUb`gD`dQsg=xPJ- zGo9FjM-snwAol7Mu-2{ET#Fg)xD_;Eyla&}L|4Zw?*?QLljpg0Jho9ZPPUqP;BZO6 z{=vkOlK526ECfo)Xf~Uk$o<3N%=mdS_76@FB45w*+y|3~NbbgS7Y z+f;(6lt!Zg%CsNcos+pv~q;gjTc4;o-oZAKa;N>g%7q_7B#& z6~6n8pTCsn`8zWNSQ0M3`POT<@ASvqx%EZc?4qwMlL}EOAf9Oh(wzHiwG{@(BMa6L zR$r+yIG)(qf&-IM?u-+hgEUBaAgixbI66KF2oaP_OMoKGfDPLmIcV0d{mt79U!IxF zCdxu=qifSb#nfxd+&vzH>!;#U+NwM!V2LoQp;Q3aXo>}F_wQB>B^eBYOP!0mp{9LNdX5iW@TlC zqmz+&7J#i*s|-gIL`77WDh$V;AVeqyOSWz#>j(hQR4Y#~9F0wzI3;LX4}gm~{V6nG zc;z?V$H($oe7-nEgyrvVf8G3JC>4MY(!k1psaji_@%07*qoM6N<$f*&M~vH$=8 literal 0 HcmV?d00001 diff --git a/assets/player/player-3.png b/assets/player/player-3.png new file mode 100644 index 0000000000000000000000000000000000000000..33fdc41070172c480b69925f5ed60298f22b655c GIT binary patch literal 2890 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 ST1f2x0000 const all_collectables = { {"rose", CollectableConfig("rose", 1)}, {"rosebush", CollectableConfig("rosebush", 3)}, {"stone", CollectableConfig("stone", 5)}, - {"bike", CollectableConfig("bike", 10)}, + {"bike", CollectableConfig("bike", 15)}, {"small-tree", CollectableConfig("small-tree", 20)}, - {"tram", CollectableConfig("tram", 50)} + {"tram", CollectableConfig("tram", 50)}, + {"lantern", CollectableConfig("lantern", 10)} }; #endif //HOLESOME_COLLECTABLES_HPP diff --git a/src/config.h b/src/config.h index 912d384..b80cbcc 100644 --- a/src/config.h +++ b/src/config.h @@ -6,12 +6,15 @@ #define DEVELOPER_MODE true +#define GAME_NAME "Holesome" + // Player #define DEFAULT_PLAYER_SPEED 5.f // World units per second #define DEFAULT_PLAYER_POINTS 0 #define PLAYER_MIN_RADIUS 0.5f // World units #define PLAYER_RADIUS_PER_LEVEL 0.25f #define DEFAULT_MAX_PLAYER_NUMBER 4 +#define MIN_PLAYER_COUNT 2 // World #define WORLD_GRAVITY b2Vec2(0.f, -9.8f) diff --git a/src/game/game.cpp b/src/game/game.cpp index 87e4b5c..f8bd729 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -9,6 +9,7 @@ #include "layer/global_layer.hpp" #include "player/player_collection.hpp" #include "../screens/winner_screen.hpp" +#include "../screens/join_screen.hpp" Game::Game(std::shared_ptr window) : window(std::move(window)) { @@ -207,3 +208,13 @@ void Game::handleWinning() GlobalLayer::getInstance()->add(winnerScreen); addGameObject(GlobalLayer::getInstance()); } + +void Game::showJoinScreen() +{ + InputMapper::getInstance()->allowNewInputIdentities = true; + auto joinScreen = std::make_shared(); + GlobalLayer::getInstance()->add(joinScreen); + addGameObject(GlobalLayer::getInstance()); + PlayerCollection::getInstance()->deactivatePlayers(); + addGameObject(PlayerCollection::getInstance()); +} diff --git a/src/game/game.h b/src/game/game.h index 7e7cd1b..35b1568 100644 --- a/src/game/game.h +++ b/src/game/game.h @@ -38,6 +38,9 @@ public: void addGameObject(const std::shared_ptr &gameObject); std::shared_ptr window; + + void showJoinScreen(); + private: static inline std::shared_ptr singletonInstance = nullptr; std::vector> gameObjects = {}; diff --git a/src/game/input/game_action.hpp b/src/game/input/game_action.hpp index 803129c..7896455 100644 --- a/src/game/input/game_action.hpp +++ b/src/game/input/game_action.hpp @@ -3,12 +3,7 @@ enum GameAction { - CONFIRM, - CANCEL, - MENU, - - GROW, - SHRINK + CONFIRM }; #endif //HOLESOME_GAME_ACTION_HPP diff --git a/src/game/level/level_loader.cpp b/src/game/level/level_loader.cpp index c50bc95..e4ed98a 100644 --- a/src/game/level/level_loader.cpp +++ b/src/game/level/level_loader.cpp @@ -21,12 +21,15 @@ void LevelLoader::loadLevel(const LevelConfig &levelConfig) LOG(INFO) << "Loading level '" << levelConfig.name << "' ..."; game->setLevel(levelConfig); + InputMapper::getInstance()->allowNewInputIdentities = false; + PlayerCollection::getInstance()->activatePlayers(); + MapSimulation::getInstance()->resetMap(levelConfig.worldMapSize); HolesSimulation::getInstance()->clear(); - PlayerCollection::getInstance()->clear(); GlobalLayer::getInstance()->clear(); HoleLayout::getInstance()->clear(); + // Add views game->addGameObject(MultiplayerView::getInstance()); diff --git a/src/game/player/player.cpp b/src/game/player/player.cpp index d6b6b12..a280ff2 100644 --- a/src/game/player/player.cpp +++ b/src/game/player/player.cpp @@ -5,7 +5,7 @@ Player::Player(std::shared_ptr assignedInput, const std::string &skinRessourceName, GridCoordinates initCoordinates) - : spawnPosition(initCoordinates) + : spawnPosition(initCoordinates), skinName(skinRessourceName) { playerId = playerCreationCounter++; coordinates->setTranslated(spawnPosition); @@ -37,9 +37,9 @@ void Player::update() return; } - auto moveDirection = input->direction.asIsometricVector(); - auto moveDelta = moveDirection * speed * FRAME_TIME.asSeconds(); - coordinates->move(moveDelta); + if (!passiveMode) { + performInteractiveUpdates(); + } GameObject::update(); } @@ -103,3 +103,25 @@ void Player::consume(int points) updateRadiusBasedOnPoints(); } + +std::string Player::getSkinName() const +{ + return skinName; +} + +std::shared_ptr Player::getInput() const +{ + return input; +} + +void Player::setPassiveMode(bool newPassiveMode) +{ + passiveMode = newPassiveMode; +} + +void Player::performInteractiveUpdates() +{ + auto moveDirection = input->direction.asIsometricVector(); + auto moveDelta = moveDirection * speed * FRAME_TIME.asSeconds(); + coordinates->move(moveDelta); +} diff --git a/src/game/player/player.hpp b/src/game/player/player.hpp index 331f060..232e4ba 100644 --- a/src/game/player/player.hpp +++ b/src/game/player/player.hpp @@ -33,12 +33,22 @@ public: void consume(int points); + std::string getSkinName() const; + + std::shared_ptr getInput() const; + + void setPassiveMode(bool newPassiveMode); + TranslatedCoordinates spawnPosition; private: std::shared_ptr input; float radiusInWorld = 0.5f; // In world units std::shared_ptr skinSprite; + bool passiveMode = true; + + std::string skinName; + long points = DEFAULT_PLAYER_POINTS; int playerId; @@ -47,6 +57,8 @@ private: void setWorldRadius(float newWorldRadius); void updateRadiusBasedOnPoints(); + + void performInteractiveUpdates(); }; diff --git a/src/game/player/player_collection.cpp b/src/game/player/player_collection.cpp index 08d87e8..39f75be 100644 --- a/src/game/player/player_collection.cpp +++ b/src/game/player/player_collection.cpp @@ -35,6 +35,8 @@ void PlayerCollection::addPlayer(const std::shared_ptr &player) void PlayerCollection::clear() { + takenSkinIndices.clear(); + newPlayerBuffer.clear(); removedPlayerBuffer.clear(); @@ -97,6 +99,16 @@ void PlayerCollection::setSpawnPoints(std::vector newSpawnPoint { this->spawnPoints = std::move(newSpawnPoints); nextSpawnPointIndex = 0; + + // Go through all players and update their spawn points + for (auto &player: getPlayers()) + { + auto spawn = spawnPoints[nextSpawnPointIndex]; + nextSpawnPointIndex = static_cast((nextSpawnPointIndex + 1) % spawnPoints.size()); + + player->spawnPosition = TranslatedCoordinates(spawn); + player->coordinates->setGrid(spawn); + } } void PlayerCollection::spawnPlayer(const std::shared_ptr &inputIdentity) @@ -105,7 +117,7 @@ void PlayerCollection::spawnPlayer(const std::shared_ptr &inputId auto spawn = spawnPoints[nextSpawnPointIndex]; nextSpawnPointIndex = static_cast((nextSpawnPointIndex + 1) % spawnPoints.size()); - auto player = std::make_shared(inputIdentity, PLAYER_SKIN, spawn); + auto player = std::make_shared(inputIdentity, getNextSkin(), spawn); addPlayer(player); } @@ -145,7 +157,7 @@ void PlayerCollection::updateInputIdentityAllowance() const InputMapper::getInstance()->allowNewInputIdentities = getPlayers().size() < maxPlayerCount; } -std::shared_ptr PlayerCollection::getClosestPlayer(const TranslatedCoordinates& point) const +std::shared_ptr PlayerCollection::getClosestPlayer(const TranslatedCoordinates &point) const { std::shared_ptr closestPlayer = nullptr; float closestDistance = INFINITY; @@ -163,3 +175,55 @@ std::shared_ptr PlayerCollection::getClosestPlayer(const TranslatedCoord } return closestPlayer; } + +std::string PlayerCollection::getNextSkin() +{ + int numberOfSkins = static_cast(PLAYER_SKINS.size()); + + // Count how often each skin index is taken + std::map 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); + newPlayerBuffer.push_back(player); + } +} + +void PlayerCollection::deactivatePlayers() +{ + for (auto player: getPlayers()) + { + player->setPassiveMode(true); + } +} diff --git a/src/game/player/player_collection.hpp b/src/game/player/player_collection.hpp index 04aed42..fd21d53 100644 --- a/src/game/player/player_collection.hpp +++ b/src/game/player/player_collection.hpp @@ -34,6 +34,9 @@ public: void clear(); + void activatePlayers(); + void deactivatePlayers(); + private: static inline std::shared_ptr singletonInstance = nullptr; @@ -45,6 +48,10 @@ private: void spawnPlayer(const std::shared_ptr &inputIdentity); + std::vector takenSkinIndices = {}; + + std::string getNextSkin(); + int maxPlayerCount; void updateInputIdentityAllowance() const; diff --git a/src/input_config.h b/src/input_config.h index 92d0215..38a6986 100644 --- a/src/input_config.h +++ b/src/input_config.h @@ -3,6 +3,7 @@ #include +#include #include "game/input/input_device_group.h" #include "game/input/button_config.hpp" #include "game/input/game_action_config.hpp" @@ -13,40 +14,52 @@ // Keys const std::map KEY_CONFIGS = { - {sf::Keyboard::Up, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, HardDirection::UP)}, - {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::GROW})}, - {sf::Keyboard::Numpad0, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, {GameAction::SHRINK})}, + {sf::Keyboard::Up, ButtonConfig(InputDeviceGroup::KEYBOARD_ARROWS, HardDirection::UP)}, + {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::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::GROW})}, - {sf::Keyboard::Q, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, {GameAction::SHRINK})}, + {sf::Keyboard::W, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, HardDirection::UP)}, + {sf::Keyboard::S, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, HardDirection::DOWN)}, + {sf::Keyboard::A, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, HardDirection::LEFT)}, + {sf::Keyboard::D, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, HardDirection::RIGHT)}, + {sf::Keyboard::Q, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, {GameAction::CONFIRM})}, + {sf::Keyboard::E, ButtonConfig(InputDeviceGroup::KEYBOARD_WASD, {GameAction::CONFIRM})}, - {sf::Keyboard::I, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::UP)}, - {sf::Keyboard::K, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::DOWN)}, - {sf::Keyboard::J, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::LEFT)}, - {sf::Keyboard::L, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::RIGHT)}, - {sf::Keyboard::U, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, {GameAction::GROW})}, - {sf::Keyboard::O, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, {GameAction::SHRINK})} + {sf::Keyboard::I, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::UP)}, + {sf::Keyboard::K, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::DOWN)}, + {sf::Keyboard::J, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::LEFT)}, + {sf::Keyboard::L, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, HardDirection::RIGHT)}, + {sf::Keyboard::U, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, {GameAction::CONFIRM})}, + {sf::Keyboard::O, ButtonConfig(InputDeviceGroup::KEYBOARD_IJKL, {GameAction::CONFIRM})} }; // Gamepad buttons const std::map GAMEPAD_BUTTON_CONFIGS = { - {GamepadButton::EAST, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::GROW})}, - {GamepadButton::SOUTH, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::SHRINK})} + {GamepadButton::EAST, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::CONFIRM})}, + {GamepadButton::SOUTH, ButtonConfig(InputDeviceGroup::GAMEPAD, {GameAction::CONFIRM})} }; // Actions const std::map GAME_ACTION_CONFIGS = { - {GameAction::GROW, GameActionConfig(InteractionMode::HOLD)}, - {GameAction::SHRINK, GameActionConfig(InteractionMode::HOLD)} + {GameAction::CONFIRM, GameActionConfig(InteractionMode::PRESS)} +}; + + +const std::map DEVICE_GROUP_NAMES = { + {InputDeviceGroup::KEYBOARD_WASD, "WASD"}, + {InputDeviceGroup::KEYBOARD_IJKL, "IJKL"}, + {InputDeviceGroup::KEYBOARD_ARROWS, "Arrow keys"}, + {InputDeviceGroup::GAMEPAD, "Gamepad"} +}; +const std::map DEVICE_GROUP_CONFIRM = { + {InputDeviceGroup::KEYBOARD_WASD, "Q or E"}, + {InputDeviceGroup::KEYBOARD_IJKL, "U or O"}, + {InputDeviceGroup::KEYBOARD_ARROWS, "Space"}, + {InputDeviceGroup::GAMEPAD, "A or B"} }; #endif //HOLESOME_INPUT_CONFIG_H diff --git a/src/levels.hpp b/src/levels.hpp index a4c97ab..1a71fbb 100644 --- a/src/levels.hpp +++ b/src/levels.hpp @@ -23,7 +23,7 @@ std::map const all_levels = { CollectableInLevel("rose", {1, 2}), CollectableInLevel("small-tree", {4, 3}), CollectableInLevel("rose", {8, 3}), - CollectableInLevel("rose", {6, 7}), + CollectableInLevel("lantern", {6, 7}), CollectableInLevel("rose", {5, 5}), CollectableInLevel("tram", {9, 5}), CollectableInLevel("rose", {0, 1}) diff --git a/src/main.cpp b/src/main.cpp index 607399e..49f1e13 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,11 +36,9 @@ void loadAllFonts() void runGame() { LOG(INFO) << "Starting game ..."; - auto game = GameFactory::createWindowed("Holesome"); - - // Load initial level - LevelLoader::loadLevel(INITIAL_LEVEL); + auto game = GameFactory::createWindowed(GAME_NAME); + game->showJoinScreen(); game->run(); InputMapper::getInstance().reset(); diff --git a/src/screens/join_screen.cpp b/src/screens/join_screen.cpp new file mode 100644 index 0000000..5a6a368 --- /dev/null +++ b/src/screens/join_screen.cpp @@ -0,0 +1,147 @@ +#include "join_screen.hpp" +#include "../config.h" +#include "../typography/font_manager.hpp" +#include "../game/player/player_collection.hpp" +#include "../levels.hpp" +#include "../game/level/level_loader.hpp" + +JoinScreen::JoinScreen() +{ + // Title text + auto titleText = std::make_shared(); + titleText->setString(GAME_NAME); + titleText->setFont(*FontManager::getInstance()->getDefaultFont()); + titleText->setCharacterSize(150); + titleText->setStyle(sf::Text::Bold); + titleText->setFillColor(sf::Color::White); + titleText->setPosition(REFERENCE_SIZE.x / 2.f - titleText->getGlobalBounds().width / 2.f, 20); + add(titleText); + + // Create join-player text + auto joinText = std::make_shared(); + joinText->setString("Press button to join"); + joinText->setFont(*FontManager::getInstance()->getDefaultFont()); + joinText->setCharacterSize(24); + joinText->setFillColor(sf::Color::White); + joinText->setPosition(REFERENCE_SIZE.x / 2.f - joinText->getGlobalBounds().width / 2.f, 200); + add(joinText); +} + +void JoinScreen::update() +{ + Screen::update(); + + // Did any player start game? + auto players = PlayerCollection::getInstance()->getPlayers(); + for (auto &player: players) + { + auto input = player->getInput(); + if (input->isPerformingAction(GameAction::CONFIRM)) + { + // Start game + LevelLoader::loadLevel(INITIAL_LEVEL); + return; + } + } + + + // Update player list items + if (players.size() == lastPlayerCount) + { + return; + } + lastPlayerCount = players.size(); + + playerList.clear(); + playerListObjects.clear(); + int index = 0; + for (auto &player: players) + { + addPlayerAtIndex(player, index); + index++; + } +} + +void JoinScreen::draw(sf::RenderWindow *window) +{ + Screen::draw(window); + + // Draw player list items + for (auto &item: playerList) + { + window->draw(*item); + } + + for (auto &item: playerListObjects) + { + item->draw(window); + } +} + +void JoinScreen::addPlayerAtIndex(std::shared_ptr &player, int index) +{ + int numberOfMaxPlayers = PlayerCollection::getInstance()->getMaxPlayerCount(); + int paddingLeftRight = 100; + int paddingTop = 400; + int relativePaddingPerColumn = 0.1; + int columnWidth = (REFERENCE_SIZE.x - 2 * paddingLeftRight) / numberOfMaxPlayers; + int paddingPerColumn = columnWidth * relativePaddingPerColumn; + int contentWidth = columnWidth - paddingPerColumn; + auto topLeft = sf::Vector2f(paddingLeftRight + columnWidth * index, + paddingTop); + + // Create player character + auto skinSprite = std::make_shared(player->getSkinName(), + sf::Vector2f(contentWidth, contentWidth * ISOMETRIC_SKEW) * + 0.9f); + skinSprite->coordinates->setIsometric(IsometricCoordinates(topLeft)); + playerListObjects.push_back(skinSprite); + skinSprite->update(); + skinSprite->lateUpdate(); + skinSprite->preRenderUpdate(); + + // Show device group + auto deviceGroupText = std::make_shared(); + InputDeviceGroup deviceGroup = player->getInput()->deviceGroup; + deviceGroupText->setString(DEVICE_GROUP_NAMES.at(deviceGroup)); + deviceGroupText->setFont(*FontManager::getInstance()->getDefaultFont()); + deviceGroupText->setCharacterSize(24); + deviceGroupText->setFillColor(sf::Color::White); + deviceGroupText->setPosition(topLeft.x + contentWidth / 2.f * 0.9f - deviceGroupText->getGlobalBounds().width / 2.f, + topLeft.y + skinSprite->getSize().y + 20); + playerList.push_back(deviceGroupText); + + if (lastPlayerCount < MIN_PLAYER_COUNT) + { + return; + } + + // Show button to press to start + auto pressText = std::make_shared(); + pressText->setString("Press"); + pressText->setFont(*FontManager::getInstance()->getDefaultFont()); + pressText->setCharacterSize(24); + pressText->setFillColor(sf::Color::Yellow); + pressText->setPosition(topLeft.x + contentWidth / 2.f * 0.9f - pressText->getGlobalBounds().width / 2.f, + topLeft.y + skinSprite->getSize().y + 100); + playerList.push_back(pressText); + + std::string buttonToStart = DEVICE_GROUP_CONFIRM.at(deviceGroup); + auto buttonText = std::make_shared(); + buttonText->setString(buttonToStart); + buttonText->setFont(*FontManager::getInstance()->getDefaultFont()); + buttonText->setCharacterSize(24); + buttonText->setFillColor(sf::Color::Yellow); + buttonText->setPosition(topLeft.x + contentWidth / 2.f * 0.9f - buttonText->getGlobalBounds().width / 2.f, + topLeft.y + skinSprite->getSize().y + 130); + playerList.push_back(buttonText); + + auto toStartText = std::make_shared(); + toStartText->setString("to start"); + toStartText->setFont(*FontManager::getInstance()->getDefaultFont()); + toStartText->setCharacterSize(24); + toStartText->setFillColor(sf::Color::Yellow); + toStartText->setPosition(topLeft.x + contentWidth / 2.f * 0.9f - toStartText->getGlobalBounds().width / 2.f, + topLeft.y + skinSprite->getSize().y + 160); + playerList.push_back(toStartText); +} diff --git a/src/screens/join_screen.hpp b/src/screens/join_screen.hpp new file mode 100644 index 0000000..4caad06 --- /dev/null +++ b/src/screens/join_screen.hpp @@ -0,0 +1,27 @@ +#ifndef HOLESOME_JOIN_SCREEN_HPP +#define HOLESOME_JOIN_SCREEN_HPP + + +#include "screen.hpp" +#include "../game/player/player.hpp" +#include + +class JoinScreen : public Screen +{ +public: + JoinScreen(); + + void update() override; + + void draw(sf::RenderWindow *window) override; + +private: + int lastPlayerCount = 0; + std::vector> playerList = {}; + std::vector> playerListObjects = {}; + + void addPlayerAtIndex(std::shared_ptr &player, int index); +}; + + +#endif //HOLESOME_JOIN_SCREEN_HPP diff --git a/src/screens/winner_screen.cpp b/src/screens/winner_screen.cpp index 52528c3..7f00710 100644 --- a/src/screens/winner_screen.cpp +++ b/src/screens/winner_screen.cpp @@ -17,7 +17,7 @@ WinnerScreen::WinnerScreen(const std::shared_ptr &winner) add(winnerTitle); // Add graphic of the winner - auto winnerGraphic = std::make_shared(PLAYER_SKIN); + auto winnerGraphic = std::make_shared(winner->getSkinName(), sf::Vector2f(500, 500 * ISOMETRIC_SKEW)); auto center = REFERENCE_SIZE / 2.f; auto winnerPosition = center - winnerGraphic->getSize() / 2.f; winnerGraphic->coordinates->setIsometric(IsometricCoordinates(winnerPosition)); diff --git a/src/texture_config.h b/src/texture_config.h index 0b8ee3a..5630699 100644 --- a/src/texture_config.h +++ b/src/texture_config.h @@ -9,7 +9,7 @@ #include "sprites/tiling/tileset_config.hpp" #include "sprites/configs/masked_sprite_config.hpp" -#define PLAYER_SKIN "hole" +#define PLAYER_SKINS std::vector { "player-0", "player-1", "player-2", "player-3" } /** * All textures used in the game. @@ -18,7 +18,6 @@ std::map const all_textures = { {"numbers", "assets/numbers.png"}, {"64", "assets/64.png"}, - {"edge", "assets/edge.png"}, {"ring", "assets/ring.png"}, {"grasses", "assets/grass_plus.png"}, {"hole", "assets/hole.png"}, @@ -28,7 +27,14 @@ std::map const all_textures = { {"small-tree", "assets/collectables/small-tree.png"}, {"stone", "assets/collectables/stone.png"}, {"tram", "assets/collectables/tram.png"}, - {"iso-tiles", "assets/isometric-tiles.png"} + {"lantern", "assets/collectables/lantern.png"}, + {"player", "assets/player/player.png"}, + {"player-0", "assets/player/player-0.png"}, + {"player-1", "assets/player/player-1.png"}, + {"player-2", "assets/player/player-2.png"}, + {"player-3", "assets/player/player-3.png"}, + {"iso-tiles", "assets/isometric-tiles.png"}, + {"custom-grass-tiles", "assets/grass-tiles.png"} }; /** @@ -38,7 +44,8 @@ std::map const all_textures = { std::map const all_sheets = { {"numbers", SheetConfig("numbers", 4, 2)}, {"grasses", SheetConfig("grasses", 25, 14)}, - {"iso-tiles", SheetConfig("iso-tiles", 6, 2)} + {"iso-tiles", SheetConfig("iso-tiles", 6, 2)}, + {"custom-grass-tiles", SheetConfig("custom-grass-tiles", 6, 2)} }; /** @@ -57,7 +64,12 @@ std::map const all_sprites = { {"64", SpriteConfig("64")}, {"edge", SpriteConfig("edge")}, {"ring", SpriteConfig("ring")}, - {"hole", SpriteConfig("hole")} + {"hole", SpriteConfig("hole")}, + {"player", SpriteConfig("player")}, + {"player-0", SpriteConfig("player-0")}, + {"player-1", SpriteConfig("player-1")}, + {"player-2", SpriteConfig("player-2")}, + {"player-3", SpriteConfig("player-3")} }; /** @@ -70,6 +82,7 @@ std::map const all_masked_sprites = { {"rosebush", MaskedSpriteConfig("rosebush")}, {"stone", MaskedSpriteConfig("stone")}, {"tram", MaskedSpriteConfig("tram")}, + {"lantern", MaskedSpriteConfig("lantern")}, {"small-tree", MaskedSpriteConfig("small-tree")} }; @@ -78,7 +91,7 @@ std::map const all_masked_sprites = { * The key is the name of the tileset, the value is the tileset config. */ std::map const all_tilesets = { - {"iso-tiles", TileSetConfig("iso-tiles", {0, 1, 2, 3, 4, 5})} + {"iso-tiles", TileSetConfig("custom-grass-tiles", {0, 1, 2, 3, 4, 5})} }; #endif //HOLESOME_TEXTURE_CONFIG_H