I know the title may seem fairly confusing, was just unsure on how to ask this...
So, I'm working on a basic sample game (not going to be a complete game or anything), where you can move around and are chased by an enemy character that attacks you. The problem is that when the attack function is called, instead of only removing one heart/hitpoint, they continue to be 'spam removed'. Here's what I'm working with...
void Enemy::attackPlayer()
{
if (distance < 50)
{
Player::health--;
return;
}
}
Pretty simple, right? Well the problem is that I need some way of I guess 'sleeping' the single function so that instead of continuing to remove health, it stops after one, then after let's say, 3 seconds, allows another attack to occur.
I think you can create two global time variables that are passed to your attack function. startTime is initiated once you call your attack function (outside). endTime is initiated right after removing one health from player (inside your function). Then you simply add a if statement before the distance if statement to check the delta time between these two and if they are more than 3 seconds then do the rest to remove another health.
You could probably have the Enemy class contain a method like:
bool canAttack(){
if(attackTimer >= 3000){
attackTimer = 0;
return true;
}
return false;
}
Then you could modify your damage condition to be something like:
if (distance < 50 && canAttack())
Of course, you would have to add a timer to the Enemy class and have it start and stop based on proximity to the player.
I'm sure there is a better way to handle this--also, this depends a bit on the implementation of the rest of your code. If you are using something like SFML, there is a built-in event system that would make this a bit easier to handle. Hopefully this helps a bit!
After taking some of the answers you guys gave me into consideration and messing around with some things by myself, I've came up with a pretty simple solution:
int Enemy::attackTime = 0;
And then...
void Enemy::attackPlayer()
{
if (distance > 60)
return;
if (time(0) > attackTime)
{
attackTime = time(0) + 3;
Player::health--;
}
}
I guess, player won't get another attacked from any enemy for 3 seconds. However, enemy can attack to another player if exist. Thus, this timer variable is keep into Player class. If I am correct, I think this code will work.
class Player
{
private:
uint32_t last_attack_timer;
...
public:
void set_last_attack_timer(uint32_t timer){this->last_attack_timer = timer;};
uint32_t get_last_attack_timer(void){return last_attack_timer;};
...
}
void Enemy::attackPlayer()
{
uint32_t timer = time(0);
if (distance < 50 && timer-Player::get_last_attack_timer>3000)
{
Player::health--;
Player::set_last_attack_timer(timer(0));
return;
}
}
I'm working on a game in C++, and have a player object and a bullet object. I have a std::vector collection for both kinds of object, defined earlier in my code like so:
std::vector<Player*> players;
std::vector<Bullet*> bullets;
My problem happens in my main game loop, where I do all of my update logic. Using a for loop to check each player and update him, I get any bullets he has spawned and add them to the bullets vector.
// update players
for (int i = 0; i < players.size(); ++i){
Player *player = players[i];
player->Update(delta);
// get bullets
for (int b = 0; b < player->Bullets.size(); ++b){
// THIS IS WHERE THINGS GET WEIRD FOR PLAYER 2
Bullet *bull = player->Bullets[i];
bullets.push_back(bull);
}
player->Bullets.clear();
}
While my first player works fine - he can shoot as much as he wants - when you get to the second player, making him shoot causes an EXC_BAD_ACCESS later on when I iterate through the bullets vector. As I was stepping through with a debugger (using Xcode), when I get to the part below my comment ("// THIS IS WHERE THINGS GET WEIRD FOR PLAYER 2"), I notice that the bullet is NULL and the player is NULL as well. Somewhere between calling that player's update method and where I pull the bullets out, things go horribly wrong.
I've been debugging this for several hours now, and I'm wondering what I am missing.
Thanks!
It's a simple typo:
Bullet *bull = player->Bullets[b];
^ not i
You could avoid this kind of mistake using range-based loops (C++11 or later):
for (Player * player : players) {
player->Update(delta);
for (Bullet * bullet : player->Bullets) {
bullets.push_back(bullet);
}
player->Bullets.clear();
}
and/or replacing the inner loop with
bullets.insert(bullets.end(), player->Bullets.begin(), player->Bullets.end());
I'm controlling a sprite on screen using glutkeyboardfunc and keyDown's.
Eg. When I implement the following code the sprite successfully jumps up:
if(keyDown[119]){//w key = up
sprite_y +=40.0f;
spriteT=spriteJ;
}
Whilst in the air, the sprite returns using this code:
if(sprite_y>0){ gravity-=10*delta; sprite_y+=gravity; }
else{ gravity=0; }
The problem I'm having is, if you keep your finger down on the w key, the sprite jumps forever and keeps going up.
I understand this is because I'm using the keyDown method so I tried this:
if(keyDown[119]){//w key = up
sprite_y +=40.0f;
spriteT=spriteJ;
keyDown[119]=0;//reset keyDown to keyUp
}
But I had no luck...
Is there a way I can limit the keyDown to a certain time period or only act as if it was pressed once? Thanks.
edit I dont quite understand how this question is worthy of a vote down. Did you even read it?
You can use a variable to represent the previous state of the key, and then see if it has changed.
For example:
//Initialise a bool called keyWasDown to false
if (keyDown[119]){
if (!keyWasDown){
//Do jumping code
keyWasDown = true;
}
else {
keyWasDown = false;
}
I have a for loop in which I declare positions of sprites. But they usually get spawned near each other and it looks horrible. Using such functions as dbWait(1000); freezes all the sprites with it so it's not a solution.
Is there a way to make a gap between spawning?
for(int i=20;i<=25;i++){
dbSprite(i,dbRnd(500),dbRnd(90)+400,20);
dbHideSprite(i);
}
int spritesToSpawn = 25;
while (LoopGDK ())
{
if (spritesToSpawn)
if (waitToSpawn <= 0)
{
dbSprite (i, dbRnd (500), dbRnd(90)_400, 20);
waitToSpawn = SPAWNING_DELAY;
--spritesToSpawn;
}
else
--waitToSpawn;
...
dbSync();
}
That is, the trick is to make use of that main loop. Let it go about its business, making use of it to spawn whenever you're ready for another.
I know that in order to kill invaders in C++, I need to make a collider.
However, nothing will ever kill the invaders in that game.
Here's the code in the header:
bool DoCollision(float Xbpos, float Ybpos, int BulWidth, int BulHeight, float Xipos, float Yipos, int InvWidth, int InvHeight);
This is the function I'm initializing:
bool Game::DoCollision(float Xbpos, float Ybpos, int BulWidth, int BulHeight, float Xipos, float Yipos, int InvWidth, int InvHeight) {
if (Xbpos+BulWidth < Xipos || Xbpos > Xipos+InvWidth) return false;
if (Ybpos+BulHeight < Yipos || Ybpos > Yipos+InvHeight) return false;
return true;
}
And this is what happens if somebody presses the space key:
if (code == 57) { //Space
myKeyInvader.MeBullet.Active = true;
myKeyInvader.MeBullet.Xpos = myKeyInvader.Xpos + 10;
myKeyInvader.MeBullet.Ypos = myKeyInvader.Ypos - 10;
myKeyInvader.MeBullet.yvuel = 0.2;
myKeyInvader.MeBullet.BulletP->CopyTo(m_Screen,myKeyInvader.Xpos,myKeyInvader.Ypos);
if (DoCollision(Invaders[counter].MyBullet.Xbpos,Invaders[counter].MyBullet.Ybpos,Invaders[counter].MyBullet.BulWidth,
Invaders[counter].MyBullet.BulHeight,Invaders[counter].Xipos,Invaders[counter].Yipos,Invaders[counter].InvWidth,Invaders[counter].InvHeight)) {
//myKeyInvader.Ypos = 100;
Invaders[counter].Active = false;
printf("Collide!\n");
}
}
Does anybody know what's going wrong?
The problem isn't C++. The problem is how you are using it. The only way you'll get a kill with your code as written is if the invader is right on top of you. But that's too late. The alien invader has already killed you.
What you need to do is make those bullets into objects that you propagate over time, just like your invaders are objects that you propagate over time. The response to the user pressing a space key should be to add a new instance of a bullet to the set of active bullets. Each of those active bullets has a position that changes with time. On each time step, you should advance the states of the active invaders per the rules that dictate how invaders move and advance the states of the active bullets per the rules that dictate how bullets move. Remove bullets when they reach the top of the screen, and if an alien invader reaches the bottom of the screen, game over.
After propagating, removing off-screen bullets, and checking for game over, you want to check for collisions between each of the N bullets with each of the M invaders. When a collision is detected, remove the bullet from the set of active bullets and delete the alien invader from the set of active invaders. And of course you'll want some nifty graphics to show the user that another alien bit the dust.
Aside: Being an NxM problem, this check might be the biggest drain on CPU usage. You can speed this up with some simple heuristics.
You could manage the collections of alien invaders and bullets yourself, carefully using new and delete so as to prevent your invaders and bullets from killing your program with a memory leak. You don't have to do this. C++ gives you some nifty tools to manage these collections. Use one of the C++ standard library collections instead of rolling your own collection. For example, std::vector<AlienInvader> invaders; or std::list<AlienInvader> invaders, and the same for bullets. You'll be deleting from the middle a lot, which suggests that std::list or std::deque might be more appropriate than std::vector here.
You test the collision for the fired item just when they are created
Shouldn't be the test collision done in the main loop for each existing item at each frame ?
Don't worry, C++ has got all you need to kill invaders :)))
It's not easy to give advice based on so little code, but here the only logical error seems to be you test for collision only when space is pressed; you should test for it in an outside loop probably:
if (code == 57) { //Space
myKeyInvader.MeBullet.Active = true;
myKeyInvader.MeBullet.Xpos = myKeyInvader.Xpos + 10;
myKeyInvader.MeBullet.Ypos = myKeyInvader.Ypos - 10;
myKeyInvader.MeBullet.yvuel = 0.2;
myKeyInvader.MeBullet.BulletP->CopyTo(m_Screen,myKeyInvader.Xpos,myKeyInvader.Ypos);
}
From a logical point of view, pressing Space should fire a bullet: the starting position for the bullet is set, and so is its speed on the Y axis (so that it goes up).
The code that check for collision should go outside of this if block. In fact, this block of code is executed only if you're still pressing space -that is: still firing-. Should collision be checked only if you're "still firing"? Do the fact that you fired a bullet and started waiting for it to destroy the invader interfere in some way with the fact that this bullet can reach the invader and, indeed, destroy it? Of course not!
if (DoCollision(Invaders[counter].MyBullet.Xbpos,Invaders[counter].MyBullet.Ybpos,Invaders[counter].MyBullet.BulWidth,
Invaders[counter].MyBullet.BulHeight,Invaders[counter].Xipos,Invaders[counter].Yipos,Invaders[counter].InvWidth,Invaders[counter].InvHeight)) {
//myKeyInvader.Ypos = 100;
Invaders[counter].Active = false;
printf("Collide!\n");
}
You want collision to be checked in an outside loop, the same that probably also contains the checks for key presses. In this way, even if you're just looking at the screen and waiting, the program keeps testing the condition and, when it's fulfilled, code associated with the event of collision is executed (that is: an invader is "inactivated").
You say //Space , is that what it is or should it be 32 (if ASCII) instead of 57? Does the program flow into the if==57 block?
Your code looks fine, but you need two loops around the collision checker: one for checking all invaders (not just one of them) and another one to check at every bullet position along its trajectory, not just the moment when it leaves the gun.
I will assume we have an auxiliary function that moves the bullet and returns whether it is still inside the screen:
bool BulletIsInScreen();
Then we can write the loops:
if (code == 57) { // Space
while (BulletIsInScreen()) {
for (size_t i = 0; i < counter; ++i) { // counter is the number of invaders,
// according to your comment to your own answer
myKeyInvader.MeBullet.Active = true;
myKeyInvader.MeBullet.Xpos = myKeyInvader.Xpos + 10;
myKeyInvader.MeBullet.Ypos = myKeyInvader.Ypos - 10;
myKeyInvader.MeBullet.yvuel = 0.2;
myKeyInvader.MeBullet.BulletP->CopyTo(m_Screen,myKeyInvader.Xpos,myKeyInvader.Ypos);
if (DoCollision(Invaders[i].MyBullet.Xbpos, Invaders[i].MyBullet.Ybpos,
Invaders[i].MyBullet.BulWidth, Invaders[i].MyBullet.BulHeight,
Invaders[i].Xipos, Invaders[i].Yipos,
Invaders[i].InvWidth, Invaders[i].InvHeight)) {
//myKeyInvader.Ypos = 100;
Invaders[i].Active = false;
printf("Collide!\n");
}
}
}
}
Now this should work as expected.