Help with my 2d game

Hi, I'm really stuck trying to write decent/mantainable code for my little tilebased oldschool 2d engine.
I started trying to use the less math possible, like the majority of old 2d games did... Now apart from the stupid algorithm that I'm using(the main point here is not how to do collisions), I would like to know how can I organize lots of code that is always dependent on the same value (in this case direction.x), as you can see. At first I had 2 main branches with lots of repeated code, I tried to precompute some stuff in this example, like inFront, isNewOverlap and isNewBestEntity. This allowed me to use the same code after, without having two main branches differing only for these checks, all of this is inside a loop that checks for some tiles in front of the entity moving. But as you can imagine, later code (but also before this example) is the same, we always have checks that compare greater o less then values, or include width or not... I thought of switching to a center based coordinates system instead of top left (so that I can always do centerX + width*direction) but since I'm working with bitmap I have no real center so I tried to avoid that... and even if I had done it, I would still have to fight with all those > < checks...

So the real and only question is:

Is there a better way for handling all of these repeating code without changing the algorithms? (stuff like the example here the code is starting to grow and it's full of stuff like this)


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
                       
                if(foundCollideEntity)
                {
                    ASSERT(direction.x != 0.0f);
                    //compute direction dependent calculations:
                    b32 inFront = 0, isNewOverlap = 0, isNewBestEntity = 0;
                    b32 isOverlap = (foundCollideEntity->flags & ENTITY_FLAG_OVERLAP_W_ENTITIES);
                    if(direction.x > 0.0f)
                    {
                        inFront = toPixelCoord(foundCollideEntity->p.x) >= toPixelCoord(entity->p.x + entity->width-1);
                        isNewOverlap = isOverlap && (!*validOverlapX || foundCollideEntity->p.x < (*validOverlapX)->p.x);
                        isNewBestEntity = (!(*bestEntityX)) || (foundCollideEntity->p.x < (*bestEntityX)->p.x);
                            
                    }
                    else
                    {
                        inFront = toPixelCoord(foundCollideEntity->p.x + foundCollideEntity->width-1) <=
                            toPixelCoord(entity->p.x);
                        isNewOverlap = isOverlap && (!(*validOverlapX) ||  (foundColliderEntity->p.x +
                                                                         foundColliderEntity->width-1) >
                                                     ((*validOverlapX)->p.x + (*validOverlapX)->width-1));
                        isNewBestEntity = (!(*bestEntityX)) || (foundCollideEntity->p.x + foundCollideEntity->width-1) >
                            (*bestEntityX)->p.x + (*bestEntityX)->width-1;
                    }
                    //NOTE: we now also check for the current tile and entitites can be behind the player

                    if(inFront) //direction dependent!!
                    {
                        if(IsEdgeToEdgeInRange(entity->p.y,
                                               entity->p.y + entity->height-1,
                                               foundCollideEntity->p.y,
                                               foundCollideEntity->p.y + foundCollideEntity->height-1))
                        {
                            //TODO: HERE WE SHOULD HAVE A LIST OF OVERLAPPING ENTITIES SINCE WE CAN OVERLAP
                            // WITH MORE THAN ONE AT THE SAME TIME
                            //entity is COLLIDING
                            if(isNewOverlap)//direction dependent!!
                                (*validOverlapX) = foundCollideEntity;
                            else if(isNewBestEntity)//direction dependent!!
                                    (*bestEntityX) = foundCollideEntity;
                        }
                    }
                }
            }



I don't know if this type of code is inherently like that, and then I should only stay with the previous one, with a big main branch with repeated code and accept the form of it... I hope someone can shine some light on my question!
Cheers from Italy!

Edited by rizoma on Reason: typo
rizoma
I thought of switching to a center based coordinates system instead of top left (so that I can always do centerX + width*direction) but since I'm working with bitmap I have no real center so I tried to avoid that...


Why is working with bitmap an issue with the representation of the bounding box of your entities ? Do you mean you use integer values and so the center could be on half a pixel and that would be a problem ?

I would suggest to use some helper structures and functions. For example use a rectangle structure to represent your entities bounding boxes and create some functions that take the rectangles and direction as parameters and return the answer to your "questions".

Here is some example code that rewrites the code you provided.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
typedef struct rectangle_t {
    r32 left, bottom, right, top;
} rectangle_t;

rectangle_t r2_min_size( r32 x, r32 y, r32 width, r32 height ) {
    rectangle_t result = { x, y, x + width, y + height };
    return result,
}

b32 is_b_in_front_of_a( rectangle_t a, r32 direction, rectangle_t b ) {
    
    b32 result = false;
    
    if ( direction > 0 ) {
        result = a.right < b.left;
    } else {
        result = a.left > b.right;
    }
    
    return result;
}

b32 test_new_overlap( rectangle_t a, r32 direction, rectangle_t b ) {
    
    b32 result = false;
    
    if ( direction > 0 ) {
        result = a.left < b.left;
    } else {
        result = a.right > b.right;
    }
    
    return result;
}

entity_t* valid_overlap_p = *valid_overlap_x;
entity_t* best_entity_p = *best_entity_x;

rectangle_t entity = r2_min_size( entity->p.x, entity->p.y, entity->width, entity->height );
rectangle_t new_collider = r2_min_size( found_collide_entity->p.x, found_collide_entity->p.y, found_collide_entity->width, found_collide_entity->height );
rectangle_t valid_ovelap = r2_min_size( ... );
rectangle_t best_entity = r2_min_size( ... );

b32 in_front = is_b_in_front_of_a( entity, direction.x, new_collider );
b32 is_new_overlap = is_overlap && ( valid_overlap_p == 0 || test_new_overlap( new_collider, direction, valid_overlap ) );
b32 is_new_best_entity = best_entity_p == 0 || test_new_overlap( new_collider, direction, best_entity );
b32 is_edge_to_edge = is_edge_to_edge_in_range( entity, new_collider );

if ( in_front && is_edge_to_edge ) {
    
    if ( is_new_overlap ) {
        valid_overap_p = found_collide_entity;
    } else if ( is_new_best_entity ) {
        best_entity_p = found_collide_entity;
    }
}


Depending on what you want to do, you might even remove the direction parameter from the functions and just inverse the parameter beforehand, and restore them at the end.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
rectangle_t a = entity;
rectangle_t b = new_collider;
b32 inverted = false;

if ( direction <= 0 ) {
    rectangle_t temp = a;
    a = b;
    b = temp;
    inverted = true;
}

b32 in_front = is_b_in_front_of_a( a, b );
b32 is_new_overlap = test_new_overlap( a, b );
...

if ( inverted ) {
    rectangle_t  temp = a;
    a = b;
    b = temp;
    inverted = false;
}

if ( in_front && is_edge_to_edge_in_range ) {
    ...
}
mrmixer

Why is working with bitmap an issue with the representation of the bounding box of your entities ? Do you mean you use integer values and so the center could be on half a pixel and that would be a problem ?


I'm not using sub-pixel accuracy since this is suppose to be a retro game, so I just truncate to integers before rendering and collision detection. I could probably do the same with a center, but that idea wasn't the first in the theme of the oldscool 2d game... I will give it a shoot at some point

mrmixer

I would suggest to use some helper structures and functions. For example use a rectangle structure to represent your entities bounding boxes and create some functions that take the rectangles and direction as parameters and return the answer to your "questions".
...
Depending on what you want to do, you might even remove the direction parameter from the functions and just inverse the parameter beforehand, and restore them at the end.


I see what you are saying and I get some good points.
Probably I'm falling for premature optimizations worrying about hiding the same if statements (like the main one on the direction parameter). Who knows, maybe the compiler will even be smart enough to optimize them all out...

I will go with helper functions as you said...

Thanks for the help!