Friday, November 11, 2011

I'm not dead! Enemies added!

I apologize, it's been a while since I updated.  I have been busy, but I finally am able to get back to this.

And, the big news is, I've added our 1st enemies!  OK, they're REALLY stupid, they just kind of move towards the player, shooting occasionally.  Anyway, onto the code.

Here's a link to the code:
SmashPc Code 11-12-11

Before I get to the good stuff, I did a little more house cleaning..  I moved all the objects that live in the game (Items, Bullets, and Enemies) into the SmashPcData object, and let it keep the lists and call the update functions.  So, now my main loop looks like this:

// main game loop while (App.IsOpened()) { pOurPlayer->CheckInput(); cpSpaceStep(pMap->GetSpace(), 1.0f/60.0f); /* clear screen and draw map */ App.Clear(sf::Color(200, 200, 200, 255)); pMap->Draw(pOurPlayer->GetBody()); // Update all the bullets in the world GameData.UpdateBullets(pOurPlayer->GetBody()); // check for items as well GameData.UpdateItems(pOurPlayer->GetBody()); // Finaly update all enemies GameData.UpdateEnemies(pOurPlayer->GetBody()); pOurPlayer->Draw(); App.Display(); if (CheckGameEnd(&App)) { App.Close(); break; } }

You can see it's pretty simple, and probably won't have much more to add.  I suppose handling a dead player and changing levels.

So, how do Enemies get added?  In the level, I have "EnemySpawn" items, and they spawn enemies at a given rate.  The plan is to give these spawn points levels, and the different enemy levels will make the enemies smarter, faster, tougher, etc.

Here's the piece from SmashPcItem.xml for EnemySpawn1 (for level 1 enemies):
<EnemySpawn1 Type="EnemySpawn"> <Interact>No</Interact> <Value>50</Value> <ReleaseRate>5000</ReleaseRate> <EnemyLevel>1</EnemyLevel> <Rotate>0</Rotate> </EnemySpawn1>

Which means we release a level 1 enemy every 5 seconds until 50 have been released.

So, in SmashPcItem::Update(), we have this (Let me say, I really need to inherit from SmashPcItem, and make a SmashPcEnemySpawn class, but that'll be later):

BOOL SmashPcItem::Update(void) { if (mu32TimeToLive) { if (timeGetTime() > mu32TimeToLive) { // Mark Item for deletion mbActive = FALSE; GameSound::Play("Despawn"); return FALSE; } } // 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; }

And, our enemy class looks like this (I'm including it all 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; }

OK, some explanation. In the constructor, we setup the specific physics attributes, including the collision handler.  We're basically saying we want the enemy to get a callback when it's hit by the player's bullet.  The Layer is set so it will be in the same collision layer as the player and the player bullets, even though we only want to get notified of hitting bullets (see chipmunk-physics for more on collision layers).

In SmashPcEnemy::Update(), we move the enemy towards the player every so often.  Notice we don't reset the velocity when we change the force, so the enemy will somewhat slowly change direction, continuing on it's previous path until the force can change it's direction fully.  This will change for different "levels" of enemies, but this is it for now.

The BulletCollision() simply calls the enemies NotifyHit(), with the bullet it hit, and in NotifyHit(), we reduce the health of the enemy.

Finally, there is no specific enemy Draw, it uses the PhysicsObject::Draw() by default (the inherited class).

The other thing I did was add a Collision Handler to the SmashPcPlayer class, which I didn't have before.  It's basically identical to the Enemy's, except we subtract the damage from the armor 1st if the player has armor.

Well, this results in some bad enemies.  I'll have to adjust the AI some, but here's some pics.  The first one, I just let the enemies pile up, and watched em run around.  It's def a group mentality.

OK, until next time!

No comments:

Post a Comment