Bees.

Posted by Astryl on Dec. 8, 2014, 2:36 p.m.

I had a somewhat amusing day at work. I arrived this morning just before 9AM, and noticed something out of the corner of my eye.

There, on our store-front window, was a swarm of bees clustered together, about the size of a grapefruit.

I hate bees, and being stung. Fortunately, these bees were not interested in me in the slightest.

Still, as a precaution, we kept the doors closed for most of the day and put a sign up "CAUTION: Door closed due to bees" with an arrow pointing to the swarm.

Most people thought it was a joke, until they looked. Some of them jumped pretty far.

We don't know why they were hanging there, but they stayed that way all day while we waited for some beekeeper or other to show up. He didn't, and my boss was getting tired of waiting. The bees were hurting (literally in some cases) our business.

So she grabbed a can of Doom (Insecticide), sprayed some quickly and dashed back inside.

The bees didn't die, but they did start to fall apart. One of the last 'pieces' of the swarm that fell off had the queen in it, surrounded by a ball of other bees. Reminded me of Katamari…

The funny thing is, there wasn't anything there for the bees to make a hive in. They were literally gripping onto a tiny bit of dried paint that had formed a small bump in the paintwork, on the underside of the window lintel (Concrete and metal construction).

That was certainly the last thing I imagined happening today; would've taken a photo, but I kinda forgot. :P

Ludum Dare

I kinda flunked this. Not intentionally, nor because I didn't have time. The whole country has been going through rolling blackouts, and they were particularly bad on the weekend. I lost quite a bit of work to the first one, and had a lot of trouble getting back on track after that.

I didn't upload my 'final' product, but here's what I had at the end:

The theme for this LD was "The entire game on one screen". I decided to make something arcade-like. Waves of enemies would be beamed in by that contraption in the middle, and you'd have to survive for as long as possible, collect randomized powerups and possibly have a second player join in.

Somewhat low-key for me, but that's because of what I coded this in. What I do have, I made entirely from scratch in C++. I was originally planning to use one of my existing frameworks to accelerate the workflow, but decided to test the difficulty of doing something like this from scratch.

Turns out, not too long.

All I really have here is animation, running and jumping (And collision, of course). I had started on an enemy, projectile and health system when the power went out. And of course I hadn't saved.

I did stream a bit. Potato quality if you want to take a look: http://www.twitch.tv/64mega/profile/past_broadcasts

Uhh… Impulse post mortem, I guess?

Overall 'engine' design

I decided to use a system that was a hybrid of my old Exile system, and a newer one I have been working on.

In Exile, all GameObjects are spawned into a single list. Every frame, this list is traversed, dead objects are culled and the update() method for each object is called.

This is great, when you're dealing with a small number of objects.

It's also great when you don't need any information about the type of object.

Oh, and it becomes a bloody mess when you need to do collision detection/object interaction. Loops within loops within loops…

Exile worked though. :3

My 'new' system uses a few different techniques. Here's the base game object class:

class GObject {
public:
    static GRegistry registry;

    std::string id = "object";
    GSprite* sprite = nullptr;
    float x = 0.0F, y = 0.0F;
    bool dead = false;

    GObject();
    virtual ~GObject();

    virtual void update();
};

It's very similar to what I used for Exile. It has a 'dead' flag, it has a position (I could decouple that into a separate object, but I was rushing), and it has the update method.

The magic here is in the static GRegistry object.

A GRegistry is nothing more than a list of objects. Which isn't a big deal. Here's the definition:

class GRegistry {
public:
    void add(GObject* obj);
    void cull();
    void iterateOver(std::function<bool(GObject&)> func);
    void update();
    void cullAll();
private:
    std::vector<GObject*> objects;
};

The only mildly unusual thing is the iterateOver() method; it may be alien to C++ developers who haven't yet begun poking into the newer C++11 features.

An std::function is basically a 'safe' function pointer. Much cleaner than the old syntax.

In GObject's constructor, only one statement is called:

registry.add(this);

This basically means that any GObject is automatically added to the list.

The 'trick' here only comes into play when I start adding new classes to the game.

The first one I added was a "block" type, for level pieces.

class EBlock : public GObject
{
public:
    static GRegistry registry;
    EBlock(int tx = 0, int ty = 0);
    virtual ~EBlock();

    virtual void update();
};

Note that it also declares a static GRegistry member. This overrides registry for the EBlock object, but leaves GObject's registry intact. This has a nifty use, as can be seen a bit later.

The EBlock constructor is basically this:

EBlock::EBlock(int tx, int ty) :
GObject()
{
    this->registry.add(this);
}

This explicitly places a pointer to the newly instanced EBlock into EBlock::registry, but also calls the base constructor, which will add a pointer to the instance to GObject::registry.

Later, in the Player object, I need to check for collision with any EBlocks. But I only want to check EBlocks, I don't want to loop through every single GObject like Exile used to do. Especially not for level tiles (Exile actually separated level collisions completely, adding another layer of collision detection. It was complicated).

But because of this system, I can use a very nice representation to interact with specific classes of object. Like so:

EBlock::registry.iterateOver([&](GObject& object) {
    EBlock* block = (EBlock*) &object;
    // Do collision stuff with block
});

I treat the cast as implicitly safe due to knowing that the objects EBlock::registry should contain are all of type EBlock. This is something that would be a lot uglier to implement if it weren't for lambdas; I'd have to define a separate method, and pass a pointer to that method into the iterateOver() method.

Of course, even with this narrowing of search data, I'd still rather move away from using GObject's for level collisions anyway. Generally I create a tile-table, and check against those directly (It's easy to determine what point in a grid the player will be in by the end of a frame, making the entire thing a single simple check as opposed to potentially hundreds of little checks).

Unfortunately, I ran out of time (Though I may finish this little project at some point).

This entire system makes working with classes of objects a lot easier. In Exile, I had a string called "id" (Which is present here for reasons discussed below) that I would check to find out if I was dealing with an object I wanted to deal with.

With this system, I know what type I'm dealing with, and can avoid a lot of conditional tests I'd otherwise be using.

The id member is there more as a tagging system now. Kind of like a packed data system that can be used for anything I want.

I could set the string to "cd" and have the following in my player code:

if(object->id.find_first_of("c") != string::npos) {
    // Collide with this object
}
if(object->id.find_first_of("d") != string::npos) {
   // Get damaged by this object
}

Though with the way my design is moving, I probably won't use it like that. I'm using a Component system more often at the moment, and my Object class will change to reflect that.

To avoid extensive checking, I'd probably implement the system somewhat like this:

// Partial class definition
class GObject {
    private:
        Damage *damage = nullptr; 
        Collider *collider = nullptr;
   public:
       void DoCollision(std::function<void(GObject&)> func);
       void DoDamage(std::function<void(GObject&)> func);
};

// Will use like this:
void Player::update()
{
    Player *self = this;
    EEnemyProjectile::iterateOver([&](GObject& obj) {

        // Check if colliding and perform following
        obj.DoDamage([&](GObject& obj) {
            TDamage* damage = (TDamage*) &obj;
            self->hp -= damage->value;
        });
    }); 
}

The implementation for DoCollision and the like will basically only execute the function if it actually has a pointer to a TCollider, and likewise for DoDamage.

On that note, I haven't actually tested internal lambda capture rules yet. The [&] I passed to the lambda mean to 'capture' all variables in the scope the lambda was defined in. I'm not 100% sure if the nested lambda will capture the entire scope, including the one passed from the outer lambda.

My logic says it should, but the compiler may say otherwise.

I rambled more than I meant to. I'll stop now. :P

Comments

LAR Games 9 years, 11 months ago

Beads?

Astryl 9 years, 11 months ago

Quote:
AD;OGKAJDG;LKASJDFGL;KAWIRJGHPAOIWRGJ DON'T KILL BEES >:(
In South Africa, Bees Kill You if you're not careful.

Only one or two on the outside of the cluster actually died. The rest swarmed and flew off towards the river.

EDIT: To clarify… these little buggers are the origin of the "African Killer Bee". They're not fuzzy little honey bees, and are about as bad as wasps in the agression department.

Nopykon 9 years, 11 months ago

Too bad you couldn't finish Ludum. It looked like it could be a fun arcady shooter. Everytime i see dark city backgrounds, I'm reminded of Contra ]|[.

I didn't finish my entry either, but I entered what I had. http://ludumdare.com/compo/ludum-dare-31/?action=preview&uid=40112 (turn v-sync if the effects move to fast.)

From scratch in C, source included. Obvs, seriously rushed, don't judge me! :-O

A game object registry and query like that would have come in handy.

Astryl 9 years, 11 months ago

Nice, giving it a try and taking a look at your source. :P

I like reading engine source code, no matter what it looks like. You always glean ideas from doing it.

Also, props for actually submitting something. I probably would have if the power had remained stable.

Astryl 9 years, 11 months ago

Oh yeah, I forgot I'd written about that before :P

Astryl 9 years, 11 months ago

Not at all. I probably borrowed it from somewhere else anyway, and forgot where :P

It's a very natural feeling management mechanism, and also basically makes objects easier to delete. At the end of the game, I can just call GObject::registry.cullAll() to delete all Game Objects, for instance.

Exile had several lists for different objects. There was the Projectile list, EnemyProjectile list, Enemy list, Item list, PlayerInventory list, DoorList, and so on. Each of those I had to delete (And I recently noticed that I was forgetting to delete the Projectile list, causing a subtle memory leak that I wouldn't have found normally).

This is, essentially, a simplified Garbage Collection system. Objects that flag themselves as dead can trigger a sweep for dead objects, starting in their class and cascading 'up' the destructor chain to remove them from all registries.

There are a few complexities to take into account with this, such as how to deal with the parent destructor, how to decide who ultimately deletes the object, and so on.

Another little idea I added recently, back to the topic of getting the object to initiate a sweep of its own registry, I'm using an EventBus:

class EventBus 
{
    public:
        addEvent(Event* event);
        procEvents(); // Iterate through eventlist executing Event::run()
    private:
        std::vector<Event*> eventlist;
};

The only Event type I've created so far is EventSweep, which looks like this:

class EventSweep 
{
    public:
        GObject* registry = nullptr;
        virtual void run();
};

// run() is implemented like this:
void EventSweep::run() 
{
    if(registry) { registry->cull(); }
}

The reason I'm delegating the sweep like this is because of potential issues arising when you cause an object to be deleted by itself, even in a roundabout manner.

This could've been another blog.

Astryl 9 years, 11 months ago

I use static members specifically for instances where it makes logical sense for functionality or data to be associated with the entire class of an object, over a single instance of an object.