My grid movement is too fast. This problem is due to speed, but I can't change it. Because then the grid doesn't work.
if (Game::event.type == SDL_KEYDOWN) {
if (Game::event.key.keysym.sym == SDLK_w || Game::event.key.keysym.sym == SDLK_UP) {
transform->velocity.y = -1;
}
else if (Game::event.key.keysym.sym == SDLK_s || Game::event.key.keysym.sym == SDLK_DOWN) {
transform->velocity.y = 1;
}
else if (Game::event.key.keysym.sym == SDLK_d || Game::event.key.keysym.sym == SDLK_RIGHT) {
transform->velocity.x = 1;
}
else if (Game::event.key.keysym.sym == SDLK_a || Game::event.key.keysym.sym == SDLK_LEFT) {
transform->velocity.x = -1;
}
} else if (Game::event.type == SDL_KEYUP) {
transform->velocity.x = 0;
transform->velocity.y = 0;
}
And update for player position:
void update() override {
position.x += round(velocity.x * 32);
position.y += round(velocity.y * 32);
}
Problem is in update player position, but if there weren't *32 player gets out from grid. (32 is grid size)
Any idea how to solve it?
And yes, I use SDL_Delay.
If you want only one step per press then in your update() function you need to zero your velocity vector after you're done using it, that will make it step only once, but you also have to check event.key.repeat and only accept events when it's 0 to avoid repeating the event ~30 times/second as long as the key is held down. If you want the stepping to repeat after a given delay then you'll need to store the return value (a uint32_t or Uint32 if you prefer) of SDL_GetTicks(); taken the moment a SDL_KEYUP event was received (with repeats ignored) then check if enough milliseconds have passed to repeat the stepping then add the time threshold to the stored time value to properly wait for the next repeating.
Also you're zeroing your velocity vector every time any key is released, which in some cases you don't want. It might be better to add to the vector for each SDL_KEYDOWN event and subtract to under for each SDL_KEYUP, for matching keys of course. Also if you're gonna use WASD you should use scancodes instead of keysym, or your control scheme will be quite awkward on non-QWERTY layouts.
Also there's probably no need for SDL_Delay(), just use Vsync which is set when you initialise your renderer with something like renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC);.
Related
I am making a flappy bird game just to get an idea of how to make games using SFML 2.4 and C++. I have a scoring system which is supposed to increment the score by 1 everytime the bird sprite intersects with an invisible pipe. However, instead of incrementing the score by 1 the score comes to around 57 and 60. Any ideas to get this working is really appreciated.
int main()
{
int score = 0;
float PipeInvisi = 200.0;
while(window.isOpen())
{
if (state == State::PLAYING)
{
// Setup the Invisible Pipe for Movement
if (!PipeInvisbleActive)
{
// How fast is the Pipe
spriteInvisi.setPosition(905, 0);
PipeInvisbleActive = true;
}
else
{
spriteInvisi.setPosition(spriteInvisi.getPosition().x - (PipeInvisi * dt.asSeconds()), spriteInvisi.getPosition().y);
// Has the pipe reached the right hand edge of the screen?
if (spriteInvisi.getPosition().x < -165)
{
// Set it up ready to be a whole new cloud next frame
PipeInvisbleActive = false;
}
}
// Has the Bird hit the invisible pipe
Rect<float> Birdie = spriteBird.getGlobalBounds();
Rect<float> Paipu5 = spriteInvisi.getGlobalBounds();
if (Birdie.intersects(Paipu5))
{
// Update the score text
score++;
std::stringstream ss;
ss << "Score = " << score;
scoreText.setString(ss.str());
clock.restart();
}
}
}
}
Assuming that your problem stems from constant intersection, you could introduce a simple flag that marks the intersection.
bool isBirdIntersectingPipe = false;
Then in your game loop you could detect the beginning of intersection like so.
if (birdRect.intersects(pipeRect)) // Intersection this frame.
{
if (!isBirdIntersectingPipe) // No intersection last frame, so this is the beginning.
{
++score;
isBirdIntersectingPipe = true;
}
// Still intersecting, so do nothing.
}
else // No intersection this frame.
{
isBirdIntersectingPipe = false;
}
Ideally you would have a dedicated collision or even physics system that would track all objects on the scene, but in this case a simple solution like this should suffice.
I need your piece of advice. I'm using SFML and I need to play animation from the spritesheet(f.e. 64 frames and 40px width/height of each frame) after mouseclick event. The only solution I've come to is:
if (event.type == sf::Event::MouseButtonPressed) {
if (event.key.code == sf::Mouse::Left) {
float frame = 0;
float frameCount = 64;
float animSpeed = 0.005;
while (frame < frameCount) {
spriteAnimation->setTextureRect(sf::IntRect(int(frame)*w, 0, w, w));
frame += animSpeed;
window->draw(rect); // clear the area of displaying animation
window->draw(*spriteAnimation);
window->display();
}
...
But calling window->display() so many times is really not good;
Can you suggest better variants?
Instead of jamming all of your code for the animation into the event block you should spread it out.
Your design here is very inflexible in that if you ever want to display anything other than an animation you are going to have to call window->display() again outside of your event loop.
Generally in SFML your game loop proceeds similarly to as follows:
initialize();
while(running)
{
checkEvents();
clear();
update();
display();
}
Instead of performing all of the calculations and displaying for your animation inside the event's if statement you should set a bool or call a doAnimation() function of some sort. I've written a rough example below:
bool doAnimation = 0;
//declare frame, framespeed, etc
if (event.type == sf::Event::MouseButtonPressed)
{
if (event.key.code == sf::Mouse::Left)
{
doAnimation = true;
//reset frame, framespeed, etc
}
}
clear();
if(doAnimation)
{
sprite->setTexture(...);
if(frame == endFrame)
{
doAnimation = 0;
}
drawSprite();
}
window->display();
There are tons of ways to solve your problem. My example is less flexible than I would think is ideal but depending on the needs of your program it may work fine. If you wanted to take the next step moving the animation into a class of some sort would make your life a lot easier in the long run.
I am currently experiencing some heavy slowdowns with my game. I have narrowed it down to something related with texture animations.
In my game there are characters that walk in 1 of 4 possible directions, they will walk up to a point, then change direction and continue walking (sort of like a tower defense game).
First i am loading the sprite frame cache like this
SpriteFrameCache::getInstance()->addSpriteFramesWithFile("characters.plist");
This code is only run once during the life time of my application.
When the characters get loaded to the screen their animation is being set using the following code:
int direction = 0;
int number = 0;
if (this->to_x < 0) // Left
{
direction = 1;
number = 1;
}
else if(this->to_x > 0) // Right
{
direction = 2;
number = 1;
}
if (this->to_y < 0) // Down
{
direction = 0;
number = 0;
}
else if(this->to_y > 0) // Up
{
direction = 3;
number = 2;
}
int s = 0; //skin
// Set the animation
Animation *animation = Animation::create();
for (int i = 0; i < INT16_MAX; i++)
{
string frame_sprite_name = StringUtils::format("%s_%d_%d_%d.png",parameters[name].image_name.c_str(),s,number,i);
auto frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(frame_sprite_name);
if (frame) {
animation->addSpriteFrame(frame);
} else {
break;
}
}
// Invert the sprite when they go right
if (direction == 2) {
setFlippedX(true);
}else{
setFlippedX(false);
}
// Set the pace of the animation based on the type
if (name=="runner") {
animation->setDelayPerUnit(0.15f);
} else{
animation->setDelayPerUnit(0.3f);
}
Animate *animate = Animate::create(animation);
this->stopAllActions();
this->runAction(RepeatForever::create(animate));
What this code does is:
Check the direction
Get the sprite frame from the cache based on the direction
Run the action with repeat forever.
However this code is ran every time they change direction to set the new animation of the active characters. Also, at one time I can have around 40-50 of these characters going around.
I've noticed that after a few minutes in the game the slowdown starts to happen as soon as a new "character" is created, (since they are created in rapid succession in waves). And the slowdown also happens when the characters change in direction. So this makes me believe I am using the textures wrong.
If anyone knows how to fix this please let me know.
PD: I was thinking about the possibility of pre-loading all the animations and then just having each of the sprites that represent the characters run the corresponding animation.
You should definitely cache the animation in the AnimationCache with addAnimation and getAnimation methods.
Alright so I have this tic tac toe game I'm making with SDL and C++. I'm trying to implement AI into the game. I don't have a problem setting up the AI, but I have a problem making it where you can take turns. My problem is that when I make my move, I can just move as many times as I want before the AI moves. I want it so I can make my move, and I can't make my move again until the AI makes a move. No matter what I do it seems the turn taking doesn't work properly.
This is the class header
class Buttons
{
private:
SDL_Rect squares[8];
public:
Buttons();
void handle_input();
void load_Squares(SDL_Rect sqRects, SDL_Texture* squarTexs);
void show_Squares();
void AI_move();
int grid[9];
bool moveMade = true;
};
Here I check for mouse input, and depending on the location during the left button press, it sets the according grid value to equal 1, meaning it becomes displayed as a circle on the screen. I also make sure that the AI has made a move before it allows me to click.
void Buttons::handle_input()
{
double mouseX = 0, mouseY = 0;
if((event.type == SDL_MOUSEBUTTONDOWN))
{
//If left mouse button was clicked and AI has made a move
if(event.button.button == SDL_BUTTON_LEFT && moveMade == true)
{
//Get mouse location
mouseX = event.button.x;
mouseY = event.button.y;
//If mouse location is in particular square, set according grid value to 1
if((mouseX >= 0) && (mouseX < SCREEN_WIDTH / 3) && (mouseY >= 0) && (mouseY < SCREEN_HEIGHT / 3) && (grid[0] == 0))
{
grid[0] = 1;
moveMade = false;
}
//Basically does this for all other 9 grids
Here is my AI function, where I check to make sure the moveMade variable = false. Every time I make a move in the input function above, it sets moveMade to false, which means it should access this function, and only until it finishes this AI_move function should I be able to make a move again, because moveMade is set back equal to true.
void Buttons::AI_move()
{
if(moveMade == false)
{
AI_block(&moveMade);
AI_complete(&moveMade);
AI_rand(&moveMade);
moveMade = true;
}
}
Last is my show function, where I show a Circle(player) if the grid array value = 1, and I show the X(AI) if the grid value = 2.
void Buttons::show_Squares()
{
switch(grid[0])
{
case 1:
load_Squares(squares[0], circleTexture); break;
case 2:
load_Squares(squares[0], xTexture); break;
}
switch(grid[1])
{
//Does this all the way to grid[8]
}
Alright so my problem doesn't have to do with the AI dealing accordingly, as I haven't even set up my defense and offense functions. My problem is that I can make another move before the AI moves. Sorry if this is way too long, but if I could get any feedback on this that would be great.
Have you tried putting breakpoints at various points such as if(event.button.button == SDL_BUTTON_LEFT && moveMade == true) and then following the program through the see if moveMade ever actually gets changed to false?
You should also look at changing show_Squares() into a loop as there is a lot of repeated code using incremented indexes. Something like this:
void Buttons::show_Squares()
{
size_t array_size = sizeof(squares) / sizeof(int); //gets the number of elements in the array
for(size_t i = 0; i < array_size; i++)
{
switch(grid[i])
{
case 1:
load_Squares(squares[i], circleTexture); break;
case 2:
load_Squares(squares[i], xTexture); break;
}
}
}
Lets say I have 4 images and I want to use these 4 images to animate a character. The 4 images represent the character walking. I want the animation to repeat itself as long as I press the key to move but to stop right when I unpress it. It doesn't need to be SFML specific if you don't know it, just basic theory would really help me.
Thank you.
You may want some simple kind of state machine. When the key is down (see sf::Input's IsKeyDown method), have the character in the "animated" state. When the key is not down, have the character in "not animated" state. Of course, you could always skip having this "state" and just do what I mention below (depending on exactly what you're doing).
Then, if the character is in the "animated" state, get the next "image" (see the next paragraph for more details on that). For example, if you have your images stored in a simple 4 element array, the next image would be at (currentIndex + 1) % ARRAY_SIZE. Depending on what you are doing, you may want to store your image frames in a more sophisticated data structure. If the character is not in the "animated" state, then you wouldn't do any updating here.
If your "4 images" are within the same image file, you can use the sf::Sprite's SetSubRect method to change the portion of the image displayed. If you actually have 4 different images, then you probably would need to use the sf::Sprite's SetImage method to switch the images out.
How would you enforce a framerate so that the animation doesn't happen too quickly?
Hello please see my answer here and accept this post as the best solution.
https://stackoverflow.com/a/52656103/3624674
You need to supply duration per frame and have the total progress be used to step through to the frame.
In the Animation source file do
class Animation {
std::vector<Frame> frames;
double totalLength;
double totalProgress;
sf::Sprite *target;
public:
Animation(sf::Sprite& target) {
this->target = ⌖
totalProgress = 0.0;
}
void addFrame(Frame& frame) {
frames.push_back(std::move(frame));
totalLength += frame.duration;
}
void update(double elapsed) {
// increase the total progress of the animation
totalProgress += elapsed;
// use this progress as a counter. Final frame at progress <= 0
double progress = totalProgress;
for(auto frame : frames) {
progress -= (*frame).duration;
// When progress is <= 0 or we are on the last frame in the list, stop
if (progress <= 0.0 || &(*frame) == &frames.back())
{
target->setTextureRect((*frame).rect);
break; // we found our frame
}
}
};
To stop when you unpress, simply only animate when the key is held
if(isKeyPressed) {
animation.update(elapsed);
}
To support multiple animations for different situations have a boolean for each state
bool isWalking, isJumping, isAttacking;
...
if(isJumping && !isWalking && !isAttacking) {
jumpAnimation.update(elapsed);
} else if(isWalking && !isAttacking) {
walkAnimation.update(elapsed);
} else if(isAttacking) {
attackAnimation.update(elapsed);
}
...
// now check for keyboard presses
if(jumpkeyPressed) { isJumping = true; } else { isJumping false; }