How to check collision on 2D map in SFML? - c++

I have one RectangleShape for wall and I draw this one object for every "1" on my 2D map:
int map[5][8] =
{
{ 1, 1, 1, 1, 1, 1, 1, 1 },
{ 1, 0, 0, 0, 0, 0, 0, 1 },
{ 1, 0, 1, 0, 1, 1, 0, 1 },
{ 1, 2, 1, 0, 0, 0, 3, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 1 }
};
sf::RectangleShape player;
player.setSize(sf::Vector2f(32,32));
sf::RectangleShape wall;
wall.setSize(sf::Vector2f(32,32));
wall.setFillColor(sf::Color(40, 199, 40));
for (int mapX= 0; mapX< 8; mapX++)
{
for (int mapY= 0; mapY< 5; mapY++)
{
if (map[mapY][mapX] == 1)
{
wall.setPosition(mapX * 32, mapY * 32);
window.draw(wall);
}
}
}
And I checking for collision with code listed down, but this works for only last drawn wall. How to fix this? Theres only one object wall and I want to check collision for all drawned walls for my 2D array map.
if (event.type == sf::Event::TextEntered)
{
fPlayerY_t = fPlayerY;
fPlayerX_t = fPlayerX;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::W)) fPlayerY_t = fPlayerY - 32;
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) fPlayerY_t = fPlayerY + 32;
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) fPlayerX_t = fPlayerX + 32;
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) fPlayerX_t = fPlayerX - 32;
// temp pos
player.setPosition(fPlayerX_t, fPlayerY_t);
// collision with wall
if (player.getPosition().x == wall.getPosition().x &&
player.getPosition().y == wall.getPosition().y)
{
// back to prev position
player.setPosition(fPlayerX, fPlayerY);
}
else
{
// No collision
fPlayerX = fPlayerX_t;
fPlayerY = fPlayerY_t;
}
}

Convert the coordinates of the player in array coordinates then check if map[x][y] == 1.
So do not have to loop through your entire map every game loop.
To convert into array coordinates, use int conversion :
int playerTileX = (int)(playerWorldX / 32);
int playerTileY = (int)(playerWorldY / 32);
if(map[playerTileX ][playerTileY ] == 1)
{
// collision.
}
else
{
// no collision.
}
What I dislike with this solution : it only checks for tiles collision, you have to be sure every wall is the same size (here 32*32).

Related

Cannot add color to character on multiple lines using Ncurses mvchgat

I'm trying to color a video game map in Ncurses using a loop to have a specific color for multiple characters, it works fine on a single line but whenever I try to apply color to multiple lines either it doesn't apply any color, or it only applies color on the last line.
Here's my code:
initscr();
start_color();
char *map = strdup("OOOOOOOOOXOOOOOOOOO\nBXXXOXXXOXOXXXXOXXB\nOXXXOXXXOXOXXXXOXXO\nOOOOOOOOOOOOOOOOOOO\nOXXXOXOXXXXXOXXOXXO\nOOOOOXOOOXOOOXXOOOO\nXXXXOXXXOXOXXXXOXXX\nXXXXOXOOOOOOOXXOXXX\nXXXXOXOMMMMMOXXOXXX\nXXXXOOOMMMMMOOOOXXX\nXXXXOXOMMMMMOXXOXXX\nXXXXOXOOOOOOOXXOXXX\nXXXXOXOXXXXXOXXOXXX\nOOOOOOOOOXOOOOOOOOO\nOXXXOXXXOXOXXXXOXXO\nBOOXOOOOOOOOOOOOXOB\nXXOXOXOXXXXXOXXOXOX\nOOOOOXOOOXOOOXXOOOO\nOXXXXXXXOXOXXXXXXXO\nOOOOOOOOOOOOOOOOOOO");
init_pair(1, COLOR_CYAN, COLOR_BLACK);
init_pair(2, COLOR_YELLOW, COLOR_BLACK);
init_pair(3, COLOR_RED, COLOR_BLACK);
int y = 0;
int x = 0;
while (true) {
mvprintw(0, 0, map);
for (int i = 0; y < 19; i++, x++) {
if (x == 19) {
y++;
x = 0;
}
if (map[i] == 'O') {
mvchgat(map[i], y, x, 1, A_BLINK, 2, NULL);
}
else if (map[i] == 'X') {
mvchgat(y, x, 1, A_BLINK, 1, NULL);
}
else if (map[i] == 'B'){
mvchgat(y, x, 1, A_BLINK, 3, NULL);
}
refresh();
}
The \n in the string erases the next line(s), wiping out the video attributes.
mvprintw (and printw, etc), ultimately call waddch, which documents the behavior:
Newline does a clrtoeol, then moves the cursor to the window left
margin on the next line, scrolling the window if on the last line.

Drawing a chess board with SDL2's SDL_RenderDrawRect

I am fairly new to SDL2 and as my first project I wanted to just create a chess board. This has proven to be harder than I thought.
I have tried lots of different ways to draw the fields of the chess board with SDL_RenderDrawRect, this is the current state:
#include <SDL2/SDL.h>
int main()
{
bool quit = false;
SDL_Event event;
SDL_Init(SDL_INIT_EVERYTHING);
SDL_Window *window = SDL_CreateWindow("Chess", SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, 680, 680, 0);
SDL_Renderer *render = SDL_CreateRenderer(window, -1,
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
for (int x = 0; x > 3; x++) {
for (int y = 0; x > 8; x++) {
SDL_Rect rect;
rect.x = x*10;
rect.y = y*10;
rect.w = 128;
rect.h = 128;
SDL_SetRenderDrawColor(render, 159, 84, 8, 255);
SDL_RenderFillRect(render, &rect);
}
}
SDL_RenderPresent(render);
while (!quit) {
SDL_WaitEvent(&event);
switch (event.type) {
case SDL_QUIT:
quit = true;
break;
}
}
SDL_Quit();
return 0;
}
No rectangles want to show up when I use SDL_RenderDrawRect in a loop. Any ideas why that is?
Cheers!
There are multiple errors in your for loops.
You have to change your conditions in your for loops. Both iteration variables(x and y) start with 0, but your condition to run the foor loop is that they are greater than 3 or 8, so they wont get executed. Change it to less than the value(< instead of >)
You have to change the iteration variable of your second for loop. You create y but the condition and the value change is for x
Your Rect is 128x128 big, but you multiply your x and y from your loops by 10, that means your Rectangles will overlap. You have to multiply it by at least 128.
If you look at a Chess Board, beginning at the top left and going from left to right, every second field is colored. To implement that you have to start in the top left corner, and iterate over every cell, and then jump to the next row. Every second field, you have to draw a Rect.
This is a slightly edited version of your algorithm:
int startPos = 0;
for (int y = 0; y < 8; y++) {
for (int x = startPos; x < 3; x+=2) {
SDL_Rect rect;
rect.x = x * 129;
rect.y = y * 129;
rect.w = 128;
rect.h = 128;
SDL_SetRenderDrawColor(render, 159, 84, 8, 255);
SDL_RenderFillRect(render, &rect);
}
startPos = 1 - startPos;
}
Explanation:
In the first row, the first field will be colored, because startPos is 0.
Since every row has the opposite start of its predecessor, we have to change startPos as soon as the last cell of a row is drawn.
Since we only need to draw every second cell, the x has to be increased by two every iteration
Hope this helped you a little bit
Although ur problem is solved, leaving this for someone who face such issue
(anyhow its quite easy to make the board, I wrote this code)
this will create a standard 8x8 chess board
void Create_Board() {
SDL_Rect Box = { 0,0,80,80}; COORD position = { 0 };
bool toggle_color = 0;
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
if (toggle_color)
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
else
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderFillRect(renderer, &Box);
Box.x += 80;
if(j != 7) //needed for chess color pattern
toggle_color = !toggle_color;
}
Box.x = 0;
Box.y += 80;
}
}

How do you flip texture in VertexArray?

I have a spritesheet animation drawn using a vertexarray. As an example let's say I have an arrow pointing to the right with a simple animation. I would like to be able to flip the texture on the x axis so that the arrow can also point left without the need to double the size of my spritesheet.
I know that this is possible with sprites by using a negative scale factor, but in cases where I have to use vertexarray I would like to know how this is possible. I tried giving the texcoords a negative width but it did not work. Here's my example code that isolates the issue as much as possible:
#include <SFML/Graphics.hpp>
#include <iostream>
int main()
{
// CREATE THE WINDOW
sf::RenderWindow window(sf::VideoMode(200, 200), "Arrow");
window.setFramerateLimit(20);
// CREATE GRAPHICS
sf::Texture texArrow;
texArrow.loadFromFile("Arrow.png");
sf::VertexArray vaArrow;
sf::RenderStates rsArrow;
rsArrow.texture = &texArrow;
vaArrow.setPrimitiveType(sf::Quads);
vaArrow.resize(4);
vaArrow[0].position = sf::Vector2f(0, 0);
vaArrow[1].position = sf::Vector2f(100, 0);
vaArrow[2].position = sf::Vector2f(100, 100);
vaArrow[3].position = sf::Vector2f(0, 100);
vaArrow[0].texCoords = sf::Vector2f(0, 0);
vaArrow[1].texCoords = sf::Vector2f(100, 0);
vaArrow[2].texCoords = sf::Vector2f(100, 100);
vaArrow[3].texCoords = sf::Vector2f(0, 100);
// ARROW DIRECTION STATES
enum Directions { right, left};
Directions dir = right;
float AnimationFrame = 0;
// GAME LOOP
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed) { window.close(); }
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { dir = right; }
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { dir = left; }
}
// UPDATE
switch (dir)
{
case right: // NORMAL
std::cout << "Arrow is now pointing RIGHT" << std::endl;
vaArrow[0].texCoords = sf::Vector2f(AnimationFrame * 100 , 0);
vaArrow[1].texCoords = sf::Vector2f(AnimationFrame * 100 + 100 , 0);
vaArrow[2].texCoords = sf::Vector2f(AnimationFrame * 100 + 100 , 100);
vaArrow[3].texCoords = sf::Vector2f(AnimationFrame * 100 , 100);
break;
case left: // FLIPPED
std::cout << "Arrow is now pointing LEFT" << std::endl;
// THIS IS WHAT I HAVE TRIED BUT IT DOESN'T WORK:
vaArrow[0].texCoords = sf::Vector2f((AnimationFrame * 100) * -1 , 0);
vaArrow[1].texCoords = sf::Vector2f((AnimationFrame * 100 + 100) * -1 , 0);
vaArrow[2].texCoords = sf::Vector2f((AnimationFrame * 100 + 100) * -1 , 100);
vaArrow[3].texCoords = sf::Vector2f((AnimationFrame * 100) * -1 , 100);
break;
}
AnimationFrame++;
if (AnimationFrame == 5) { AnimationFrame = 0; } // Always loop 0 1 2 3 4
// RENDER
window.clear();
window.draw(vaArrow, rsArrow);
window.display();
}
return 0;
}
Spritesheet for the example:
There are many days to solve this, but your actual idea isn't that far off. Did you notice that you're basically stretching one end of the arrow over your whole quad?
The solution is pretty simple: By default. Textures aren't repeated, they're clamped (meaning that the pixels on the edges are repeated infinitely), which causes the glitch you've noticed.
So all you have to do to get your approach working is to set the texture to be repeated:
texArrow.setRepeated(true);

How do I slow down my sprite animations?

Currently I have this player.cpp class that I'm using for my sprite animations. I'm using a counter to update every frame. It animates, but it flies through the animations.
I want to slow this down. I found code that can be used to slow down sprite animations but I'm unsure how to implement it into my current program.
Below are my player.cpp file and following it is the code I found that can slow down sprite animations. When I've tried to add a clock to the counterWalking++ it didn't animate at all, and I've tried implementing this code to the same effect.
player::player()
{
rect.setSize(sf::Vector2f(32, 32));
rect.setFillColor(sf::Color::White);
rect.setPosition(300, 300);
sprite.setTextureRect(sf::IntRect(0, 0, 32, 32));
}
void player::update()
{
sprite.setPosition(rect.getPosition());
}
void player::updateMovement()
{
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) {
if (canMoveRight == true) {
rect.move(movementSpeed, 0);
sprite.setTextureRect(sf::IntRect(counterWalking * 32, 64, 32, 32));
direction = 4;
canMoveUp = true;
canMoveDown = true;
canMoveLeft = true;
canMoveRight = true;
}
}
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) {
if (canMoveLeft == true) {
rect.move(-movementSpeed, 0);
sprite.setTextureRect(sf::IntRect(counterWalking * 32, 32, 32, 32));
direction = 3;
canMoveUp = true;
canMoveDown = true;
canMoveLeft = true;
canMoveRight = true;
}
}
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) {
if (canMoveUp == true) {
rect.move(0, -movementSpeed);
sprite.setTextureRect(sf::IntRect(counterWalking * 32, 96, 32, 32));
direction = 1;
canMoveUp = true;
canMoveDown = true;
canMoveLeft = true;
canMoveRight = true;
}
}
else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) {
if (canMoveDown == true) {
rect.move(0, movementSpeed);
sprite.setTextureRect(sf::IntRect(counterWalking * 32, 0, 32, 32));
direction = 2;
canMoveUp = true;
canMoveDown = true;
canMoveLeft = true;
canMoveRight = true;
}
}
else {
//Player not moving
}
counterWalking++;
if (counterWalking == 3)
counterWalking = 0;
}
Here is the code I found that displays a slow animation:
int main()
{
sf::RenderWindow renderWindow(sf::VideoMode(640, 480), "Demo Game");
sf::Event event;
sf::Texture texture;
texture.loadFromFile("images/player.png");
sf::IntRect rectSourceSprite(0, 0, 32, 32);
sf::Sprite sprite(texture, rectSourceSprite);
sf::Clock clock;
while (renderWindow.isOpen()) {
while (renderWindow.pollEvent(event)) {
if (event.type == sf::Event::EventType::Closed)
renderWindow.close();
}
if (clock.getElapsedTime().asSeconds() > 1.0f) {
if (rectSourceSprite.left == 96)
rectSourceSprite.left = 0;
else
rectSourceSprite.left += 32;
sprite.setTextureRect(rectSourceSprite);
clock.restart();
}
renderWindow.clear();
renderWindow.draw(sprite);
renderWindow.display();
}
}
Since a solution has been given in comment but not implemented in an answer, here's some kind of "code review"
Key Binding
This code is actually using the sf::Keyboard::isKeyPressed and sf::Keyboard::isKeyReleased wich needs to be called every loop while a simple boolean switched when the key is pressed/released could to the work using events.
while (renderWindow.pollEvent(event)) {
if (event.type == sf::Event::EventType::Closed)
renderWindow.close();
}
The Event used in the main loop does also contain information about KeyEvents but isn't used that way. I'll suggest sending the evet to the player eac time a key Event happens:
while (renderWindow.pollEvent(event)) {
switch(event.type){
case sf::Event::EventType::Closed:
renderWindow.close();
break;
case sf::Event::KeyPressed: case sf::Event::KeyReleased:
myPlayer.keyEvent(event);
break;
}
}
The keyEvent function in player should then look like this:
void player::keyEvent(sf::Event event){
//Keycode of your keyboard's arrows goes from 71 to 74
if (event.key.code >= 71 && event.key.code <= 74){
//Gets the array ID from the enumeration value
int ID = event.key.code - 71;
//Stores false if the key is release, and true if it's pressed
keys[ID] = (event.type == sf::Event::KeyPressed);
}
}
Wich changes each values of the array in Player.h:
private:
bool keys[4];
Movement
Now all you have to do is call the player update() function in your game loop each second:
if (clock.getElapsedTime().asSeconds() > 1.0f) {
if (rectSourceSprite.left == 96)
rectSourceSprite.left = 0;
else
rectSourceSprite.left += 32;
sprite.setTextureRect(rectSourceSprite);
myPlayer.update();
clock.restart();
}
In the update() function, ou will then build you movement vector based on wich key is pressed:
void player::update()
{
sf::Vector2f movementVector(0,0);
//Left
if (keys[0]){
movementVector.x -= movementSpeed;
//Sends the sprite Rectangle position based on the movement type
move(32);
}
//Right
if (keys[1]){
movementVector.x += movementSpeed;
move(64);
}
//Up
if (keys[2]){
movementVector.y -= movementSpeed;
move(96);
}
//Down
if (keys[3]){
movementVector.y += movementSpeed;
move(0);
}
}
The move function will then move your sprite Position depending on the movement vector and the counter, the sprite will then be set.
Your movement counter will be incremented up to 30 and your player may only move when this counter is equal to 0, 10 or 20:
void move(int spritePos){
//Here, the check() function should tell if the player isn't going outside of the screen / in a wall
if (check(sprite.getPosition() + movementVector)){
sprite.move(movementVector);
++counterWalking %= 30;
if(!counterWalking % 10)
sprite.setTextureRect(sf::IntRect(counterWalking / 10 * 32, spritePos, 32, 32));
}
}
There are multiple ways of doing this, that's only the way I would do it
Sounds like you might need to learn about controlling the time-step. Essentially you only execute certain code when a desired amount of time has passed.
You'll commonly see this site referenced so maybe check it out:
https://gafferongames.com/post/fix_your_timestep/

How to use setMouseCallback to draw random line?

I've studied Opencv's setMouseCallback function.
I understand there are several events, list below:
CV_EVENT_MOUSEMOVE 0,
CV_EVENT_LBUTTONDOWN 1,
CV_EVENT_RBUTTONDOWN 2,
CV_EVENT_MBUTTONDOWN 3,
CV_EVENT_LBUTTONUP 4,
CV_EVENT_RBUTTONUP 5,
CV_EVENT_MBUTTONUP 6,
CV_EVENT_LBUTTONDBLCLK 7,
CV_EVENT_RBUTTONDBLCLK 8,
CV_EVENT_MBUTTONDBLCLK 9,
CV_EVENT_FLAG_LBUTTON 1,
CV_EVENT_FLAG_RBUTTON 2,
CV_EVENT_FLAG_MBUTTON 4,
CV_EVENT_FLAG_CTRLKEY 8,
CV_EVENT_FLAG_SHIFTKEY 16,
CV_EVENT_FLAG_ALTKEY 32,
and with the coordinates I get from these events, I can draw, for example: straight line, circle, ellipse...etc.
But instead of straight line, I want to draw random line, just like the "pencil" or "brush" function in Microsoft Paint.
The CV_EVENT_MOUSEMOVE does return all the coordinates as my mouse move through the image, but I don't know how to combine it with CV_EVENT_LBUTTONDOWN to represent "Start drawing line" and CV_EVENT_LBUTTONUP to represent "Finish drawing line"?
Does anyone know how to achieve my requirement with setMouseCallback?
I figured it out myself,
bool trigger;
Mat img;
void onMouse(int event, int x, int y, int flag, int param)
{
if(event == CV_EVENT_LBUTTONDOWN)
{
img = Mat::zeros(320, 240, CV_8UC3);
trigger = true;
}
if(event == CV_EVENT_LBUTTONUP)
{
trigger = false;
}
if(event == CV_EVENT_MOUSEMOVE)
{
if(trigger == true)
{
line(img, cvPoint(x, y), cvPoint(x, y), Scalar(0, 0, 255), 3, CV_AA, 0);
imshow("Drawing", img);
}
}
}
int main(int argc, char *argv[])
{
.
.
.
cvsetMouseCallback("Origin img", onMouse, NULL);
.
.
.
}