BP.IO / blog | Page 3

Nito

Haven’t uploaded some pixel art in a while, so here is Gravelord Nito from Dark Souls! He’s my favourite boss from Dark Souls by a long shot I think. ;)

A pixel art version of Gravelord Nito from Dark Souls.

A pixel art version of Gravelord Nito from Dark Souls.

Dark Souls fan art

Here’s some fan art I did for Dark Souls. The base line work is in the previous post. I coloured it digitally.

Havel

Sketch

A  sketch of Havel from Dark Souls.

A sketch of Havel from Dark Souls.

I’m Sick Today

Here’s a short game I made when I was sick. You have to infect everyone with your ickyness.

PLAY IT.

Spread the germ-love..

Spread the germ-love..

The Hound

The Hound!

The Hound!

Pixel Swamp

A pixelly swamp based on Roger Dean's Greenslade cover

A pixelly swamp based on Roger Dean’s Greenslade cover

Sketch

A few creature sketches.

A few creature sketches.

Crash

Here’s a new game I made for the cyberpunk game jam over at itch.io. I made it over 5 days with Ben Weatherall doing the cyber-art and Tim Shiel cranking the cyber-music. It’s about hacking robots while plummeting to your doom, and it requires fast typing and hacking skills. Play it now. You can also listen to the official sound track over here.

Crash

An entity system

Here’s a cross-post from my Moonman devlog.

Update: Well it’s been a while, but here’s my first update for 2014. I haven’t touched MM code for a month but instead have been designing and implementing a cleaner and simpler entity system. I’ve been meaning to do this for quite a while now, but after using Unity and looking at other bits of code like entityx I decided to finally attempt it. This is helped in part by the new c++ support in VS2013. The system is also data-oriented — all components and entities are tightly packed in memory. I use a similar free list setup as in MM. I also had to use some c++ techniques I haven’t used before, such as variadic templates and typelists. But that is all behind the scenes, this is what the API looks like:

EntitySystem es;

// Create an entity
// and add some components
Entity& e = es.create();
e.add(Transform(4,5));
e.add(Health(10));
e.add(Physics(0,-10));
e.add(ShortDescription("Ben"));
e.add(Description("An architecture-obsessed programmer."));

// Create another entity
// with different components
// this time using intializer_list shorthand
Entity& chest = es.create();
chest.add(Transform(-4.5f, 0.8f));
chest.add(Inventory{
  { Item::SWORD },
  { Item::POTION, 4 },
  { Item::POTION, 3 },
  { Item::ARROW, 64 }
});

// If we need to keep a reference to an entity
// then we use ID's (uint32s)
ID chestId = chest.id;

// Then later on somewhere we can get the entity
// and do something with it, e.g.,
if (es.has(chestId)){
  Entity& ch = es.lookup(chestId);

  // Shift the chest one unit horizontally
  Transform& tr = ch.get<Transform>();
  tr.x += 1;
}

// Iterating through all entities
// can be done with a range-based for loop
for (Entity& e : es.entities()){
  std::cout << e;
}

// Likewise, we can iterate through all 
// components of a particular type. For 
// example a PhysicsSystem might want to 
// process all the Physics components.

for (auto p: es.components<Physics>()){
  // Apply viscocity
  p.vy *= 0.9f;
  p.vx *= 0.9f;
 
  // Move entity
  Entity& e = es.lookup(p.entity);
  Transform& tr = e.get<Transform>();
  tr.x += p.vx;
  tr.y += p.vy;
}

// Everything is done with references
// if e doesn't contain C, then 
// e.get<C>() returns a blank component
// (which can be checked for validity)
Entity& e = es.lookup(id);
Health& health = e.get<Health>();
health.health = 666;
if (health){
  // It's valid
}


// Components themselves are just structs
// The CRTP gives them a unique class id
// that is used to store them in EntitySystem
struct Health: public Component<Health> {  
  float health;
  bool poisoned;
  Health(float health = 0.f, bool poisoned = false) :health(health), poisoned(poisoned){}

  std::string what() const; // human readable rep
  static const char* Name(); // name
};

// The logic of a component is implemented
// in a system. e.g., the HealthSystem might
// be responsible for decreasing a character's
// health if they are poisoned.

class HealthSystem : public ISystem {
public:
  bool implements(int componentIndex) override {
    return Health::Index()==componentIndex;
  }

  void setup(Entity& e) override {
    e.get<Health>().health = 100;
  }

  void update(EntitySystem& es, double dt) override {
    for (Health& h : es.components<Health>()){
      if (h.poisoned){
        h.health -= 0.1f * (float)dt;
        if (h.health <= 0){
          // Create KILL EVENT
        }
      }
    }
  }
};
&#91;/cpp&#93;

Besides this I've also been thinking about having Script components. These will be a special type of component that uses traditional polymorphism and lambdas to offer a concise and easy way to implement special behaviours. For example, I could attach a custom c++ poison script to a entity like this:

&#91;cpp&#93;
Script* newPoisonerScript(Entity& e){
  // variables to be captured by the lambdas
  float* duration = new float(0.f);
  float* timer = new float(0.f);

  auto poisoner = new Script("poisoner");

  poisoner->destroy = [duration, timer](Entity& e){
    Health& health = e.get<Health>();
    if (health) health.poisoned = false;
    delete duration;
    delete timer;
  };

  poisoner->start = [](Entity&e){
    Health& health = e.get<Health>();
    if (health) health.poisoned = true;
  };

  poisoner->update = [timer, duration](Entity& e, double dt){
    *duration += (float) dt;
    Health& health = e.get<Health>();
    if (health){
      if (health.poisoned){
        *timer -= (float) dt;
        if (*timer < 0){
          health.health -= 1;
          *timer = 1.0f;
        }
      }
    }
  };

  poisoner->finished = [duration](Entity& e){return *duration>1.5f;};
  return poisoner;
}

Anyway, once I’ve finalised the new system it’s going to take a couple of days to incorporate it into Moonman, but I definitely think it’s worth the deviation. Basically it means that I’ll turn macro’ed code that looks like this (see devlog for explanation of how it works):

ATTRIB(e, x) += 0.1f;
bool isOnGround = ATTRIB_OR(e, is_on_ground, false);
bool hasPhysics = HAS_ATTRIB(e, is_physics);

Into nicely autocompleting code that looks like this:

e.get<Transform>().x += 0.1f; 
// or with shorthand
e.transform().x += 0.1f;
// and
bool isOnGround = e.get<Physics>().isOnGround;
bool hasPhysics = e.has<Physics>();

A basic tile-based game in SFML

Whoops, the code below has been mangled by wordpress, sorry! I’ll keep it in the post but here’s a link to the source also.

Hey there! Here’s the source for a simple tile-based game in SFML. You move the white player around a small world that contains randomly moving red and green zombies. The code demonstrates basic gamedev and SFML concepts such as collision detection, game state, movement interpolation, and uses a simple data-oriented design.

The player is the white square. Water is blue. Zombies are green and red.

The player is the white square. Water is blue. Zombies are green and red.


#include
#include
#include #include
#include
#include
#include
using namespace std;

static const int WINDOW_WIDTH = 512, WINDOW_HEIGHT = 512;
static const int TILES_WIDE = 8, TILES_HIGH = 8;
static const int TILE_WIDTH = WINDOW_WIDTH/TILES_WIDE, TILE_HEIGHT = WINDOW_HEIGHT/TILES_HIGH;

enum Tile {
TILE_WATER,
TILE_DIRT,

TILE_INVALID,
};

class Map {
public:
Map(){}
Map(string mapString){
// Convert map string into tiles
if (mapString.length()!=TILES_WIDE*TILES_HIGH){
cerr << "map string is wrong length!"; return; } for(int i=0;i=0 && i=0 && j monsters;
};

// Checks to see if theres a monster here
bool isMonsterHere(Game& game, int i, int j){
for(auto monster: game.monsters){
if (monster.i==i && monster.j==j) return true;
}
return false;
}

// Checks to see if theres a player here
bool isPlayerHere(Game& game, int i, int j){
if (game.player.i==i && game.player.j==j) return true;
else return false;
}

// Linear interpolation
float lerp(float a, float b, float t){
return a*(1-t) + b*t;
}

int main(){
srand(time(NULL)); // seed randomiser
sf::RenderWindow window(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), “Game”);

// Setup game
Game game;
game.state = WAITING_FOR_PLAYER_INPUT;

// Setup map
string mapString =
” ”
” ~~~ ~~ ”
” ”
” ~ ~ ”

” ~ ~ ”
” ~ ~ ”
” ~~ ~~~ ”
” “;
game.map = Map(mapString);

// Setup player
game.player.i = 0;
game.player.j = 0;
game.player.isMoving = false;
game.player.oldI = 0;
game.player.oldJ = 0;
game.player.pi = 0.0f;
game.player.pj = 0.0f;

// Create some monsters
const int NUM_MONSTERS = 6;
for(int i=0;i 0.8){
type = RED_ZOMBIE;
}

// Find an empty dirt spot to place the monster
int placeI = 0, placeJ = 0;
while(true){
int ri = rand()%TILES_WIDE;
int rj = rand()%TILES_HIGH;

// Check for collision with player or other zombies
bool canPlaceHere = true;
canPlaceHere = !isPlayerHere(game, ri, rj);
canPlaceHere = canPlaceHere && !isMonsterHere(game, ri, rj);
canPlaceHere = canPlaceHere && game.map.tile(ri, rj)==TILE_DIRT;
if (canPlaceHere){
placeI = ri;
placeJ = rj;
break;
}
// else keep trying
}

Monster monster;
monster.type = type;
monster.i = placeI;
monster.j = placeJ;
monster.isMoving = false;
monster.oldI = monster.i;
monster.oldJ = monster.j;
monster.pi = (float) placeI;
monster.pj = (float) placeJ;

game.monsters.push_back(monster);
}

// Start loop
game.clock.restart();
while (window.isOpen())
{
// Manage Input

sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed){
window.close();
}

if (game.state==WAITING_FOR_PLAYER_INPUT){
if (event.type==sf::Event::KeyPressed){
int di = 0, dj = 0; // the direction to move in
switch (event.key.code){
case sf::Keyboard::Left: di = -1; break;
case sf::Keyboard::Right: di = 1; break;
case sf::Keyboard::Up: dj = -1; break;
case sf::Keyboard::Down: dj = 1; break;
default: break;
}

if (di!=0 || dj!=0){
// Player wants to move so try to move him
int i = game.player.i + di;
int j = game.player.j + dj;

// Check targe tile for collision
bool collision = false;
Tile t = game.map.tile(i,j);
switch (t){
case TILE_WATER: case TILE_INVALID: collision = true; break;
default: break;
}
collision = collision || isMonsterHere(game, i, j);

if (!collision){
// Can move there
game.player.oldI = game.player.i;
game.player.oldJ = game.player.j;
game.player.i = i;
game.player.j = j;
game.player.isMoving = true;
game.state = UPDATING_PLAYER;
game.clock.restart();
}
}
}
}
}

// Update other game state etc
switch (game.state){
case WAITING_FOR_PLAYER_INPUT:
{
// Player gets only a short time to make a move
// The logic of movement is handled in the event handled
static const float MAX_TIME_TO_MOVE = 2.0f;
float t = game.clock.getElapsedTime().asSeconds();
if (t >= MAX_TIME_TO_MOVE){
game.state = UPDATING_PLAYER;
game.clock.restart();
}
break;
}
case UPDATING_PLAYER:
{
static const float MOVE_SPEED = 0.2f; // seconds it takes to move
float t = game.clock.getElapsedTime().asSeconds();
t = min(MOVE_SPEED, t);

if (game.player.isMoving){
game.player.pi = lerp(game.player.oldI, game.player.i, t/MOVE_SPEED);
game.player.pj = lerp(game.player.oldJ, game.player.j, t/MOVE_SPEED);
}

if (t >= MOVE_SPEED){
game.player.pi = game.player.i;
game.player.pj = game.player.j;
game.player.oldI = game.player.i;
game.player.oldJ = game.player.j;
game.player.isMoving = false;
game.state = UPDATING_MONSTERS;
game.clock.restart();

// We now update all monsters
for(auto& monster: game.monsters){
// Monsters move randomly
// and sometimes stay still
double random = (1.0*rand())/RAND_MAX;
if (random > 0.6){
// stay still
continue;
}

// Here’s a list of possible directions (di,dj)
static const int DIRS[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};

// randomly index into the list
int randomDirIndex = rand()%4;

// And then try each direction in turn
for(int dir=0;dir<4;dir++){ int index = (dir + randomDirIndex)%4; int di = DIRS[index][0]; int dj = DIRS[index][1]; int i = monster.i + di; int j = monster.j + dj; // Check targe tile for collision bool collision = false; Tile t = game.map.tile(i,j); switch (t){ case TILE_WATER: case TILE_INVALID: collision = true; break; default: break; } collision = collision || isPlayerHere(game, i, j) || isMonsterHere(game, i, j); if (!collision){ // Can move there monster.oldI = monster.i; monster.oldJ = monster.j; monster.i = i; monster.j = j; monster.isMoving = true; break; } } } } break; } case UPDATING_MONSTERS: { static const float MOVE_SPEED = 0.4f; // seconds it takes a zombie to move float t = game.clock.getElapsedTime().asSeconds(); t = min(MOVE_SPEED, t); for(auto& monster: game.monsters){ if (monster.isMoving){ monster.pi = lerp(monster.oldI, monster.i, t/MOVE_SPEED); monster.pj = lerp(monster.oldJ, monster.j, t/MOVE_SPEED); } } if (t >= MOVE_SPEED){
for(auto& monster: game.monsters){
monster.oldI = monster.i;
monster.oldJ = monster.j;
monster.isMoving = false;
}
game.state = WAITING_FOR_PLAYER_INPUT;
game.clock.restart();
}
break;
}
}

// Draw
window.clear(sf::Color::Black);

// Draw map
for(int i=0;i