Started working on this 'arena tester' tool that would let me load an enemy and test them, but then realized I could just make it so I can spawn enemies in the game. I'm keeping the arena around as it will be useful later as a scriptable testing tool, but for now just spawn and test.
parent
b6c1eba1b3
commit
4f090159ab
@ -0,0 +1,45 @@ |
|||||||
|
#include "arena_fsm.hpp" |
||||||
|
#include "textures.hpp" |
||||||
|
#include "sound.hpp" |
||||||
|
#include "ai.hpp" |
||||||
|
#include "animation.hpp" |
||||||
|
#include <iostream> |
||||||
|
|
||||||
|
int main(int argc, char* argv[]) { |
||||||
|
try { |
||||||
|
dbc::check(argc == 2, "USAGE: arena enemy_name"); |
||||||
|
std::string enemy_name{argv[1]}; |
||||||
|
|
||||||
|
textures::init(); |
||||||
|
sound::init(); |
||||||
|
ai::init("assets/ai.json"); |
||||||
|
animation::init(); |
||||||
|
|
||||||
|
sound::mute(false); |
||||||
|
sound::play("ambient_1", true); |
||||||
|
|
||||||
|
arena::FSM main(enemy_name); |
||||||
|
|
||||||
|
main.event(arena::Event::STARTED); |
||||||
|
|
||||||
|
while(main.active()) { |
||||||
|
main.render(); |
||||||
|
|
||||||
|
// ZED: need to sort out how to deal with this in the FSM
|
||||||
|
if(main.in_state(arena::State::IDLE)) { |
||||||
|
main.event(arena::Event::TICK); |
||||||
|
} |
||||||
|
|
||||||
|
main.keyboard_mouse(); |
||||||
|
|
||||||
|
main.handle_world_events(); |
||||||
|
} |
||||||
|
|
||||||
|
return 0; |
||||||
|
} catch(const std::system_error& e) { |
||||||
|
std::cout << "WARNING: On OSX you'll get this error on shutdown.\n"; |
||||||
|
std::cout << "Caught system_error with code " |
||||||
|
"[" << e.code() << "] meaning " |
||||||
|
"[" << e.what() << "]\n"; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,120 @@ |
|||||||
|
#include "gui_fsm.hpp" |
||||||
|
#include <iostream> |
||||||
|
#include <chrono> |
||||||
|
#include <numeric> |
||||||
|
#include <functional> |
||||||
|
#include "components.hpp" |
||||||
|
#include <numbers> |
||||||
|
#include "systems.hpp" |
||||||
|
#include "events.hpp" |
||||||
|
#include "sound.hpp" |
||||||
|
#include <fmt/xchar.h> |
||||||
|
#include "arena_fsm.hpp" |
||||||
|
|
||||||
|
namespace arena { |
||||||
|
using namespace components; |
||||||
|
|
||||||
|
FSM::FSM(std::string enemy_name) : |
||||||
|
$enemy_name(enemy_name), |
||||||
|
$window(sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Arena Battle Tester"), |
||||||
|
$font{FONT_FILE_NAME} |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
void FSM::event(Event ev) { |
||||||
|
switch($state) { |
||||||
|
FSM_STATE(State, START, ev); |
||||||
|
FSM_STATE(State, IDLE, ev); |
||||||
|
FSM_STATE(State, END, ev); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void FSM::START(Event ) { |
||||||
|
run_systems(); |
||||||
|
$level = $level_mgr.current(); |
||||||
|
|
||||||
|
auto entity_id = $level_mgr.spawn_enemy($enemy_name); |
||||||
|
|
||||||
|
$arena_ui = make_shared<ArenaUI>($level.world, entity_id); |
||||||
|
$arena_ui->init(); |
||||||
|
state(State::IDLE); |
||||||
|
} |
||||||
|
|
||||||
|
void FSM::END(Event ev) { |
||||||
|
dbc::log(fmt::format("END: received event after done: {}", int(ev))); |
||||||
|
} |
||||||
|
|
||||||
|
void FSM::IDLE(Event ev) { |
||||||
|
using enum Event; |
||||||
|
|
||||||
|
switch(ev) { |
||||||
|
case QUIT: |
||||||
|
$window.close(); |
||||||
|
state(State::END); |
||||||
|
return; // done
|
||||||
|
case CLOSE: |
||||||
|
dbc::log("Nothing to close."); |
||||||
|
break; |
||||||
|
case TICK: |
||||||
|
// do nothing
|
||||||
|
break; |
||||||
|
case ATTACK: |
||||||
|
dbc::log("ATTACK!"); |
||||||
|
break; |
||||||
|
default: |
||||||
|
dbc::sentinel("unhandled event in IDLE"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void FSM::keyboard_mouse() { |
||||||
|
while(const auto ev = $window.pollEvent()) { |
||||||
|
if(ev->is<sf::Event::Closed>()) { |
||||||
|
event(Event::QUIT); |
||||||
|
} |
||||||
|
|
||||||
|
if(const auto* mouse = ev->getIf<sf::Event::MouseButtonPressed>()) { |
||||||
|
if(mouse->button == sf::Mouse::Button::Left) { |
||||||
|
sf::Vector2f pos = $window.mapPixelToCoords(mouse->position); |
||||||
|
(void)pos; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if(const auto* key = ev->getIf<sf::Event::KeyPressed>()) { |
||||||
|
using KEY = sf::Keyboard::Scan; |
||||||
|
|
||||||
|
switch(key->scancode) { |
||||||
|
case KEY::Escape: |
||||||
|
event(Event::CLOSE); |
||||||
|
break; |
||||||
|
case KEY::Space: |
||||||
|
event(Event::ATTACK); |
||||||
|
break; |
||||||
|
default: |
||||||
|
break; // ignored
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void FSM::draw_gui() { |
||||||
|
if($arena_ui != nullptr) { |
||||||
|
$arena_ui->render($window); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void FSM::render() { |
||||||
|
$window.clear(); |
||||||
|
draw_gui(); |
||||||
|
$window.display(); |
||||||
|
} |
||||||
|
|
||||||
|
void FSM::run_systems() { |
||||||
|
} |
||||||
|
|
||||||
|
bool FSM::active() { |
||||||
|
return !in_state(State::END); |
||||||
|
} |
||||||
|
|
||||||
|
void FSM::handle_world_events() { |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
#pragma once |
||||||
|
#include "constants.hpp" |
||||||
|
#include "stats.hpp" |
||||||
|
#include "levelmanager.hpp" |
||||||
|
#include "fsm.hpp" |
||||||
|
#include "main_ui.hpp" |
||||||
|
#include "combat_ui.hpp" |
||||||
|
#include "status_ui.hpp" |
||||||
|
#include "arena_ui.hpp" |
||||||
|
#include "map_view.hpp" |
||||||
|
#include "mini_map.hpp" |
||||||
|
|
||||||
|
namespace arena { |
||||||
|
enum class State { |
||||||
|
START, |
||||||
|
IDLE, |
||||||
|
END |
||||||
|
}; |
||||||
|
|
||||||
|
enum class Event { |
||||||
|
STARTED=0, |
||||||
|
TICK=1, |
||||||
|
CLOSE = 7, |
||||||
|
ATTACK = 10, |
||||||
|
QUIT = 14 |
||||||
|
}; |
||||||
|
|
||||||
|
class FSM : public DeadSimpleFSM<State, Event> { |
||||||
|
public: |
||||||
|
std::string $enemy_name; |
||||||
|
sf::RenderWindow $window; |
||||||
|
sf::Font $font; |
||||||
|
LevelManager $level_mgr; |
||||||
|
GameLevel $level; |
||||||
|
shared_ptr<arena::ArenaUI> $arena_ui = nullptr; |
||||||
|
|
||||||
|
FSM(std::string enemy_name); |
||||||
|
|
||||||
|
void event(Event ev); |
||||||
|
void START(Event ); |
||||||
|
void IDLE(Event ev); |
||||||
|
void END(Event ev); |
||||||
|
|
||||||
|
void try_move(int dir, bool strafe); |
||||||
|
void keyboard_mouse(); |
||||||
|
void draw_gui(); |
||||||
|
void render(); |
||||||
|
bool active(); |
||||||
|
void run_systems(); |
||||||
|
void handle_world_events(); |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,133 @@ |
|||||||
|
#include "arena_ui.hpp" |
||||||
|
#include "easings.hpp" |
||||||
|
#include "sound.hpp" |
||||||
|
#include <fmt/xchar.h> |
||||||
|
|
||||||
|
namespace arena { |
||||||
|
using namespace guecs; |
||||||
|
|
||||||
|
ArenaUI::ArenaUI(shared_ptr<DinkyECS::World> world, DinkyECS::Entity entity_id) |
||||||
|
: $world(world), |
||||||
|
$entity_id(entity_id), |
||||||
|
$config(world->get_the<components::GameConfig>()) |
||||||
|
{ |
||||||
|
$status.position(0, 0, BOSS_VIEW_X, SCREEN_HEIGHT); |
||||||
|
$status.layout( |
||||||
|
"[main_status]" |
||||||
|
"[(150)status_3|(150)status_4]" |
||||||
|
"[(150)status_5|(150)status_6]" |
||||||
|
"[(150)status_7|(150)status_8]"); |
||||||
|
|
||||||
|
$overlay.position(BOSS_VIEW_X, BOSS_VIEW_Y, |
||||||
|
BOSS_VIEW_WIDTH, BOSS_VIEW_HEIGHT); |
||||||
|
|
||||||
|
$overlay.layout("[_|=*%(200)enemy|_|_]"); |
||||||
|
|
||||||
|
$sounds = $world->get<components::Sound>($entity_id); |
||||||
|
$combat = $world->get<components::Combat>($entity_id); |
||||||
|
$sprite_config = $world->get<components::Sprite>($entity_id); |
||||||
|
} |
||||||
|
|
||||||
|
void ArenaUI::configure_sprite() { |
||||||
|
$animation = $world->get<components::Animation>($entity_id); |
||||||
|
$animation.frame_width = $sprite_config.width; |
||||||
|
|
||||||
|
auto enemy_id = $overlay.entity("enemy"); |
||||||
|
auto& enemy_image = $overlay.get<Sprite>(enemy_id); |
||||||
|
|
||||||
|
sf::IntRect frame_rect{{0,0},{$sprite_config.width, $sprite_config.height}}; |
||||||
|
enemy_image.sprite->setTextureRect(frame_rect); |
||||||
|
} |
||||||
|
|
||||||
|
void ArenaUI::configure_background() { |
||||||
|
if($world->has<components::BossFight>($entity_id)) { |
||||||
|
auto& boss = $world->get<components::BossFight>($entity_id); |
||||||
|
|
||||||
|
$entity_background = textures::get(boss.background); |
||||||
|
$entity_background.sprite->setPosition({BOSS_VIEW_X, BOSS_VIEW_Y}); |
||||||
|
$status.world().set_the<Background>({$status.$parser}); |
||||||
|
|
||||||
|
$entity_has_stage = true; |
||||||
|
|
||||||
|
if(boss.stage) { |
||||||
|
$entity_stage = textures::get(*boss.stage); |
||||||
|
} else { |
||||||
|
$entity_stage = textures::get("devils_fingers_background"); |
||||||
|
} |
||||||
|
|
||||||
|
$entity_stage.sprite->setPosition({BOSS_VIEW_X, BOSS_VIEW_Y}); |
||||||
|
} else { |
||||||
|
$entity_has_stage = false; |
||||||
|
$entity_background = textures::get("devils_fingers_background"); |
||||||
|
$entity_background.sprite->setPosition({BOSS_VIEW_X, BOSS_VIEW_Y}); |
||||||
|
$status.world().set_the<Background>({$status.$parser}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void ArenaUI::configure_gui() { |
||||||
|
for(auto& [name, cell] : $status.cells()) { |
||||||
|
auto button = $status.entity(name); |
||||||
|
$status.set<Rectangle>(button, {}); |
||||||
|
$status.set<Clickable>(button, { |
||||||
|
[this, name](auto, auto){ |
||||||
|
dbc::log(fmt::format("STATUS: {}", name)); |
||||||
|
} |
||||||
|
}); |
||||||
|
if(name == "main_status") { |
||||||
|
$status.set<Textual>(button, {fmt::format(L"HP: {}", $combat.hp)}); |
||||||
|
} else { |
||||||
|
$status.set<Label>(button, {L"Attack"}); |
||||||
|
} |
||||||
|
} |
||||||
|
$status.init(); |
||||||
|
|
||||||
|
for(auto& [name, cell] : $overlay.cells()) { |
||||||
|
auto region = $overlay.entity(name); |
||||||
|
$overlay.set<Clickable>(region, { |
||||||
|
[this, name](auto, auto){ |
||||||
|
dbc::log(fmt::format("OVERLAY: {}", name)); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
if(name == "enemy") { |
||||||
|
$overlay.set<Sprite>(region, {$sprite_config.name, 20}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$overlay.init(); |
||||||
|
|
||||||
|
configure_sprite(); |
||||||
|
} |
||||||
|
|
||||||
|
void ArenaUI::init() { |
||||||
|
// background must come first
|
||||||
|
configure_background(); |
||||||
|
configure_gui(); |
||||||
|
} |
||||||
|
|
||||||
|
void ArenaUI::render(sf::RenderWindow& window) { |
||||||
|
window.draw(*$entity_background.sprite); |
||||||
|
|
||||||
|
if($entity_has_stage) { |
||||||
|
window.draw(*$entity_stage.sprite); |
||||||
|
} |
||||||
|
|
||||||
|
$status.render(window); |
||||||
|
$overlay.render(window); |
||||||
|
} |
||||||
|
|
||||||
|
bool ArenaUI::mouse(float x, float y) { |
||||||
|
if($status.mouse(x, y)) { |
||||||
|
dbc::log("STATUS button pressed"); |
||||||
|
} |
||||||
|
|
||||||
|
if($overlay.mouse(x, y)) { |
||||||
|
$animation.play(); |
||||||
|
sound::play("Sword_Hit_1"); |
||||||
|
$entity_hit = !$entity_hit; |
||||||
|
$combat.hp--; |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
#pragma once |
||||||
|
#include <SFML/Graphics/RenderWindow.hpp> |
||||||
|
#include <SFML/Graphics/Font.hpp> |
||||||
|
#include "guecs.hpp" |
||||||
|
#include "textures.hpp" |
||||||
|
#include "components.hpp" |
||||||
|
#include <SFML/System/Clock.hpp> |
||||||
|
|
||||||
|
// aspect ratio of art is 3/2 so 1.5
|
||||||
|
// possible sizes: 900/600; 1620/1080; 1800/1200
|
||||||
|
// To calculate it do short side * 1.5 so 1080 * 1.5 == 1620
|
||||||
|
//
|
||||||
|
// Side panel = 300/1080
|
||||||
|
|
||||||
|
namespace arena { |
||||||
|
using std::string, std::shared_ptr; |
||||||
|
|
||||||
|
class ArenaUI { |
||||||
|
public: |
||||||
|
sf::Clock $clock; |
||||||
|
bool $entity_hit = false; |
||||||
|
sf::Vector2f $entity_pos; |
||||||
|
components::Combat $combat; |
||||||
|
components::Sprite $sprite_config; |
||||||
|
components::Sound $sounds; |
||||||
|
components::Animation $animation; |
||||||
|
guecs::UI $status; |
||||||
|
guecs::UI $overlay; |
||||||
|
textures::SpriteTexture $entity_background; |
||||||
|
bool $entity_has_stage = false; |
||||||
|
textures::SpriteTexture $entity_stage; |
||||||
|
std::shared_ptr<DinkyECS::World> $world = nullptr; |
||||||
|
DinkyECS::Entity $entity_id; |
||||||
|
components::GameConfig& $config; |
||||||
|
|
||||||
|
ArenaUI(shared_ptr<DinkyECS::World> world, DinkyECS::Entity entity_id); |
||||||
|
|
||||||
|
void init(); |
||||||
|
void render(sf::RenderWindow& window); |
||||||
|
bool mouse(float x, float y); |
||||||
|
void configure_sprite(); |
||||||
|
void configure_background(); |
||||||
|
void configure_gui(); |
||||||
|
}; |
||||||
|
} |
Loading…
Reference in new issue