Multiple instances of one class

Started by
14 comments, last by mypel16000 11 years, 5 months ago
HI, I'm using C++ and SFML, I wanted to make a zombie game where 2 players start at the middle of a map and zombies come from the sides. I have already done my man's animation and screen scrolling, but I don't know how to do this. I am confident with the rest of c++ and sfml, but I would like someone to explain to me, in detail, how I could make multiple enemies, how to shoot multiple projectiles, make the enemies die when they are hit 3 times.....

Thank you
Advertisement
mypel16000,

You should check out my Old Blog (also linked in my sig). I detail the steps for making a top-down 2d game similar to what you are suggesting (player starts in map, enemies spawn from an "Enemy Spawn", bullets shooting everywhere).

(Here's the link to the entry on adding enemies to my game: Click Here)

In general, my Enemy Spawn's are considered an item, and I update it like this:


BOOL SmashPcItem::Update(void)
{

// Release Enemies into the level
if (mpItemDetails->Type == "EnemySpawn")
{
if (mu32NumReleaseLeft && (mu32LastRelease + mpItemDetails->u32ReleaseRate < timeGetTime()))
{
SmashPcEnemy *pEnemy;
mu32LastRelease = timeGetTime();
mu32NumReleaseLeft--;
// Create an enemy
pEnemy = new SmashPcEnemy(mGameData, mpApp, mpSpace,
mpBody->p, mpItemDetails->u32EnemyLevel);
// Check if this enemy should release something
mGameData.AddActiveEnemy(pEnemy);
}
}
return mbActive;
}


Which basically says, if there are still enemies to release, and if it's time to release a new enemy, then create a new enemy, and add him to the list of active enemies. Here's what the enemies actually do (the full Enemy class here):

/******************************************************************************
*
* SmashPcEnemy() - Creates a player, and places him in the space
*
******************************************************************************/
SmashPcEnemy::SmashPcEnemy(SmashPcData &GameData,
sf::RenderWindow *pApp, cpSpace *pSpace,
cpVect Location, U32 u32Level) :
PhysicalObject(pApp, pSpace, "Gfx/blueenemy.bmp", "",
Location, PI/2.0f, 0.0f, TRUE),
mu32Health(20*u32Level),
mu32LastFire(timeGetTime()),
mu32LastMoveUpdate(0),
mGameData(GameData)
{
printf("Enemy created!\n");
// Set the Velocity limit to our fake movement
mpBody->v_limit = ENEMY_SPEED;
mpShape->collision_type = ENEMY_COL_TYPE;
mpShape->layers = ENEMY_PLAYER_LAYER;
mpShape->data = (void *)this;
// Create Collision handler here
cpSpaceAddCollisionHandler(mpSpace, ENEMY_COL_TYPE, BULLET_COL_TYPE, SmashPcEnemy::BulletCollision, NULL, NULL, NULL, NULL);
// Sound notifying a player has spawned
GameSound::Play("EnemySpawn");
}
/******************************************************************************
*
* ~SmashPcEnemy() - Removes a player
*
******************************************************************************/
SmashPcEnemy::~SmashPcEnemy()
{
}
/******************************************************************************
*
* Update() - Do enemy things
*
******************************************************************************/
void SmashPcEnemy::Update(cpBody *pPlayerBody)
{
U32 u32Time = timeGetTime();
// check if we need to update the movement
if (mu32LastMoveUpdate + ENEMY_UPDATE_MOVE < u32Time)
{
cpFloat Angle = atan2(pPlayerBody->p.y - mpBody->p.y,
pPlayerBody->p.x - mpBody->p.x);
// add randomization to angle
cpFloat RandomDegrees = (cpFloat)(((S32)rand() % 20) - 10);
Angle += (RandomDegrees*PI)/180.0f;
cpBodySetAngle(mpBody, Angle);
// Change force towards player
mForce = cpv(ENEMY_FORCE*cos(Angle), ENEMY_FORCE*sin(Angle));
//mpBody->f = cpv(ENEMY_FORCE*cos(Angle), ENEMY_FORCE*sin(Angle));
mu32LastMoveUpdate = u32Time;
}
mpBody->f = mForce;
if (mu32LastFire + ENEMY_REFIRE_TIME < u32Time)
{
SmashPcData::tBulletList BulletList;
mGameData.GetBulletList(BulletList);
SmashPcBullet *pBullet = new SmashPcBullet(mpApp, BulletList[0],
mpBody, mpSpace, FALSE);
mGameData.AddActiveBullet(pBullet);
mu32LastFire = u32Time;
}
}
/******************************************************************************
*
* NotifyHit() - Notify the enemy it has been hit by a bullet
*
******************************************************************************/
void SmashPcEnemy::NotifyHit(SmashPcBullet *pBullet)
{
if (mu32Health < pBullet->GetDamage())
{
// Enemy Dead
mu32Health = 0;
// Play dead sound
}
else
{
GameSound::Play("PlayerHit");
mu32Health -= pBullet->GetDamage();
}
pBullet->SetDead();
}
/******************************************************************************
*
* BulletCollision() - Callback for when Enemy hits a bullet
*
******************************************************************************/
int SmashPcEnemy::BulletCollision(cpArbiter *arb, struct cpSpace *space, void *data)
{
SmashPcBullet *pBullet;
SmashPcEnemy *pEnemy;
cpShape *pBulletShape, *pEnemyShape;
cpArbiterGetShapes(arb, &pEnemyShape, &pBulletShape);
pEnemy = reinterpret_cast<SmashPcEnemy*>(pEnemyShape->data);
pBullet = reinterpret_cast<SmashPcBullet*>(pBulletShape->data);
// We have the enemy that hit, and the bullet he hit
pEnemy->NotifyHit(pBullet);
return 0;
}


I updated my enemies later on to make them a little smarter and to shoot better, but you get the picture.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

If I understand correctly, the original poster is asking how to handle an arbitrary number of objects such as enemies, provided he has the correct logic to handle one.

You can stick all the objects of that type in a single container, like std::vector<Enemy> allEnemies. Whenever you create a new enemy, you add it into that container like so: allEnemies.push_back(... your new enemy object ...). Whenever an enemy dies, you remove it from that container. To handle things like drawing all of them and having all of them take action, you just loop through all of the objects in the container on every game tick, like so:

for(std::vector<Enemy>::size_type i = 0; i<allEnemies.size(); ++i) { allEnemies.draw(); }
or so:
std::for_each(allEnemies.begin(), allEnemies.end(), [](&Enemy e){ e.draw(); });
or so:
for(auto e : allEnemies) {e.draw();}
Thank you two, I find your posts very helful. BeerNuts,I don't really understand some of your code, but I get the basics. Stroppy Katamari, yours really gives a good insight on what to do, but still, can you get me out of doubts:

- So to create enemies, should I just use my player class and add an AI function to it? How could I do this AI? After that should I just create, say, 10 enemies in an object array and loop through their functions just like the normal player(only that using AI instead of handle Input).
- And how could I make projectiles, you still havent talked about that.
- So to create enemies, should I just use my player class and add an AI function to it?
Where in the code you should create new enemies depends on the logic you intend the game to have. Is the player avatar in the game world actually creating enemies? Probably not.

For instance, if you want enemies to spawn randomly over time, just put a function in your main game loop which does that. Assuming you want an average of one new enemy every 3 seconds, and your game runs with 60 logic ticks a second, the function should spawn an enemy 1/(3*60) of the time and do nothing the rest of the time.

- And how could I make projectiles, you still havent talked about that.
[/quote]Who's shooting? If the player, then the player class' game logic should make new projectiles. If an enemy is shooting, the enemy class' game logic should make new projectiles. Put a tick() function on everything in the game and just call that function on every object once per game tick, just like the draw() function above. Enemies' tick() function will contain the enemies' AI, moving them around and doing stuff. Simple projectiles' tick() function will just keep moving the projectile in the direction it was fired, and perhaps count down to a time or distance where the projectile dissipates and removes itself from the game.

You can handle projectiles like you handle the enemies: place them in their own container when you create them, remove them from the container when they are supposed to die.
For projectiles you may start simply. As stated before you may keep a vector or list of your projectiles and add projectile(s) when someone is shooting or remove projectile(s) when projectile has hit something or has timed out) objects as necessary.

Remember that your player / enemy doesn't own the projectiles. Keep all the projectiles in the one and same list.

Cheers!
I would recommend you to make Player and Zombie both subclasses of a base class (that could be named Person, for example). Person should have functions like Draw() and Update() (these are usually needed for every displayable object), but only the Player should have function HandleInput() and others. You can then make an array of Zombies or an array of pointers to Persons (because of variable size of the derived classes) and then call Draw() and Update() in respective loops.

Bullets are just like Zombies: You create their constructor, Draw() and Update() function. You make a container of Bullets and loops to call Draw() and Update(). And when the player or somebody else fires their gun, you construct a new Bullet using their position, heading et cetera.

or so:
for(auto e : allEnemies) {e.draw();}


isn't this better?
[source lang="cpp"]for(const Enemy & e: allEnemies){e.draw();}[/source]
An invisible text.
Ok, thank you for all these ideas, but could someone explain to me how to make a vector containing items and how to add and delete elements from it. Also how could I make the projectiles functions. Give me some code please!

[quote name='Stroppy Katamari' timestamp='1351803398' post='4996306']
or so:
for(auto e : allEnemies) {e.draw();}


isn't this better?
[source lang="cpp"]for(const Enemy & e: allEnemies){e.draw();}[/source]
[/quote]Yes, I forgot the const and &.

This topic is closed to new replies.

Advertisement