Best way to make tiles not have borders in between them? - c++

So I am trying to make a game using SFML and visual c++. Currently I have different sprites without borders on certain sides for every combination of tiles that could be bordering each other, then used nested if statements to check which sprite to use. However, this slows down my game to the point where I can't even play it. Is there a better way to do this?
Here is my code:
//Render tiles
for (int i = 0; i < blockX.size(); i++) {
block.setPosition(float(blockX[i]), float(blockY[i]));
if (blockType[i] == "metal") {
//if block is metal tile
block.setTexture(metalBlockSingle);
if (doesBlockExist(blockX[i] + 20, blockY[i])) {
//if block is to right
if (doesBlockExist(blockX[i], blockY[i] + 20)) {
//if block is below
if (doesBlockExist(blockX[i] - 20, blockY[i])) {
//if block is to left
if (doesBlockExist(blockX[i], blockY[i] - 20)) {
//if block is above
block.setTexture(metalBlockCenter);
}
else {
block.setTexture(metalBlockBottomRightLeft);
}
}
else if (doesBlockExist(blockX[i], blockY[i] - 20)) {
//if block is above
block.setTexture(metalBlockTopBottomRight);
}
else {
//if block is neither above nor to left
block.setTexture(metalBlockBottomRight);
}
}
else if (doesBlockExist(blockX[i] - 20, blockY[i])) {
//if block is to left
if (doesBlockExist(blockX[i], blockY[i] - 20)) {
//if block is above
block.setTexture(metalBlockTopRightLeft);
}
else {
block.setTexture(metalBlockRightLeft);
}
}
else if (doesBlockExist(blockX[i], blockY[i] - 20)) {
//if block is above
block.setTexture(metalBlockTopRight);
}
else {
//if block is only right
block.setTexture(metalBlockRight);
}
}
else if (doesBlockExist(blockX[i], blockY[i] + 20)) {
//if block is below
if (doesBlockExist(blockX[i] - 20, blockY[i])) {
//if block is to left
if (doesBlockExist(blockX[i], blockY[i] - 20)) {
//if block is above
block.setTexture(metalBlockTopBottomLeft);
}
else {
//if block is not above
block.setTexture(metalBlockBottomLeft);
}
}
else {
//if block is not to left
if (doesBlockExist(blockX[i], blockY[i] - 20)) {
//if block is above
block.setTexture(metalBlockTopBottom);
}
else {
//if metal block is not above
block.setTexture(metalBlockBottom);
}
}
}
else if (doesBlockExist(blockX[i] - 20, blockY[i])) {
//if block is to left
if (doesBlockExist(blockX[i], blockY[i] - 20)) {
//if block is above
block.setTexture(metalBlockTopLeft);
}
else {
block.setTexture(metalBlockLeft);
}
}
else if (doesBlockExist(blockX[i], blockY[i] - 20)) {
//if block is above
block.setTexture(metalBlockTop);
}
}
window.draw(block);
}
EDIT: Here is the code for the "doesBlockExist" function:
bool doesBlockExist(int x, int y) {
bool returnVal = false;
int findX = int(round(x / 20) * 20);
int findY = int(round(y / 20) * 20);
for (int i = 0; i < blockX.size(); i++) {
if (blockX[i] == findX && blockY[i] == findY) {
returnVal = true;
}
}
return(returnVal);
return(true);
}

Whenever you have nested if-statements (high cyclomatic complexity) like this, you should immediately stop and think about other designs. Large nested if-statements are not only prone to contain mistakes, but are impossible to really understand at a glance.
A better design can start with splitting things into multiple function, each with their own single responsibility. Other steps involve thinking about the logic hard enough to find ways that eliminate conditions, i.e. when situation X holds up, Y follows and there's no need for further checking. And then there are also situations where instead of using if-statements you can construct an object that will have the right settings after construction.
As for optimizations on your code:
Use a sprite sheet and don't use x amount of textures. setTextureRect is basically a free function, while switching textures all the time, can lead to performance issues.
If you keep the implementation of doesBlockExist, at least make sure to return right when you find the block and don't keep iterating. This means, inside the if-body just write return true and after the loop write return false and delete the variable returnVal.
However the doesBlockExist function seems badly implemented. What exactly does it check for? Do you just check the surrounding tiles? If so, then you can easily just check the 8 surrounding tiles and don't need to iterate over all the tiles.
Since you call doesBlockExist quite often, you'll have to think about, whether it wouldn't be possible to just call it once and report on all the surrounding tiles at once.
int(round(x / 20) * 20); since x is already int x / 20 will be an integer division anyways, so there's no reason to round.
Alternatively you could just use a tile editor like Tiled and place the tiles accordingly when designing the level and then just load the tile map.
As for the question in the title, I have no idea what you mean with borders.

Related

if else both executes on "dragmove" in konvajs

I have an if statement that checks whether the group(consists of a rectangle and a text) I'm dragging is over other group(I check only "y" coordinate because "x" axis is locked). If so, change the color of the stationary group(rectangle) to "red". Else make all rectangles blue. Every group is in groupfield called "groups" which I'm iterating and adding events like this 'dragmove' + others. For some reason both blocks of code executes, idk why. Height of all rectangles is set by text height which is same everywhere(and is accessed by groups[].children[1].height(). Groups[i] is the moving(dragging) group and groups[j] are stationary. In my other event("dragstart") I change the "name" of that group to "true" others are false by default so when i iterate in my if doesn't triggers the dragging group. The code works partially, colors change how they should, but "else" is running even if I drag rectangle over other rectangle which is unwanted and needs fixing. Here's my code:
groups[i].on('dragmove', () => {
//lock top stage&bottom
if(groups[i].y() <= 0){
groups[i].y(0);
}
else if (groups[i].y() + groups[i].children[1].height() >= stage.height()){
groups[i].y(stage.height() - groups[i].children[1].height());
}
//end lock top stage&bottom
for (let j = 0; j < groups.length; j++) {
if(groups[i].y() + groups[i].children[1].height() >= groups[j].y() &&
groups[i].y() <= groups[j].y() + groups[j].children[1].height() &&
groups[j].name !== "true"){
if (groups[j].children[0].fill() === "red"){
continue;
}
console.log("if dragmove");
groups[j].children[0].fill("red");
}
else {
groups[j].children[0].fill("#aaf");
}
}
})

C++ and SDL2 Grid movement is too fast

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);.

Why is my collision response not stopping my player from going through walls (SDL2, C++)?

While I found many problems that are similar too mine, none of the solutions solved my problem.
I've been experimenting with SDL2 in C++ (Visual C++) and the entity-component-system (ECS). But I just can't figure out the bug in my collision response.
So here it is: My player sometimes gets set back to its origin when it encounters something like a rock (a simple gray tile). But sometimes it goes right through and gets stuck or ends up on the other side.
I can only assume it has something to do with the data changed in between frames, so it isn't always caught. But for the life of me I can't figure it out.
Here is my rectangular detection method:
bool Collision::RectIntersect(const SDL_Rect& a, const SDL_Rect& b, SDL_Rect& intersect)
{
intersect = { 0, 0, 0, 0 };
int leftX = std::max(a.x, b.x);
int rightX = std::min(a.x + a.w, b.x + b.w);
int topY = std::max(a.y, b.y);
int bottomY = std::min(a.y + a.h, b.y + b.h);
if (leftX < rightX && topY < bottomY)
{
intersect = { leftX, topY, rightX - leftX, bottomY - topY };
return true;
}
return false;
}
Here is my snippet where my inputs are handled and subsequently any collision detections are resolved before the code actually moves anything:
void InputComponent::handleEvents(SDL_Event* e)
{
const Uint8 *keyboardState = SDL_GetKeyboardState(NULL);
if (e != nullptr)
{
/*
keyHeld: array of 4 for each direction (+/- x, +/- y (WASD))
hold value true, if pressed down, otherwise false
*/
if (keyboardState[SDL_SCANCODE_A])
{
keyHeld[0] = true;
}
else
{
keyHeld[0] = false;
}
if (keyboardState[SDL_SCANCODE_D])
{
keyHeld[1] = true;
}
else
{
keyHeld[1] = false;
}
if (keyboardState[SDL_SCANCODE_W])
{
keyHeld[2] = true;
}
else
{
keyHeld[2] = false;
}
if (keyboardState[SDL_SCANCODE_S])
{
keyHeld[3] = true;
}
else
{
keyHeld[3] = false;
}
}
/*
tmpVel: Vector to store the assumed velocity in x- and y-direction
*/
Vector2D tmpVel(0.0f, 0.0f);
// left and right (A and D)
if (keyHeld[0] && !keyHeld[1]) // left
{
tmpVel.x = -1.0f;
}
else if (!keyHeld[0] && keyHeld[1]) // right
{
tmpVel.x = 1.0f;
}
else
{
tmpVel.x = 0.0f; // left and right cancel each other out
}
// up and down (W and S)
if (keyHeld[2] && !keyHeld[3]) // up
{
tmpVel.y = -1.0f;
}
else if (!keyHeld[2] && keyHeld[3]) // down
{
tmpVel.y = 1.0f;
}
else
{
tmpVel.y = 0.0f; // up and down cancel each other out
}
/*
check for collision with presumed direction according to tmpVel
*/
SDL_Rect intersection;
// get current player position
SDL_Rect movedPlayer = entity->getComponent<CollisionComponent>().getCollider();
// add trajectory of theoretical movement
movedPlayer.x += static_cast<int>(tmpVel.x * vel_->getSpeed());
movedPlayer.y += static_cast<int>(tmpVel.y * vel_->getSpeed());
bool hasCollided = false;
// collect all collidable objects
for (auto& c : manager_->getGroup(GroupLabel::GR_COLLIDERS))
{
// check player against each collidable tile
//if (SDL_IntersectRect(&movedPlayer, &c->getComponent<CollisionComponent>().getCollider(), &intersection))
if (Collision::RectIntersect(movedPlayer, c->getComponent<CollisionComponent>().getCollider(), intersection))
{
// collision on x-axis
if (intersection.w > 0)
{
// set velocity on x-axis to 0
vel_->setVelocityX(0.0f);
// reset player position back according to width of intersected rectangle
pos_->setPosX(pos_->getPos().x + (static_cast<float>(intersection.w) * (-tmpVel.x)));
}
// collision on y-axis
if (intersection.h > 0)
{
// set velocity on y-axis to 0
vel_->setVelocityY(0.0f);
// reset player position back according to height of intersected rectangle
pos_->setPosY(pos_->getPos().y + (static_cast<float>(intersection.h) * (-tmpVel.y)));
}
hasCollided = true;
}
}
if (!hasCollided)
{
vel_->setVelocity(tmpVel);
}
}
Can anybody put me in the right direction?
What happens when the right edge of the player exactly equals the left edge of the rock? It looks like the collision is not detected, since the test is for (leftX < rightX). So the velocity is updated and the player is moved by the velocity. (It's odd that you simply update the velocity and later move the player instead of just moving them to the new already calculated position.) If you change the check to (leftX <= rightX), does the problem persist?
As far as I can see there are two things wrong with your collision detection. The first is that you're testing (leftX < rightX && topY < bottomY) when you should be testing (leftX <= rightX && topY <= bottomY). If you fix this your code will work in most situations.
The second problem you've got, which may not become apparent straight away, is that your are performing collision detection for discreet points in time. If your player has a large enough velocity vector you may end up with this situation:
Update 1: Player is in front of wall travelling towards it. AABB test shows no collision.
Update 2: Player is behind wall travelling away from it. AABB test shows no collision.
Your AABB test is correct and yet the player has passed through the wall. The naive approach to fixing this is to test more often (update 1.5 may have shown a collision), or to limit player velocity. Both approaches will require a lot of fine tuning especially if you're dealing with objects that can move at different speeds and walls with differing thickness.
A more robust approach is to take account of velocity in your test. Since you know the velocity of your AABB you can project this shape along its velocity vector. If you do this for both AABBs you'll end up with two elongated shapes which you can test against each other. If they overlap then you know that their paths cross and that there may be a collision.
Of course, knowing that there might be a collision is not hugely helpful. The problem is one AABB may be moving very slowly and the other very quickly so even though they both pass through the same space (their elongated shapes intersect) they don't pass through it at the same time.
Figuring out whether they both pass through the same space at the same time is hard, so instead we cheat. If you subtract the velocity of B from the velocity of A and then use this modified velocity to project the elongated shape of A, you can effectively treat B as a stationary object and still get the correct result. Knowing this, your test is now "does B overlap the elongated shape of A?". This is just a simple AABB vs Ngon problem.
While the above will give you a boolean as to whether two moving AABBs collide it will not tell you when they collide which is also useful for calculating things like rebounds.
I would very much recommend the book Real Time Collision Detection by Christer Ericson which is pretty much the go to book on collision detection for any aspiring game developer.
The following is a code snippet from the CD-ROM which accompanies the book. It tests a moving AABB against another moving AABB and also provides a time of first contact.
// Intersect AABBs ‘a’ and ‘b’ moving with constant velocities va and vb.
// On intersection, return time of first and last contact in tfirst and tlast
int IntersectMovingAABBAABB(AABB a, AABB b, Vector va, Vector vb, float &tfirst, float &tlast)
{
// Exit early if ‘a’ and ‘b’ initially overlapping
if (TestAABBAABB(a, b)) {
tfirst = tlast = 0.0f;
return 1;
}
// Use relative velocity; effectively treating ’a’ as stationary
Vector v = vb - va;
// Initialize times of first and last contact
tfirst = 0.0f;
tlast = 1.0f;
// For each axis, determine times of first and last contact, if any
for (int i = 0; i < 3; i++) {
if (v[i] < 0.0f) {
if (b.max[i] < a.min[i]) return 0;
// Nonintersecting and moving apart
if (a.max[i] < b.min[i]) tfirst = Max((a.max[i] - b.min[i]) / v[i], tfirst);
if (b.max[i] > a.min[i]) tlast = Min((a.min[i] - b.max[i]) / v[i], tlast);
}
if (v[i] > 0.0f) {
if (b.min[i] > a.max[i]) return 0;
// Nonintersecting and moving apart
if (b.max[i] < a.min[i]) tfirst = Max((a.min[i] - b.max[i]) / v[i], tfirst);
if (a.max[i] > b.min[i]) tlast = Min((a.max[i] - b.min[i]) / v[i], tlast);
}
// No overlap possible if time of first contact occurs after time of last contact
if (tfirst > tlast) return 0;
}
return 1;
}
The following attribution is required by Elsevier's Software License Agreement:
“from Real-Time Collision Detection by Christer Ericson, published by Morgan Kaufmann Publishers, © 2005 Elsevier Inc”

How do I set up an if statement to animate a sprite?

How can I set up an if statement so after every frame the sprite shows the next frame and then the next and then once it goes through all the frames it is over?
I tried using if statements and it has never worked for me, could anyone give an example?
Edit:
After demand for code I have decided to add a sample.
int frame4 = 1;
if(frame4 = 1)
{
WalkDownFrame1(); //Renders frame 4
}
else if(frame4 = 2)
{
WalkDownFrame2(); //Renders frame 2
}
else if(frame4 = 3)
{
WalkDownFrame3(); //Renders frame 3
}
else if(frame4 = 4)
{
WalkDownFrame4(); //Renders frame 4
}
else if(frame4 = 5)
{
frame4 = 1;
}
frame4++;
no matter what modifications I apply it stays stuck on one frame.
I'm assuming you mean if the conditions are true the animation occurs and if the conditions are false it stops, in which case it would look like
/*Rest of your model transformation code*/
if(shouldbeanimating){
/*animation code*/
}else{
/*default state or nothing if you want the model to
freeze at that point in the animation*/
}
Then whenever the program should stop the animation you just set shouldbeanimating to false
Well, you need to know how many frames your animation has. Then you proceed to draw frame after frame. If you hit the last frame you go back to the first or you stop.
Here's a link that will help you. It's doesnt matter if its SDL or any other lib, the approach is always the same.
http://lazyfoo.net/SDL_tutorials/lesson20/index.php
As an example
while(isAnimating)
{
framecounter++;
isAnimating = (framecounter < MAX_FRAMES);
}
They're many solutions. You can do a Sprite Class and add an attribute _tick, store your Texture into a container, like a std::vector. I'll give you a short hint. You put a method tick() to increment your tick number. You put an attribute nbFrames, that contains the number of frames for the current sprite.
int tick;
int nbFrames; //equivalent of textures.size()
std::vector<SDL_Texture*> textures;
SDL_Texture *idle_frame;
bool moving;
int x;
int y;
Sprite::Sprite()
{
_tick = 0;
//init your textures
moving = false;
x = 0;
y = 0;
}
int _tick;
void Sprite::tick(void)
{
// Consider that _tick can reach the Max Value of int32
_tick++;
}
void Sprite::render(void)
{
SDL_Texture *texture;
if(moving)
{
int fr_index = _tick % nbFrames;
texture = textures[fr_index];
}
else
{
texture = idle_frame;
}
//then you do your render code with SDL_RenderCopy, or OpenGL code
//.
//..
}
Still missing some other thing to handle, but that an hint for your solution.
Is it possible that you are just assigning in the if statement and not testing? Or did you just miss typed it here?
Because if your code is if(frame = 0) it should be (frame == 0).

C++/SDL, Tic Tac Toe turn taking

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;
}
}
}