game design

J

jaso

Hi,
I'm making this video game in C.
The game contains a player, enemies and bullets. These objects,
which are arrays of structures, are initialized, updated and drawn
in a game loop. Now I am unsure on where to declare these things.

I could declare them in main() like this

#define NUM_ENEMIES 10
#define NUM_BULLETS 20

int main(void)
{
struct *player player1;
struct enemy enemies[NUM_ENEMIES];
struct bullet bullets[NUM_BULLETS];

init_player(player1);
init_enemies(enemies, NUM_ENEMIES);
init_bullets(bullets, NUM_BULLETS);

while (1) {
update_...
draw_...
}
}

Or, I could declare them globally and make main() a little "cleaner"
by calling the functions with void parameters and let the functions
handle the objects and the count by themselves.

I don't know which one to choose, the second way seems a little
more modular, since I move the internal (should it be internal?)
stuff out of main.
But OTOH, I've read that local variables are better
than global. But again, if I put the handling functions in an other
file, and have static linkage, they won't be global, right?

I would be grateful if someone could give me some points on how
to do.

Thanks!
 
M

Malcolm

jaso said:
Hi,
I'm making this video game in C.
The game contains a player, enemies and bullets. These objects,
which are arrays of structures, are initialized, updated and drawn
in a game loop. Now I am unsure on where to declare these things.

I could declare them in main() like this

#define NUM_ENEMIES 10
#define NUM_BULLETS 20

int main(void)
{
struct *player player1;
struct enemy enemies[NUM_ENEMIES];
struct bullet bullets[NUM_BULLETS];

init_player(player1);
init_enemies(enemies, NUM_ENEMIES);
init_bullets(bullets, NUM_BULLETS);

while (1) {
update_...
draw_...
}
}

Or, I could declare them globally and make main() a little "cleaner"
by calling the functions with void parameters and let the functions
handle the objects and the count by themselves.

I don't know which one to choose, the second way seems a little
more modular, since I move the internal (should it be internal?)
stuff out of main.
But OTOH, I've read that local variables are better
than global. But again, if I put the handling functions in an other
file, and have static linkage, they won't be global, right?

I would be grateful if someone could give me some points on how
to do.
This is a common problem.
The game needs a "world" and many quite low level functions need to operate
on this world.
For instance the gas-cloud baddy doesn't do its stuff whilst the hurricane
baddy is on screen, or else you need the game to handle gas clouds being
blow about by high winds. This sort of consideration destroys modularity.

So the answer is to declare a single header file, "game.h" and put all your
structures into that. Then have a single pointer to a "GAME" instance. It
doesn't really matter if this is global or passed to every function. Every
file include "game.h".
 
J

jaso

Malcolm skrev:
This is a common problem.
The game needs a "world" and many quite low level functions need to operate
on this world.
For instance the gas-cloud baddy doesn't do its stuff whilst the hurricane
baddy is on screen, or else you need the game to handle gas clouds being
blow about by high winds. This sort of consideration destroys modularity.

So the answer is to declare a single header file, "game.h" and put all your
structures into that. Then have a single pointer to a "GAME" instance. It
doesn't really matter if this is global or passed to every function. Every
file include "game.h".

So you mean that I have a pointer to my world, and the
functions operate on this "GAME" pointer, am I getting this right?
What kind of type is a "GAME" instance?
 
A

Andrew Poelstra

Malcolm skrev:

So you mean that I have a pointer to my world, and the
functions operate on this "GAME" pointer, am I getting this right?
What kind of type is a "GAME" instance?

Create a GAME struct (I will call it "world" because I think that's what you
want):

struct world
{
/* All necessary world data here */
};

struct world *current_level = load_first_level();

The /only/ global you will need is a pointer to a world struct. This pointer
would be changed only by functions which move you from level to level, and any
other global variables would be accessed as such: current_level->global_var_here

Having a struct or two to contain all globals makes fairly clean code.
 
C

CBFalconer

Andrew said:
.... snip ...

Create a GAME struct (I will call it "world" because I think
that's what you want):

struct world
{
/* All necessary world data here */
};

Ridiculous. Well designed to make the end result unmaintainable.

--
"Our enemies are innovative and resourceful, and so are we.
They never stop thinking about new ways to harm our country
and our people, and neither do we." -- G. W. Bush.
"The people can always be brought to the bidding of the
leaders. All you have to do is tell them they are being
attacked and denounce the pacifists for lack of patriotism
and exposing the country to danger. It works the same way
in any country." --Hermann Goering.
 
J

jaso

CBFalconer said:
Andrew Poelstra wrote:
... snip ...

Ridiculous. Well designed to make the end result unmaintainable.

How come? Why will it be unmaintainable?
Could you please tell me how to design it well? It would
be nice to know beforehand, so it won't be a nightmare to fix it
when I understand I took the wrong decision.

Thank you
 
R

Richard Heathfield

jaso said:
How come? Why will it be unmaintainable?

What usually happens is that the coupling between modules is drastically
increased by the existence of file scope data.

You're a maintenance programmer. You're fairly new to the project. You are
good at your job, but you don't know the code base terribly well. You're
debugging. This is a nasty one, you're really stuck, and you're stepping
through the code. All is well so far, and you get to this bit:

world.foo = 42;

seemingly_innocent_function();

baz = (world.foo + 117) / 35;

It seems obvious that baz should now have the value 4, but your debugger
tells you it has the value 6.

Finally, you twig that seemingly_innocent_function() must have updated
world.foo somehow. So you go digging around, and discover to your horror
that seemingly_innocent_function() is a wrapper around several thousand
lines. Nothing for it but to dig in and look for references to world.foo -
or maybe there's a world.p that is used to point to any of various world.*
objects - so maybe you have to look for that, too, and the whole mess gets
very wearying indeed. And you will find it almost impossible to decouple
seemingly_innocent_function() for use in other programs.

When you pass the data a function needs via its parameters, it makes it far
easier to track changes to the values of objects, and far easier to
separate out code for later re-use in a different context.
 
J

jaso

Richard said:
> [snip]
>
> When you pass the data a function needs via its parameters, it makes it far
> easier to track changes to the values of objects, and far easier to
> separate out code for later re-use in a different context.
>

Ok, now I understand why to avoid the world structure. Thanks!
So, generally, the best way is to declare all structures separately in a
"high layer" like the game loop, and to pass all needed structures
all the way down to the lowest level functions?
 
R

Richard Heathfield

jaso said:
Richard said:
[snip]

When you pass the data a function needs via its parameters, it makes it far
easier to track changes to the values of objects, and far easier to
separate out code for later re-use in a different context.

Ok, now I understand why to avoid the world structure. Thanks!
So, generally, the best way is to declare all structures separately in a
"high layer" like the game loop, and to pass all needed structures
all the way down to the lowest level functions?

Yes, sort of. Ideally, each function should only be given the information it
actually needs for doing its job. So try to arrange your structures in such
a way as to minimise the amount of superfluous information you hand to your
functions. And localise your data as much as possible. If you must define a
structure in main(), okay - if you must, you must. But at least investigate
the possibility of making it more local than that.
 
R

Richard Tobin

Richard Heathfield said:
Yes, sort of. Ideally, each function should only be given the information it
actually needs for doing its job. So try to arrange your structures in such
a way as to minimise the amount of superfluous information you hand to your
functions. And localise your data as much as possible.

None of this means that you shouldn't have a structure that gives
access to the entire state. It may well be useful to do this so that
you can easily pass it to a function that, say, saves the game. But
don't make it a monolithic structure - have it contain pointers to
other structures that make sense in themselves: lists of objects or
people for example.

And as has been said, don't make it a global variable. Apart from
the possibility of hidden modification, maybe one day you will want to
make a multi-player version of the game in which there are several
such structures.

-- Richard
 
A

Andrew Poelstra

Ridiculous. Well designed to make the end result unmaintainable.

In my code, it's fairly obvious what globals are being changed (I never
have more than three or four) in which functions.

If there's ambiguity (or a situation where I'll be maintaining instead of
rewriting), I'll comment what variables are changed, and why.
 
D

Default User

Richard said:
jaso said:

Yes, sort of. Ideally, each function should only be given the
information it actually needs for doing its job. So try to arrange
your structures in such a way as to minimise the amount of
superfluous information you hand to your functions. And localise your
data as much as possible. If you must define a structure in main(),
okay - if you must, you must. But at least investigate the
possibility of making it more local than that.

In my reworking of my text adventure game (a programming project 15
years old and still going!) I've tried to limit most entities' access
to game state data.

One way of doing this is to sort of borrow a concept from OO
programming. I move a part of the state data into a separate
translation unit, and make it static within there. There are a
relatively few public functions for which can access the data only in
certain predefined ways, and a bunch of static helper functions to
accomplish this.

So, for instance, no other part of the game can directly manipulate the
player inventory. They can use the public functions to query for an
item, add one, drop one, output the list, etc. This allows changing the
underlying data structures to a large degree without affecting the rest
of the game.


Brian
 
M

Malcolm

Richard Heathfield said:
Yes, sort of. Ideally, each function should only be given the information
it
actually needs for doing its job. So try to arrange your structures in
such
a way as to minimise the amount of superfluous information you hand to
your
functions. And localise your data as much as possible. If you must define
a
structure in main(), okay - if you must, you must. But at least
investigate
the possibility of making it more local than that.
When you are wriitng world simulators this approach, though generally a good
idea, doesn't work.
The problem is that real world objects do communicate their state to each
other. If I walk into a pool of hot water, the water state communicates with
my nerves and the block of butter I am carrying in my knapsack.
So the only way to handle things, in practise, is to have a "world"
parameter that is apssed to every function. That is not to say that you
don't try to communicate through interfaces as much as possible, but
inevitably someone will throw a curveball that you didn't think of some time
during development.The demon turns red when it is about to attack, but it
doesn't look nice against a red wall. So if the wall is red, just turn off
the attack routine. Great idea, but now the attack locic needs access to the
wall pixel data.
 
J

jaso

Malcolm said:
When you are wriitng world simulators this approach, though generally a good
idea, doesn't work.
The problem is that real world objects do communicate their state to each
other. If I walk into a pool of hot water, the water state communicates with
my nerves and the block of butter I am carrying in my knapsack.
So the only way to handle things, in practise, is to have a "world"
parameter that is apssed to every function. That is not to say that you
don't try to communicate through interfaces as much as possible, but
inevitably someone will throw a curveball that you didn't think of some time
during development.The demon turns red when it is about to attack, but it
doesn't look nice against a red wall. So if the wall is red, just turn off
the attack routine. Great idea, but now the attack locic needs access to the
wall pixel data.

Indeed. I've thought alot since my first post about the design, and if
I am going to avoid globals I need to pass a lot of data as arguments
to my functions. And a world structure would free me from the uglyness
of 6+ argument functions. For example, if I have a update_enemies()
function that moves the enemies and make them shoot bullets at the
player, I would have to declare something like
update_enemies(struct enemy *e, int num_enemies, struct bullet *b,
int num_bullets, struct player *p, int num_players);
And if I add things like powerups or weapons I need to add more
arguments if I want the enemies to interact with them. IMHO,
that would be pretty messy.
A call like update_enemies(world) is cleaner, and then from that
function I can call functions that don't need all world data like this
enemy_shoot(w->enemy, w->player);

Does that sound reasonable, or am I way off?
 
A

Andrew Poelstra

When you are wriitng world simulators this approach, though generally a good
idea, doesn't work.
The problem is that real world objects do communicate their state to each
other. If I walk into a pool of hot water, the water state communicates with
my nerves and the block of butter I am carrying in my knapsack.
So the only way to handle things, in practise, is to have a "world"
parameter that is apssed to every function. That is not to say that you
don't try to communicate through interfaces as much as possible, but
inevitably someone will throw a curveball that you didn't think of some time
during development.The demon turns red when it is about to attack, but it
doesn't look nice against a red wall. So if the wall is red, just turn off
the attack routine. Great idea, but now the attack locic needs access to the
wall pixel data.

Well, you could have a "get_sight(WORLD_OBJECT self)" function,
which returns a struct or an array containing every object visible to the
caller. You could extract red wall data from there, and it would be basically
the same as the AI which allows the character to attack at all.
 
R

Richard Bos

jaso said:
How come? Why will it be unmaintainable?

Some people are scared of global data and want to pretend that it is a
local to main(). To avoid this abhorrence, they put all their data in a
struct in main(), and then pass lots of pointers to every non-trivial
function. The result is that you still don't know which function
modifies which member of the struct, and you have to pass world pointers
everywhere. Big win :-/.
IYAM, if data is global by nature, make it global by declaration, too.
If 90% of your functions are going to refer to the "monsters in the
dungeon" array, why pretend that it's not a global object?

Richard
 
A

Andrew Poelstra

Some people are scared of global data and want to pretend that it is a
local to main(). To avoid this abhorrence, they put all their data in a
struct in main(), and then pass lots of pointers to every non-trivial
function. The result is that you still don't know which function
modifies which member of the struct, and you have to pass world pointers
everywhere. Big win :-/.
IYAM, if data is global by nature, make it global by declaration, too.
If 90% of your functions are going to refer to the "monsters in the
dungeon" array, why pretend that it's not a global object?

The only functions to which I would pass an entire struct would be
a few initialization or cleanup functions.

Everywhere else, I pass specific members of the struct. I prefer the
struct system because it means that I can go to a header file and see
all of my members in one place.
 
R

Richard Bos

Andrew Poelstra said:
The only functions to which I would pass an entire struct would be
a few initialization or cleanup functions.

Everywhere else, I pass specific members of the struct.

How does that help when 80% of all major functions need to read that one
member, and some 30% want to change it?

Richard
 
M

Malcolm

[ world structures in video games ]
The only functions to which I would pass an entire struct would be
a few initialization or cleanup functions.

Everywhere else, I pass specific members of the struct. I prefer the
struct system because it means that I can go to a header file and see
all of my members in one place.
You need to pass the entire struct, but not necessarily to access it.
The problem is that in the real world there are no barriers to information
flow, and requirements chance.
If a games designer says "stop the demon from attacking if it is standing
next to a red wall" he doesn't want to be told that we can't achieve this
because the attack logic is in the gameplay module whilst the wall pixel
data is locked up in the texture cache. The information is in the computer,
so he sees his request as reasonable.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,183
Messages
2,570,969
Members
47,524
Latest member
ecomwebdesign

Latest Threads

Top