Is hacky code allowed in industry?

Started by
28 comments, last by rpiller 10 years, 5 months ago


Given that Mario.collide is calling a method on Block, is there some reason you can't have Block.createMushroom or Block.collide(thingHittingMe)? Does it need to be in update for some reason? An event makes more sense to me than an update loop for a context like this--the collision happens once at one moment and really only needs to fire off, "Hey, I've hit something!" to the interested parties (usually those involved in the collision). It's a lot cheaper than every few milliseconds, "Am I hit? Am I hit? Am I hit? I'm hit! Am I hit? Am I hit?"

I'm faulty of going that road too, very often. I have a post-it on my left screen (coding screen) that says exactly 'use events!' and it helps.

As for, is it acceptable in the industry: not really.

Does it happen?: All the time!

The thing is, no client (whether it be internal or external) will flat-out say that it's acceptable to code cheaply. They all want quality for cheap, but only some of them actually have realistic budgetary estimates.

Then you need to account for the quantity of change of direction during production, how late it happens, etc.

These tend to lead to hacky code appearing in the codebase. The technical debt increases, and through escalation of commitment, it gets more and more ignored (and not refactored).

As an 'indie developer', I give myself a guideline that goes as follows:

Code 5 days.

Refactor 1 day.

Take 1 day off.

During the 5 days I develop, I try to make things as 'fast' as possible.

During the refactor day, I take all the things I've done and consider how to make it more efficient, cleaner, etc. I break longer functions into smaller ones, fix my encapsulation usage of local vars, etc.

No later than yesterday, I've fixed a problem exactly like yours (occurring in an onEnterFrame loop and using a hacky Boolean to make sure it happens only once). It was taxing my loop unnecessarily and I switched that to an actual Event.

Whether it ends up being acceptable or not, I think you should aim at being able to avoid it, so that when given the time, you are able to code cleanly. The idea is that, if you're not rushed early in dev, you'll be able to do a clean codebase and keep relatively the same velocity over time.

Advertisement

I recently was faced with a bug which was pretty annoying:

periodically, all the point-light sources would momentarily fail, causing the scene to periodically go into full darkness.

long story short, eventually figured out that it occurred whenever the shadow maps were being redrawn (basically redrawing the scene via FBOs and a cube-map), but would fix itself whenever the scene-visibility was re-evaluated (via occlusion queries).

spent a while trying to figure out just where/how the OpenGL state was getting messed up here, but couldn't locate the issue.

eventually, I just hacked it, so that the shadow-maps would always be redrawn prior to re-evaluating visibility (rather than afterwards), which fixed the issue (even if the actual cause of the bug was not determined).

better still would have been to try to properly hunt down and eliminate the bug though.

What's "hacky" about your code? You seem to have a requirement that collisions with a block should power up a mushroom only once, and have implemented it as such.

The "hacky" part is that it's wasting cycles by checking an if statement on every update or a block. The more block objects he has in his level the more wasteful his code becomes. This situation in itself isn't the worse but if he does things like this all over the place it could add up. The better approach would be to have an OnCollisionEnter() for Mario which gets called once and only once per collision. Then inside this event create the mushroom. It's more efficient.

I tend to think about implementation details in terms of the mechanics of the game. Your example suggests a mario clone sort of block triggering powerups. From that perspective, you need to first think about what the mechanism for triggering the blocks is going to be. Simply colliding with the blocks is too low level of a mechanism with which to trigger the spawning of the powerup. You want mario to trigger the powerup when he jumps upward into the block and collides, and also if the game has the power drop thing that allows you to smash down from a jump with it. Because the triggering of the blocks should be dependent on the type of action that is being performed, it would make more sense to generalize these activities within the state inside the Mario object class, such that when you jump up into something, or smash down onto something, you are calling functions that provide that context, and the objects being hit can respond accordingly.

Some example pseudocode


enum HitFeedback
{
    FEEDBACK_NOHIT,
    FEEDBACK_HIT,
};

class Mario : public GameObject
{
    void collide( collisionevent )
    {
        HitFeedback hit = FEEDBACK_HIT;

        // assuming -z is down
        if ( collisionevent.normal.z < 0.5 ) // did we jump up into the object?
            hit = collisionevent.otherObject.HeadSmashed( this );
        else if ( collisionevent.normal.z > 0.5  && IsSmashingDown()  ) // did we fall down into the object in the smash down state?
            hit = collisionevent.otherObject.SmashDown( this );

        if ( hit == FEEDBACK_HIT )
        // handle landing on slopes or ground to change state to walking, etc
        if ( hit == FEEDBACK_NOHIT )
        // allow the character to continue its current physics state, if flying, keep flying, if smashing, keep smashing
    }
}

// Then your various other game objects can easily implement responses to the game mechanics available by your player

class BreakableBlock : public GameObject
{
    void HeadSmashed( Mario & player )
    {
        Break();
        return FEEDBACK_HIT;
    }
    void SmashDown( Mario & player )
    {
        return FEEDBACK_NOHIT; // so that mario can continue smashing down a column of breakable blocks
    }
}

class PowerupBlock : public GameObject
{
    void HeadSmashed( Mario & player )
    {
        // implemented via a state as it likely involves playing an animation and isnt just an immediate spawning of the powerup
        ChangeState( SPAWN_POWERUP );
        return FEEDBACK_HIT;
    }
    void SmashDown( Mario & player )
    {
        ChangeState( SPAWN_POWERUP );
        return FEEDBACK_HIT;
    }
}
class CoinBlock : public GameObject
{
    void HeadSmashed( Mario & player )
    {
        if ( mCoinsRemaining > 0 )
        {
            player.AddEvent( AddCoin( 1 );
            --mCoinsRemaining;
            if ( mCoinsRemaining <= 0 )
                ChangeState( DEACTIVATED ); // animate, disable callbacks, etc
        }
        return FEEDBACK_HIT;
    }
    void SmashDown( Mario & player )
    {
        // same action as HeadSmashed
        return HeadSmashed( player );
    }
}

class Turtle : public GameObject
{
    void HeadSmashed( Mario & player )
    {
        // hitting a turle from below is damaging, and should probably be treated as solid
        player.AddEvent( Damage( 1 ) );
        return FEEDBACK_HIT;
    }
    void SmashDown( Mario & player )
    {
        ChangeState( FLIPPED_OVER );
        // add some small velocity so the turle gets knocked to the side a bit from where mario smashed down
        return FEEDBACK_NOHIT; // so that mario can continue smashing down to the ground
    }
}

Implement things with respect to game mechanics, and not so much the low level functionality of collision, physics, sound. Those low level systems will often be intricately tied to the function of certain mechanics. In marios case there will be some degree of complexity in interpreting the physics and collision information into a way that gets the actual game mechanics working as you want, but most of those details would ideally be encapsulated into the objects where it matters, such as your Mario class in a simple implementation. The game objects can provide some amount of hit information back from its callbacks such as in this case, so that the type of object can propagate back and effect

You're sort of missing the problem he was having with the code he used (besides the general question he asked about hacks). Since collide() for Mario is getting called every cycle the collision happens, the block results keep happening. In your example you'd get all your coins on the first hit because it would call HeadSmashed() hundreds of times while the collision is happening. So you'd be stuck implementing something like he did because Mario has the collision() function, which is a little "hacky".

OP, you could have the blocks have the collision() function instead of Mario. In order for this to work correctly then you'd have to pass in the object that got collided with into your collision functions so that the "Mario" object could be passed in. Then this would be where you place your boolean check to see if it happened already. This way you'd waste some cycles while the collision is happening but as soon as the collision ends it wouldn't waste cycles anymore.

Then if you wanted to take the design farther you could implement a messaging system to talk between your blocks and the object it hit so that you aren't tightly coupling Mario with your block code. Why do this? Well you could then use similar blocks in a breakout game if you wanted and wouldn't have to refactor them to remove Mario types.

The "hacky" part is that it's wasting cycles by checking an if statement on every update or a block. The more block objects he has in his level the more wasteful his code becomes. This situation in itself isn't the worse but if he does things like this all over the place it could add up. The better approach would be to have an OnCollisionEnter() for Mario which gets called once and only once per collision. Then inside this event create the mushroom. It's more efficient.

I must have misread the OP. Yes, you want a collision event handler, but even inside of it you need to check if the mushroom has already been created because Mario can collide with the same block more than once.

By the way, I think I've seen this game before. smile.png


but even inside of it you need to check if the mushroom has already been created because Mario can collide with the same block more than once.

Correct, you'd want to check the block active state (or whatever) but that would be 1 check for 1 block for 1 cycle inside the collision enter event as opposed to what he currently has which is a check every cycle for every block in his level. The differences in terms of efficiency are huge. The differences in actual real-world game speed aren't even noticeable in this case most likely.

You're sort of missing the problem he was having with the code he used (besides the general question he asked about hacks). Since collide() for Mario is getting called every cycle the collision happens, the block results keep happening. In your example you'd get all your coins on the first hit because it would call HeadSmashed() hundreds of times while the collision is happening. So you'd be stuck implementing something like he did because Mario has the collision() function, which is a little "hacky".

My Mario::collide function would be more appropriate as OnCollideStart, which tends to be the more common collision notification callback types in physics engines Unity(PhysX) and others. OnCollideStart, OnCollideEnd, OnCollide are probably more widely understood physics callbacks within which to generate this type of events. In my example an OnCollideStart, would call an OnCollideStart on mario where the contact can be interpreted and then called out to the relevant object with the relevant action based on additional internal state of Mario. Each of these three functions might call back into the game objects to implement different game mechanics. These 3 callbacks are ultimately what much of the gameplay and interaction between game elements are built on.

Still, the suggestion is to take the low level state, such as the collision details here, and interpret that into higher level function callbacks that represent the different mechanics of the game, and build the game on top of that level of logic. I think ultimately his code is only hacky to the extent that it doesn't utilize a similar sort of OnCollideStart/OnCollideEnd in order to drive his logic, rather than a callback that by its name, is assumed to be called constantly, and instead tries to implement that sort of tracking manually.


My Mario::collide function would be more appropriate as OnCollideStart, which tends to be the more common collision notification callback types in physics engines Unity(PhysX) and others.

I agree, but that's where the difference his. His collision callback isn't like that. It's calling for every frame it collides. No clue what he's using for his physics. I'm guessing it's his own code.

This topic is closed to new replies.

Advertisement