Sign in to follow this  

Design Question

This topic is 3502 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

In my current game project I have a bullet object. In some cases I would like to be able to split the object into multiple bullets, sort of like a delayed tri-shot sort of thing. This brings up two design questions. 1. Is this bullet type worthy of a new class, or should I simply add the code to the existing bullet class? 2. How should I implement keeping track of these bullets? My game class has vectors contain the various object types. Tanks, bullets, and explosions. I can't think of a graceful way to add more bullets to the bullet-vector in the game class. Any help would be very appreciated! Thanks. You can also talk to my on AIM or GTalk AIM:ZackGomez GTalk:starruler@gmail.com -edited to add IM handles-

Share this post


Link to post
Share on other sites
You don't want to actually split the object (bullet) to create 3 separate bullets. Instead, you want to create 3 separate and independent bullets. Wherever the code is that creates and fires the bullets, you want to edit that to create 3 bullets instead of only 1. Then, calculate the trajectories of each bullet to be displaced from the previous bullet by a small amount.

Quote:

1. Is this bullet type worthy of a new class, or should I simply add the code to the existing bullet class?


No and no. You should have a Bullet class that does only 1 thing: simulates a Bullet. Bullets don't split into more bullets, so neither should your Bullet class.

Quote:

2. How should I implement keeping track of these bullets? My game class has vectors contain the various object types. Tanks, bullets, and explosions.


By simply creating 3 Bullets instead of 1 when you fire the weapon you don't need to change your data structures; all you're doing is adding more Bullets to the data structures.

Quote:

I can't think of a graceful way to add more bullets to the bullet-vector in the game class.



void GameClass::addBullet(Bullet b){
bullet_vector.push_back(b);
}


EDIT:
Quote:

...sort of like a delayed tri-shot...


Do you actually mean delayed? As in you fire a single shot from your weapon that travels for 30 yards and then splits into multiple shots that then continue to travel away?

Share this post


Link to post
Share on other sites
Yeah I do mean delayed. Like when the projectile gets to its maximum height it splits into three different projectiles with different x-velocity components.
As for part 1 I'll subclass the projectile into a SplittingBullet or something similar when I figure out how to implement it.

Share this post


Link to post
Share on other sites
It sounds like maybe you do want a subclass of your Bullet class called SplittingBullet or something. SplittingBullet would only override one of Bullet's functions, the "update" function responsible for doing the bullet's logic every frame.

Share this post


Link to post
Share on other sites
Quote:
Original post by starruler
>two design questions.
>1. Is this bullet type worthy of a new class, or should I simply add the code to the existing bullet class?
>2. How should I implement keeping track of these bullets?

This must be some new meaning of the word "design" of which I'd previously been unaware.

Share this post


Link to post
Share on other sites
I understand the subclassing, however the new class's update function would be horrid if it had to keep track of the projectiles after the split. It would have to be responsible for the update and exploding of all three projectiles, logic that I think should be in the main game loop.

Quote:
Original post by Tom Sloper
Quote:
Original post by starruler
>two design questions.
>1. Is this bullet type worthy of a new class, or should I simply add the code to the existing bullet class?
>2. How should I implement keeping track of these bullets?

This must be some new meaning of the word "design" of which I'd previously been unaware.


What is the correct term for the questions I'm asking?

Share this post


Link to post
Share on other sites
Well you could have something like:

class Bullet
{
...
Bullet(init Position, init trajectory .... );
void Update BulletPosition();

};

and then

class BigBullet : public Bullet
{
...
CheckDelaySinceShot()
{
if(TimeElapsedSinceShot > WantedDelay)
CreateThreeNewBullets(CurrentPosition,NewTrajectory...)
}
};

My Reasoning: A bullet is just a bullet and should only worry about itself and what it does: travel at some trajectory and speed.
So.... should a regular bullet worry about splitting? No! A regular bullet does not split. It is more than just a bullet... Dun da dun!!! derived class!!!

A class should fulfill its purpose and nothing more. Derived classes may add functionality to fulfill their own purposes. So if you want a bullet that splits, make a new class that spawns three base class bullets after the delay.
Hope this helps you,

Sab

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

-EDIT-
Quote:
CreateThreeNewBullets(CurrentPosition,NewTrajectory...)

I'm asking the best way to accomplish this task.

Share this post


Link to post
Share on other sites
Some game engines are built on messaging systems that would pass a message to the system to create the bullets and add them to the 'system' array.

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

Share this post


Link to post
Share on other sites
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.


Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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.




Share this post


Link to post
Share on other sites
I'd go with the pass-reference-on-creation method:


// In the class def
protected: BulletCollection& bulletCollection;

// The constructor
SplittingBullet::SplittingBullet(BulletCollection& bc) : bulletCollection(bc)
{
/* ... */
}

// The update
void SplittingBullet::Update()
{
if(ShouldSplit())
{
bulletCollection.add(CreateSplitBullets());
bulletCollection.remove(this);
}
}

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
Quote:
Original post by superpig
I'd go with the pass-reference-on-creation method:


// In the class def
protected: BulletCollection& bulletCollection;

// The constructor
SplittingBullet::SplittingBullet(BulletCollection& bc) : bulletCollection(bc)
{
/* ... */
}

// The update
void 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)?

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites

This topic is 3502 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this