Design Question

Started by
17 comments, last by superpig 15 years, 11 months ago
Quote:Original post by starruler
Quote:Original post by Ravyne
The Splitbullet class wouldn't be responsible for tracking the three bullets, it would simply spawn the additional bullets at it's current location.

If the 3 bullets "fan out" eg: one breaks left, one right, and one stays on course, then you might just want to spawn the left and right as regular bullets and just let the SplitBullet continue it's course. Alternatively, you can adjust the course of the SplitBullet to a new trajectory, or simply remove it and replace it with a standard Bullet, depending on what you're most comfortable with.


Right, but how do the spawned bullets get added to the game class's bullet vector so they can be updated? I could pass the vector to the update function of the bullet, or maybe have the game object be global. Actually the game object being global doesn't seem like a terrible idea.


Actually, making the game object global just for the simple ability to have a bullet get access to the bullet list (or bullet factory, or what-have-you) is a terrible idea. Think of all the things you'd be making globally available -- do you really want to make all that global just so you can have one specific object access to another specific object?

You have some other options --

  1. Pass a reference to the bullet list/factory/etc to each bullet on creation.

  2. Have bullet hold a static member specifying the list. That way, the reference will only exist in one place rather than being duplicated per bullet. You'll need to initialize this variable after the list/factory/whatever has been created, and this will restrict a given bullet class to one specific list/factory/whatever. This is probably acceptible, but there are ways around it if you need, such as creating a BulletGroup class or templatizing bullets based on their list/factory/whatever.


throw table_exception("(? ???)? ? ???");

Advertisement
Quote:Pass a reference to the bullet list/factory/etc to each bullet on creation.

So like vector<Bullet> Bullets.push_back(Bullet(pos, vel ..., Bullets)? And then let the bullet objects operate on the vector? (I use a vector to hold all of the bullets in the game class)

Quote:If this is something really simple, I would suggest that you make a System class that encapsulates all the global variables. So if you wanted to spawn more bullets, you would just go System.CreateBullet() three times


So a class like
class System
{
private:
Game theGame;
...
public:
createBullet(pos, vel...)
...
}

And then in main.cpp I would create a System object and call it's init function or something like that to create the game object. Is that something like what you're suggesting?
I assume you have an object,say World, that contains all the entities of the game. That class would probably have a method that creates bullets:

class World
{
void CreateBullet(Vec3 pos);
};


What we need now, is a way for a SplitBullet to "tell" the world to create 3 new bullets. There are a few ways to do this:

1)Make the World global. That way your bullet can just do World.CreateBullet(...). The downside is that you increase coupling(the bullet code is tied to the particular global instance), and also the instance can be accessed and changed by your whole code, which makes bugs harder to track.

2)Pass a World instance as reference when you create a bullet, and store it. Now, inside a SplitBullet method, you can do this->world->CreateBullet(...). The downside is that you create a circular dependency, and that you give the bullet access to World parts that it doesn't need, since it can call every method of World, not just CreateBullet. What's worse, the bullet can't be reused by any other higher-level modules other than World.

3)Pass the bullet list(or vector,or...) as a parameter to the bullet. While this reduces dependencies, it kind of exposes the implementation details of World to the bullet. For example, if you use a vector and then for some reason you decide to use a list, you have to change not only the World code, but also the bullet code.Also, the bullet, having control over the list, can do unwanted changes to it(like delete items).

4)Have a IBulletCreator interface with functions like CreateBullet(...), which the World implements. The SplitBullet just asks for an IBulletCreator. We still have a circular dependency, but at least the Bullet now doesn't need a World instance; it can be reused by any higher-level module that implements IBulletCreator. The downside is that you add one more class in the hierarchy, which is rarely a good thing.

5)Simply pass the CreateBullet function as parameter when you create the bullet. For example, in C++(boost::function for niceness, in other languages it could be much more elegant):

class SplitBullet{  private:    boost::function<void(Vec3)> bullet_creator;  public:    SplitBullet(boost::function<void(Vec3)> bullet_creator):bullet_creator      (bullet_creator)    {}    void Update(float dt)    {        ...        if (must_split)        {          bullet_creator(...);           bullet_creator(...);           bullet_creator(...);         }        ...                }}


Now, all the SplitBullet needs is the method passed to it. For example:

split_bullet=SplitBullet(boost::bind(world->CreateBullet,_1))

This way is much better than the others, since it does exactly what we want, no more, no less. The bullet gets only the functionality it needs, and it doesn't need a World instance(global or passed by reference) to function, it just needs a function that "creates" bullet. The Bullet class can be reused by any higher-level module that has a function like that.

6)Use a message passing system, that allows the subsystems to communicate via message pipes. When creating a bullet, we pass to the Bullet the one end of the pipe, while the other is connected to the world. When the SplitBullet wants to create a new bullet, it can pass a message: pipe->SendMessage(CREATE_BULLET,...)
Again, this reduces dependencies, a bullet does not need a World object, only a pipe provided to it.

[Edited by - mikeman on May 14, 2008 3:58:06 PM]
You would use this method only if the project was simple and small.
Once you move into making games with sounds, music, physics, networking etc... then i would strongly suggest you implement a message passing system like suggested above.

But for now, try to use the 'world' class.

You should make a base class of 'object' for example, and then derive classes into static and animated objects. Each object would take care of its own position with an update function

Your 'world' class would have a vector of 'object' pointers to keep track of all the worldly objects and call their update functions.

You would just need a way to get the 'world' to create and add these objects into the vectors.

Here, you have some choices.
- You can make a callback function that would be provided to the bullet class
- You can create a global event queue which the bullet would add to and the 'world' would read from.
- Make the 'world' instance global so you can call the create() function from anywhere.


The bigger the project, the messier this becomes and should be changed into a message passing system.




"Only two things are infinite, the universe and human stupidity, and I'm not sure about the former." - Albert Einstein (1879-1955) That is so very true...
I'd go with the pass-reference-on-creation method:

// In the class defprotected: BulletCollection& bulletCollection;// The constructorSplittingBullet::SplittingBullet(BulletCollection& bc) : bulletCollection(bc){ /* ... */}// The updatevoid SplittingBullet::Update(){ if(ShouldSplit()) {  bulletCollection.add(CreateSplitBullets());  bulletCollection.remove(this); }}

Richard "Superpig" Fine - saving pigs from untimely fates - Microsoft DirectX MVP 2006/2007/2008/2009
"Shaders are not meant to do everything. Of course you can try to use it for everything, but it's like playing football using cabbage." - MickeyMouse

It is common in games to have a layer(a class) that is global to the entire game. When the program is executed, this global layer (Application layer) is responsible for creating the Logic layer (the guts of the game). Therefore, the Application layer will have a pointer to the Logic layer. The Logic layer will have the CreateBullet() method. Then, your SplittingBullet will just need to grab the pointer to the Application layer, then grab the pointer to the Logic layer, then call the CreateBullet() function.

This is not the same as making a bunch of globals or as "hiding globals in a Singleton." The Application layer does a lot more than just hiding a global pointer to the Logic in a class that is accessible globally. The Application layer will hold all the code that is platform-specific. In that sense, ideally you would only have to change the Application layer when you wanted to port your game from a Windows platform to a UNIX platform.

Without enough experience making games, programmers tend to roll the Application layer and Logic layer together (which you've probably done in your game), but separating the two really proves to be beneficial.

But, as with everything in programming, this is only one way to handle game architecture.
Quote:Original post by superpig
I'd go with the pass-reference-on-creation method:

// In the class defprotected: BulletCollection& bulletCollection;// The constructorSplittingBullet::SplittingBullet(BulletCollection& bc) : bulletCollection(bc){ /* ... */}// The updatevoid SplittingBullet::Update(){ if(ShouldSplit()) {  bulletCollection.add(CreateSplitBullets());  bulletCollection.remove(this); }}


Why? A Bullet doesn't need to know about other Bullets in the game. Plus, passing around an entire collection of Bullets (even though it is a reference) seems to be undesirable and unnecessary.

Or are you just suggesting this solution because it would probably require the least amount of work to implement (in this particular case)?
Quote:
Why? A Bullet doesn't need to know about other Bullets in the game.


It doesn't need to know about the whole Application and Logic class, either, or everything that they contain. Same with a lot of components that don't need to know about them, but they can nevertheless, since they're globally accessible in your design.

Quote:
Plus, passing around an entire collection of Bullets (even though it is a reference) seems to be undesirable and unnecessary.


Why?
Quote:Original post by Shakedown
Why? A Bullet doesn't need to know about other Bullets in the game. Plus, passing around an entire collection of Bullets (even though it is a reference) seems to be undesirable and unnecessary.
It's a SplittingBullet, not a Bullet, so it does need to know about other bullets in the game - specifically, the three that it creates - and in any case, I never said it would know about other bullets in the game. All I've said is that it's got a reference to some collection through which it can add or remove bullets. There is no suggestion that it can iterate through them. "BulletCollection" isn't the best name in the world for it; mikeman suggested 'IBulletCreator' which may have been better.

Quote:Or are you just suggesting this solution because it would probably require the least amount of work to implement (in this particular case)?
I'm suggesting this solution because it has the smallest impact on the rest of the codebase, the lowest potential for abuse, and it's a fairly close representation of what (semantically) we're actually trying to solve here. My first alternative would be mikeman's solution #5.

Richard "Superpig" Fine - saving pigs from untimely fates - Microsoft DirectX MVP 2006/2007/2008/2009
"Shaders are not meant to do everything. Of course you can try to use it for everything, but it's like playing football using cabbage." - MickeyMouse

This topic is closed to new replies.

Advertisement