Added weapon ammo/reloading, with GUI ammo widget.

This commit is contained in:
Felix Ableitner 2013-06-25 17:44:40 +02:00
parent fd7173adee
commit 9cc928e894
15 changed files with 142 additions and 64 deletions

BIN
resources/DejaVuSans.ttf Normal file

Binary file not shown.

View 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

View file

@ -5,5 +5,5 @@ health: 100
speed: 100
radius: 25
size: [50, 50]
weapon: weapon.yaml
weapon: assault_rifle.yaml
faction: 1

View file

@ -5,5 +5,5 @@ health: 100
speed: 100
radius: 25
size: [50, 50]
weapon: weapon.yaml
weapon: assault_rifle.yaml
faction: 0

View file

@ -1,6 +0,0 @@
name: Pistol
texture: pistol.png
bullet: bullet.yaml
interval: 250
automatic: true

View file

@ -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) {
@ -31,7 +31,13 @@ Game::Game(sf::RenderWindow& window) :
mGenerator.generateCurrentAreaIfNeeded(sf::Vector2f());
mPlayer = std::shared_ptr<Player>(new Player(mWorld, mPathfinder,
mGenerator.getPlayerSpawn()));
mWorld.insertCharacter(mPlayer);
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();
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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().

View file

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

View file

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