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