Added weapon ammo/reloading, with GUI ammo widget.
This commit is contained in:
parent
fd7173adee
commit
9cc928e894
15 changed files with 142 additions and 64 deletions
BIN
resources/DejaVuSans.ttf
Normal file
BIN
resources/DejaVuSans.ttf
Normal file
Binary file not shown.
8
resources/yaml/assault_rifle.yaml
Normal file
8
resources/yaml/assault_rifle.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
name: Assault Rifle
|
||||
|
||||
bullet: bullet.yaml
|
||||
fire_interval: 150
|
||||
reload_time: 2000
|
||||
automatic: true
|
||||
magazine_size: 30
|
||||
max_total_ammo: 270
|
|
@ -5,5 +5,5 @@ health: 100
|
|||
speed: 100
|
||||
radius: 25
|
||||
size: [50, 50]
|
||||
weapon: weapon.yaml
|
||||
weapon: assault_rifle.yaml
|
||||
faction: 1
|
|
@ -5,5 +5,5 @@ health: 100
|
|||
speed: 100
|
||||
radius: 25
|
||||
size: [50, 50]
|
||||
weapon: weapon.yaml
|
||||
weapon: assault_rifle.yaml
|
||||
faction: 0
|
|
@ -1,6 +0,0 @@
|
|||
name: Pistol
|
||||
texture: pistol.png
|
||||
|
||||
bullet: bullet.yaml
|
||||
interval: 250
|
||||
automatic: true
|
|
@ -19,9 +19,9 @@ const int Game::FPS_GOAL = 60;
|
|||
/**
|
||||
* Initializes game, including window and objects (sprites).
|
||||
*/
|
||||
Game::Game(sf::RenderWindow& window) :
|
||||
Game::Game(tgui::Window& window) :
|
||||
mWindow(window),
|
||||
mView(sf::Vector2f(0, 0), mWindow.getView().getSize()),
|
||||
mWorldView(sf::Vector2f(0, 0), mWindow.getView().getSize()),
|
||||
mGenerator(mWorld, mPathfinder),
|
||||
mQuit(false),
|
||||
mPaused(false) {
|
||||
|
@ -32,6 +32,12 @@ Game::Game(sf::RenderWindow& window) :
|
|||
mPlayer = std::shared_ptr<Player>(new Player(mWorld, mPathfinder,
|
||||
mGenerator.getPlayerSpawn()));
|
||||
mWorld.insertCharacter(mPlayer);
|
||||
|
||||
mAmmo = window.add<tgui::Label>();
|
||||
setAmmoText();
|
||||
mAmmo->setTextSize(20);
|
||||
mAmmo->setPosition(mWindow.getSize().x - mAmmo->getSize().x,
|
||||
mWindow.getSize().y - mAmmo->getSize().y);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,12 +63,32 @@ Game::loop() {
|
|||
|
||||
mWorld.step(elapsed);
|
||||
|
||||
setAmmoText();
|
||||
|
||||
render();
|
||||
|
||||
mGenerator.generateCurrentAreaIfNeeded(mPlayer->getPosition());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays current player ammo in the ammo widget.
|
||||
*/
|
||||
void
|
||||
Game::setAmmoText() {
|
||||
int mag = mPlayer->getMagazineAmmo();
|
||||
int total = mPlayer->getTotalAmmo();
|
||||
|
||||
std::string magString = tgui::to_string(mag);
|
||||
if (mag < 10) magString = "0" + magString;
|
||||
|
||||
std::string totalString = tgui::to_string(total);
|
||||
if (total < 100) totalString = "0" + totalString;
|
||||
if (total < 10) totalString = "0" + totalString;
|
||||
|
||||
mAmmo->setText(magString + "/" + totalString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles general game input.
|
||||
*/
|
||||
|
@ -143,6 +169,9 @@ Game::keyDown(const sf::Event& event) {
|
|||
case sf::Keyboard::D:
|
||||
mPlayer->setDirection(Player::Direction::RIGHT, false);
|
||||
break;
|
||||
case sf::Keyboard::R:
|
||||
mPlayer->reload();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -153,7 +182,7 @@ Game::keyDown(const sf::Event& event) {
|
|||
*/
|
||||
sf::Vector2<float>
|
||||
Game::convertCoordinates(int x, int y) {
|
||||
return mWindow.mapPixelToCoords(sf::Vector2i(x, y), mView);
|
||||
return mWindow.mapPixelToCoords(sf::Vector2i(x, y), mWorldView);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -192,15 +221,15 @@ void
|
|||
Game::render() {
|
||||
mWindow.clear();
|
||||
|
||||
mView.setCenter(mPlayer->getPosition());
|
||||
mWorldView.setCenter(mPlayer->getPosition());
|
||||
|
||||
// Render world and dynamic stuff.
|
||||
mWindow.setView(mView);
|
||||
|
||||
mWindow.setView(mWorldView);
|
||||
mWindow.draw(mWorld);
|
||||
|
||||
// Render GUI and static stuff.
|
||||
mWindow.setView(mWindow.getDefaultView());
|
||||
mWindow.drawGUI();
|
||||
|
||||
mWindow.display();
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#ifndef DG_GAME_H_
|
||||
#define DG_GAME_H_
|
||||
|
||||
#include <TGUI/TGUI.hpp>
|
||||
|
||||
#include "generator/Generator.h"
|
||||
#include "Pathfinder.h"
|
||||
#include "World.h"
|
||||
|
@ -20,7 +22,7 @@ class Player;
|
|||
*/
|
||||
class Game : private sf::NonCopyable {
|
||||
public:
|
||||
explicit Game(sf::RenderWindow& window);
|
||||
explicit Game(tgui::Window& window);
|
||||
~Game();
|
||||
|
||||
void loop();
|
||||
|
@ -35,13 +37,15 @@ private:
|
|||
void mouseUp(const sf::Event& event);
|
||||
|
||||
sf::Vector2<float> convertCoordinates(int x, int y);
|
||||
void setAmmoText();
|
||||
|
||||
private:
|
||||
static const int FPS_GOAL;
|
||||
|
||||
sf::RenderWindow& mWindow;
|
||||
tgui::Window& mWindow;
|
||||
sf::Clock mClock;
|
||||
sf::View mView;
|
||||
sf::View mWorldView;
|
||||
tgui::Label* mAmmo;
|
||||
|
||||
World mWorld;
|
||||
Pathfinder mPathfinder;
|
||||
|
|
|
@ -168,3 +168,18 @@ Character::getCharacters() const {
|
|||
{return c->getFaction() == getFaction();}), characters.end());
|
||||
return characters;
|
||||
}
|
||||
|
||||
int
|
||||
Character::getMagazineAmmo() const {
|
||||
return mWeapon->getMagazineAmmo();
|
||||
}
|
||||
|
||||
int
|
||||
Character::getTotalAmmo() const {
|
||||
return mWeapon->getTotalAmmo();
|
||||
}
|
||||
|
||||
void
|
||||
Character::reload() {
|
||||
mWeapon->reload();
|
||||
}
|
||||
|
|
|
@ -47,6 +47,9 @@ protected:
|
|||
bool isMoving() const;
|
||||
bool isVisible(const sf::Vector2f& target) const;
|
||||
std::vector<std::shared_ptr<Character> > getCharacters() const;
|
||||
int getMagazineAmmo() const;
|
||||
int getTotalAmmo() const;
|
||||
void reload();
|
||||
|
||||
private:
|
||||
void move();
|
||||
|
|
|
@ -16,11 +16,15 @@
|
|||
Weapon::Weapon(World& world, Character& holder, const Yaml& config) :
|
||||
Emitter(world),
|
||||
mHolder(holder),
|
||||
mBullet(config.get(YAML_KEY::BULLET, YAML_DEFAULT::BULLET)),
|
||||
mLastShotWaitInterval(0),
|
||||
mFireInterval(config.get(YAML_KEY::INTERVAL, YAML_DEFAULT::INTERVAL)),
|
||||
mBullet(config.get("bullet", std::string("bullet.yaml"))),
|
||||
mFireInterval(config.get("fire_interval", 0)),
|
||||
mReloadTime(config.get("reload_time", 0)),
|
||||
mFire(false),
|
||||
mAutomatic(config.get(YAML_KEY::AUTOMATIC, YAML_DEFAULT::AUTOMATIC)) {
|
||||
mAutomatic(config.get("automatic", false)),
|
||||
mMagazineSize(config.get("magazine_size", 0)),
|
||||
mMagazineAmmo(mMagazineSize),
|
||||
mMaxTotalAmmo(config.get("max_total_ammo", 0)),
|
||||
mTotalAmmo(mMaxTotalAmmo) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,26 +50,53 @@ Weapon::releaseTrigger() {
|
|||
*/
|
||||
void
|
||||
Weapon::onThink(int elapsed) {
|
||||
// Waiting for next shot, subtract time since last onThink.
|
||||
if (mLastShotWaitInterval > 0)
|
||||
mLastShotWaitInterval -= elapsed;
|
||||
// Only reset to zero if we didn't recently fire (allow catching up for missed bullets).
|
||||
else
|
||||
mLastShotWaitInterval = 0;
|
||||
// Loop just in case we miss a bullet to fire.
|
||||
while (mFire && mLastShotWaitInterval <= 0) {
|
||||
mLastShotWaitInterval += mFireInterval;
|
||||
if (!mTimer.isExpired())
|
||||
return;
|
||||
if (mIsReloading) {
|
||||
mMagazineAmmo = (mTotalAmmo >= mMagazineSize)
|
||||
? mMagazineSize
|
||||
: mTotalAmmo;
|
||||
mTotalAmmo -= mMagazineAmmo;
|
||||
mIsReloading = false;
|
||||
}
|
||||
|
||||
if (mFire && mMagazineAmmo != 0) {
|
||||
emit();
|
||||
if (!mAutomatic)
|
||||
mFire = false;
|
||||
}
|
||||
|
||||
if (mMagazineAmmo == 0 && mTotalAmmo != 0)
|
||||
reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and fires a projectile.
|
||||
*/
|
||||
std::shared_ptr<Sprite>
|
||||
Weapon::createParticle() {
|
||||
mTimer.restart(sf::milliseconds(mFireInterval));
|
||||
mMagazineAmmo--;
|
||||
|
||||
// Minus to account for positive y-axis going downwards in SFML.
|
||||
sf::Vector2f offset(0, - mHolder.getRadius());
|
||||
thor::rotate(offset, thor::polarAngle(mHolder.getDirection()));
|
||||
return std::shared_ptr<Sprite>(new Bullet(mHolder.getPosition() + offset,
|
||||
mHolder, mHolder.getDirection(), Yaml(mBullet)));
|
||||
}
|
||||
|
||||
int
|
||||
Weapon::getMagazineAmmo() const {
|
||||
return mMagazineAmmo;
|
||||
}
|
||||
|
||||
int
|
||||
Weapon::getTotalAmmo() const {
|
||||
return mTotalAmmo;
|
||||
}
|
||||
|
||||
void
|
||||
Weapon::reload() {
|
||||
mIsReloading = true;
|
||||
mTimer.restart(sf::milliseconds(mReloadTime));
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
|
||||
#include <SFML/System.hpp>
|
||||
|
||||
#include <Thor/Time.hpp>
|
||||
|
||||
#include "../particle/Emitter.h"
|
||||
|
||||
class Character;
|
||||
|
@ -31,6 +33,9 @@ public:
|
|||
void pullTrigger();
|
||||
void releaseTrigger();
|
||||
void onThink(int elapsed);
|
||||
int getMagazineAmmo() const;
|
||||
int getTotalAmmo() const;
|
||||
void reload();
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Sprite> createParticle();
|
||||
|
@ -38,11 +43,17 @@ protected:
|
|||
private:
|
||||
Character& mHolder;
|
||||
|
||||
const std::string mBullet; //< Bullet config filename.
|
||||
int mLastShotWaitInterval; //< Remaining time left after firing last bullet before firing next one.
|
||||
const int mFireInterval; //< Time between firing bullets.
|
||||
bool mFire; //< True if the trigger is pulled.
|
||||
bool mAutomatic; //< True if the weapon continues firing after pulling the trigger once.
|
||||
thor::Timer mTimer;
|
||||
const std::string mBullet;
|
||||
const int mFireInterval;
|
||||
const int mReloadTime;
|
||||
bool mFire;
|
||||
bool mAutomatic;
|
||||
const int mMagazineSize;
|
||||
int mMagazineAmmo;
|
||||
const int mMaxTotalAmmo;
|
||||
int mTotalAmmo;
|
||||
bool mIsReloading = false;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "Game.h"
|
||||
#include "util/Loader.h"
|
||||
#include "util/Yaml.h"
|
||||
#include "util/Log.h"
|
||||
|
||||
/**
|
||||
* Creates Game object.
|
||||
|
@ -18,9 +19,12 @@ int main(int argc, char* argv[]) {
|
|||
Loader::i().setFolder("resources/");
|
||||
Loader::i().setSubFolder<sf::Texture>("textures/");
|
||||
|
||||
sf::RenderWindow window(sf::VideoMode(800, 600, 32), "Dungeon Gunner",
|
||||
tgui::Window window(sf::VideoMode(800, 600, 32), "Dungeon Gunner",
|
||||
sf::Style::Close | sf::Style::Titlebar);
|
||||
|
||||
if (!window.globalFont.loadFromFile("resources/DejaVuSans.ttf"))
|
||||
LOG_W("Failed to load font at 'resources/DejaVuSans.ttf'");
|
||||
|
||||
Game game(window);
|
||||
|
||||
game.loop();
|
||||
|
|
|
@ -33,22 +33,6 @@ Player::setCrosshairPosition(const sf::Vector2f& position) {
|
|||
mCrosshairPosition = position - getPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull the trigger on the attached weapon.
|
||||
*/
|
||||
void
|
||||
Player::pullTrigger() {
|
||||
Character::pullTrigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the trigger on the attached weapon.
|
||||
*/
|
||||
void
|
||||
Player::releaseTrigger() {
|
||||
Character::releaseTrigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the movement direction. This is destined for input via keys (eg. WASD).
|
||||
* Disables any previous commands via Player::move().
|
||||
|
|
|
@ -32,10 +32,13 @@ public:
|
|||
const sf::Vector2f& position);
|
||||
|
||||
void setCrosshairPosition(const sf::Vector2f& position);
|
||||
void pullTrigger();
|
||||
void releaseTrigger();
|
||||
using Character::pullTrigger;
|
||||
using Character::releaseTrigger;
|
||||
void setDirection(Direction direction, bool unset);
|
||||
void setDestination(const sf::Vector2f& destination);
|
||||
using Character::getMagazineAmmo;
|
||||
using Character::getTotalAmmo;
|
||||
using Character::reload;
|
||||
|
||||
private:
|
||||
void onThink(int elapsed);
|
||||
|
|
|
@ -93,11 +93,6 @@ namespace YAML_KEY {
|
|||
|
||||
// Bullet
|
||||
const std::string DAMAGE = "damage";
|
||||
|
||||
// Weapon
|
||||
const std::string BULLET = "bullet";
|
||||
const std::string INTERVAL = "interval";
|
||||
const std::string AUTOMATIC = "automatic";
|
||||
}
|
||||
|
||||
namespace YAML_DEFAULT {
|
||||
|
@ -105,9 +100,6 @@ namespace YAML_DEFAULT {
|
|||
const float SPEED = 100;
|
||||
const std::string WEAPON = "weapon.yaml";
|
||||
const int DAMAGE = 10;
|
||||
const int INTERVAL = 250;
|
||||
const std::string BULLET = "bullet.yaml";
|
||||
const bool AUTOMATIC = false;
|
||||
const int FACTION = 1;
|
||||
}
|
||||
|
||||
|
|
Reference in a new issue