How should i scale this for multiple buttons in SDL? - c++

I have a simple script which displays one button (which is a png image) and when the user clicks it the application quits.
But i want to add multiple buttons which is where im finding my current thinking will lead to a very long if:else situation and I am wondering if that is the only way.
This is how i have my current menu set up in a main.cpp file.
bool handle_mouse_leftClick(int x, int y, SDL_Surface *button) {
if( ( ( mouseX > x ) && ( mouseX < x + button->w ) ) && ( ( mouseY > y ) && ( mouseY < y + button->h ) ) ) {
return true;
} else {
return false;
}
}
This is my detection function.
Below is my main function which acts as my game loop, i've removed non relevant code to keep it easier to follow:
//menu button
SDL_Surface *button;
button = IMG_Load("button.png");
while(!quit){
//handle events
while( SDL_PollEvent( &event ) ){
switch(event.type){
case SDL_QUIT: quit = true; break;
case SDL_MOUSEBUTTONDOWN:
if (event.button.button == SDL_BUTTON_LEFT) {
if(handle_mouse_leftClick(btnx,btny,button)){
quit = true;
}
}
break;
}
}
The issue is should my main.cpp have all this checking going on, is going to get very long very quickly when i add more buttons so I'm wondering if I have missed a trick to simplify my efforts?

When you get right down to the basic logic/computation, I would say the answer is "no", you haven't missed any tricks. I've never found a way around checking each target one at a time - at least in terms of computation. You could make your code cleaner a lot of ways. You could have a GuiElement class that exposes a "bool IsIn( int x, int y )" method. Then your big case statement would look more like:
bool handle_mouse_leftClick(int x, int y, SDL_Surface *button)
{
if (Button1.IsIn( mouseX, mouseY )
{
// do this
}
else if (Button2.IsIn( mouseX, mouseY ))
{
// do that
}
else
{
return false;
}
}
You could then further reduce amount code with a list or table:
int handle_mouse_leftClick(int x, int y, SDL_Surface *button)
{
for (unsigned int i = 0; i < buttonList.size(); i++
{
if (buttonList[i].IsIn( mouseX, mouseY ))
{
return i; // return index of button pressed
}
return -1; // nothing pressed
}
}
But it's still ultimately looking at each rectangle one at a time.
Couple caveats:
I don't think there's much real computational overhead in checking the hit boxes of each item - unless you're doing it at some crazy high frame rate.
Sure, you could optimize the checking with some kind of spatial index (like a b-tree or quad-tree organized by the locations of the buttons on the screen), but ... see #1. ;-)
If instead of 10 or 15 buttons/controls you have THOUSANDS then you will likely want to do #2 because #1 will no longer be true.
********* Update ***********
Here's a brief sample of a class that could be used for this. As far as .h vs main.cpp, the typical approach is to put the header in a "Button.h" and the implementation (code) in a "Button.cpp", but you could just put this at the top of main.cpp to get started - it has all the logic right in the class definition.
You'll notice I didn't really write any new code. The "IsIn()" test is your logic verbatim, I just changed the variable names to match the class. And since you already have a single button, I'm assuming you can reuse the code that renders that button the Render() method.
And lastly, if is not something you're familiar with, you don't have to create a list/vector at all. The code the renders the buttons could just call "okButton.Render()", followed by "cancelButton.Render()".
Sample "button" class:
class Button
{
private:
int m_x, m_y; // coordinates of upper left corner of control
int m_width, m_height; // size of control
public:
Button(int x, int y, int width, int height, const char* caption)
{
m_x = x;
m_y = y;
m_width = width;
m_height = height;
// also store caption in variable of same type you're using now for button text
}
bool IsIn( int mouseX, int mouseY )
{
if (((mouseX > m_x) && (mouseX < m_x + m_width))
&& ((mouseY > m_y) && (mouseY < m_y + m_height ) ) ) {
return true;
} else {
return false;
}
}
void Render()
{
// use the same code you use now to render the button in OpenGL/SDL
}
};
Then to create it/them (using the list approach):
Button okButton( 10, 10, 100, 50, "OK" );
buttonList.push_back( okButton );
Button cancelButton( 150, 10, 100, 50, "Cancel" );
buttonList.push_back( cancelButton );
And in your render loop:
void Update()
{
for (unsigned int i = 0; i < buttonList.size(); i++
{
buttonList[i].Render();
}
}

Related

SFML Collisions never register in my system

Trying to make a collision system in sfml for the first time in SFML without using a tutorial, using a array-based thing like so:
bool moright, moleft, moup, xcollision, ycollision;
float xvel, yvel;
int position, predictx, predicty, cm, arraynum;
class playerClass{
public:
playerClass(){
}
void update()
{
if (moright == true){
xvel = 2;}
if (moleft == true){
xvel = -2;}
if (!(moright || moleft)){
if (xvel < 0)
xvel = 0;
if (xvel > 0)
xvel = 0;}
}
};
int main()
{
playerClass playerObject;
// Create the main window
RenderWindow window(VideoMode(1080, 720), "SFML window");
// Load a sprite to display
Texture texture;
if (!texture.loadFromFile("gsquare100x100.png"))
return EXIT_FAILURE;
Sprite sprite(texture);
Sprite floor(texture);
Sprite wall(texture);
floor.setPosition(Vector2f(0.f, 498.f));
wall.setPosition(Vector2f(598.f,0.f));
floor.setColor(Color(0, 255, 0));
floor.setScale(12.f, 12.f);
wall.setScale(12.f, 12.f);
wall.setColor(Color(0, 0, 255));
int collisions[2][4]{
{0, 400, 500, 600},
};
// Start the game loop
while (window.isOpen())
{
Vector2f position = sprite.getPosition();
cout << position.y << endl;
predictx = position.x + xvel;
predicty = position.y + yvel;
yvel = 1;
for (arraynum = 0; arraynum < 2; arraynum++){
if ((predictx > collisions[arraynum][0])&&(predictx < collisions[arraynum][1])&&(predicty > collisions[arraynum][2])&&(predicty < collisions[arraynum][3])){
if ((position.y > collisions[arraynum][3])||(position.y < collisions[arraynum][2])){
xcollision = true;}
if ((position.x > collisions[arraynum][1])||(position.x < collisions[arraynum][0])){
ycollision = true;}
}
}
if (xcollision == true)
xvel = 0;
xcollision = false;
if (ycollision == true)
yvel = 0;
ycollision = false;
sprite.move(sf::Vector2f(0.f+xvel, 0.f+yvel));
// Process events
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == Event::KeyPressed)
{if (event.key.code == Keyboard::D)
moright = true;
if (event.key.code == Keyboard::A)
moleft = true;}
if (event.type == Event::KeyReleased)
{if (event.key.code == Keyboard::D)
moright = false;
if (event.key.code == Keyboard::A)
moleft = false;}
playerObject.update();
}
However the collision never registers, removing the bit that checks from which direction the sprite is moving in from doesn't help.
Very new to c++ so apologies if this is a stupid question and for my likely overly elaborate collision system.
I've written simple collisions with SFML before, and here's my advice to you: make your code as readable as possible! Things are going to get more complicated, and you need to have a system is reusable and easy to understand.
I've read your code but I don't understand why you've used an array. I assume you're trying to check if a smaller rectangle sprite is about to exit the collisions array?
For this purpose I suggest using a FloatRect object. It has useful functions like .contains() and .intersects() that you might need in the future. One downside it that is has top and left only, and to make it more and short, we'll define a simple struct to handle that part for us, as well as work for rectangular sprites as well.
I've left comments that explain the code, but haven't tested it personally. You can do that and integrate what you've learned into your project. Good luck
#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
using namespace sf;
//using a struct is not necessarily faster. BUT it does give your code more readability and is reusable for future needs
//this struct just stores a floatRect of the given sprite/Floatrecct, defining some useful functions allowing for shorter code and more readability
struct rectangularShape
{
FloatRect containingRectangle;
//constructor with sprite input
rectangularShape(Sprite sprite)
{
this -> containingRectangle = FloatRect(Vector2f(sprite.getGlobalBounds().left, sprite.getGlobalBounds().top),
Vector2f(sprite.getGlobalBounds().left + sprite.getGlobalBounds().width,sprite.getGlobalBounds().top + sprite.getGlobalBounds().height));
}
//constructor with floatrect
rectangularShape(FloatRect rect)
{
this -> containingRectangle = rect;
}
//any useful functions for rectangular shapes-- you could add more if you want
float getTop() {return containingRectangle.top;}
float getbottom() {return containingRectangle.top + containingRectangle.height;}
float getleft() {return containingRectangle.left;}
float getright() {return containingRectangle.left + containingRectangle.width;}
};
//note the use of a FloatRect instead of the arrays you were using, this just makes it easier to understand
FloatRect inclusionArea(TopLeftVector, BottomRightVector);
Sprite sprite(texture);
//declare rectangularShapes, here we check if smallRectangle is exiting it's container
rectangularShape containingRectangle(inclusionArea);
rectangularShape smallRectangle(sprite);
//alternatively you can use the sprite's next position:
/*
spriteCopy = sprite;
spriteCopy.move(deltaTime * Vector2f(xSpeed, ySpeed));
rectangularShape smallRectangle(spriteCopy);
*/
//do the check:
if (smallRectangle.getTop() < containingRectangle.getTop() or smallRectangle.getBottom() > containingRectangle.getBottom())
//exiting in Y axis
//do something;
;
if (smallRectangle.getLeft() < containingRectangle.getLeft() or smallRectangle.getRight() > containingRectangle.getRight())
//exiting in X axis
//do something;
;
I can't comment due to low reputation.
From the code presented, it's seems like you never set xcollision or ycollision to true anywhere.

Write text input on the screen in SFML

So I'm creating a graphing calculator. I have an input string s. From the string, I can graph it using SFML. I start from the a MIN x-coordinate to a MAX x-coordinate, get the corresponding y from a EvaluateString() method, and all the coordinates to a VertexArray v. I wrote my method and the graphing method already and it all worked well.
However, I have a small issue. I want to input my string on the screen, such as "sin(cos(tan(x)))" like this. I'm struggling to find a way to do it. I kinda figured out it has to do with the event TextEntered, but still I can't find anything completely.
Please suggest me a way.
class Calculator{
public:
void main();
private:
WindowSize DefaultWindow;
sf::RenderWindow window;
Cartesian vertexX[2],vertexY[2];
sf::Vertex axis[4];
const double MAX = 10;
const double MIN = -10;
const double INCREMENT = 0.001;
};
int main(){
DefaultWindow.Max = Cartesian(10,10);
DefaultWindow.Min = Cartesian(-10,-10);
DefaultWindow.plane.width=1500;
DefaultWindow.plane.height=1500;
// Set up x and y-axis
vertexX[0] = Cartesian(-100,0);
vertexX[1] = Cartesian(100, 0);
vertexY[0] = Cartesian(0,-100);
vertexY[1] = Cartesian(0,100);
axis[0] = sf::Vertex(convertCartesiantoWindow(vertexX[0],DefaultWindow));
axis[1] = sf::Vertex(convertCartesiantoWindow(vertexX[1],DefaultWindow));
axis[2] = sf::Vertex(convertCartesiantoWindow(vertexY[0],DefaultWindow));
axis[3] = sf::Vertex(convertCartesiantoWindow(vertexY[1],DefaultWindow));
// Set up the window
window.create(sf::VideoMode(1500, 1500), "Graphing calculator");
// Input string
string s = "sin(cos(tan(x)))";
// Stack c contains all the Cartesian coordinate vertices
// Cartesian is a struct which contains x and y coordinates
Stack<Cartesian> c;
sf::VertexArray v;
// For a certain function in string s, I evaluate it
// and return the y_coordinate from the function EvaluateString (s, i)
// Push each (x,y) evaluated in the Stack c
for (double i = MIN; i <= MAX; i+= INCREMENT)
c.Push(Cartesian(i,EvaluateString(s,i)));
// v is VertexArray which contains all the vertices (x,y)
v = plot(DefaultWindow, c);
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
switch (event.type) {
case sf::Event::Closed:
window.close();
break;
}
}
}
// Draw the graph
window.clear(sf::Color::Black);
window.draw(axis,4,sf::Lines);
window.draw(v);
window.display();
}
As #super suggest, use a library would be a nice solution, and surely better than mine, but just in case this satisfies your needs, I implemented a super basic TextField class.
It may be plenty of errors, but it can gives you an idea on how to achieve that functionality.
A TextField is nothing more than a rectangle which contains a text. Since it will have a sf::Text, it must have a sf::Font. Additionally, I limit the number of characters that it will contain. In order for us to write inside the TextField, we have to know if it's selected, i.e. if it has the focus. So, a first approach could be:
class TextField : public sf::Transformable, public sf::Drawable{
private:
unsigned int m_size;
sf::Font m_font;
std::string m_text;
sf::RectangleShape m_rect;
bool m_hasfocus;
};
We need a constructor for this class:
class TextField : public sf::Transformable, public sf::Drawable{
public:
TextField(unsigned int maxChars) :
m_size(maxChars),
m_rect(sf::Vector2f(15 * m_size, 20)), // 15 pixels per char, 20 pixels height, you can tweak
m_hasfocus(false)
{
m_font.loadFromFile("C:/Windows/Fonts/Arial.ttf"); // I'm working on Windows, you can put your own font instead
m_rect.setOutlineThickness(2);
m_rect.setFillColor(sf::Color::White);
m_rect.setOutlineColor(sf::Color(127,127,127));
m_rect.setPosition(this->getPosition());
}
private:
unsigned int m_size;
sf::Font m_font;
std::string m_text;
sf::RectangleShape m_rect;
bool m_hasfocus;
};
We also need some basic methods, we want to get the text inside:
const std::string sf::TextField::getText() const{
return m_text;
}
and move it, placing it somewhere inside our window:
void sf::TextField::setPosition(float x, float y){
sf::Transformable::setPosition(x, y);
m_rect.setPosition(x, y);
}
this is a tricky one. We are overwritting setPosition method of sf::Transformable because we need to update our own m_rect.
Also, we need to know if a point is inside of the box:
bool sf::TextField::contains(sf::Vector2f point) const{
return m_rect.getGlobalBounds().contains(point);
}
pretty simple, we use cointains method of sf::RectangleShape, already in sfml.
Set (or unset) focus on the TextField:
void sf::TextField::setFocus(bool focus){
m_hasfocus = focus;
if (focus){
m_rect.setOutlineColor(sf::Color::Blue);
}
else{
m_rect.setOutlineColor(sf::Color(127, 127, 127)); // Gray color
}
}
easy one. For aesthetics, we also change the outline color of the box when focused.
And last, but not least, our TextField has to behave some way when input (aka an sf::Event) is received:
void sf::TextField::handleInput(sf::Event e){
if (!m_hasfocus || e.type != sf::Event::TextEntered)
return;
if (e.text.unicode == 8){ // Delete key
m_text = m_text.substr(0, m_text.size() - 1);
}
else if (m_text.size() < m_size){
m_text += e.text.unicode;
}
}
That delete key check is little dirty, I know. Maybe you can find better solution.
That's all! Now main looks like:
int main()
{
RenderWindow window({ 500, 500 }, "SFML", Style::Close);
sf::TextField tf(20);
tf.setPosition(30, 30);
while (window.isOpen())
{
for (Event event; window.pollEvent(event);)
if (event.type == Event::Closed)
window.close();
else if (event.type == Event::MouseButtonReleased){
auto pos = sf::Mouse::getPosition(window);
tf.setFocus(false);
if (tf.contains(sf::Vector2f(pos))){
tf.setFocus(true);
}
}
else{
tf.handleInput(event);
}
window.clear();
window.draw(tf);
window.display();
}
return 0;
}
Proof of concept:
std::string str;
sf::String text;
// In event loop...
if (event.Type == sf::Event::TextEntered)
{
// Handle ASCII characters only
if (event.Text.Unicode < 128)
{
str += static_cast<char>(event.Text.Unicode);
text.SetText(str);
}
}
// In main loop...
window.Draw(text);
This should create an sf::Event::TextEntered for input, and sf::String for output

C++ While loop ending unexpectedly

I have a while loop that runs independently in a second thread in my c++ program, however the loop ends without meeting the conditions I have set.
I have tried setting a counter to keep track of how many times the loop runs and it is different every time, ranging from eight to hundreds of times.
Does anyone know why this could be happening?
while (ghostPosition.X != playerPosition.X && ghostPosition.Y != playerPosition.Y)
{
randomDirection = getRandomNumber(1, 5);
x = ghostPosition.X;
y = ghostPosition.Y;
if (randomDirection == 1)
{
if (ghostPosition.X > 1)
{
COORD oldPosition = { x, y };
COORD moveTo = { --x, y };
updateGraphics(oldPosition, moveTo, ghost);
ghostPosition.X = ghostPosition.X--;
}
}
else if (randomDirection == 2)
{
if (ghostPosition.Y < 84)
{
COORD oldPosition = { x, y };
COORD moveTo = { x, ++y };
updateGraphics(oldPosition, moveTo, ghost);
ghostPosition.Y = ghostPosition.Y++;
}
}
else if (randomDirection == 3)
{
if (ghostPosition.X < 24)
{
COORD oldPosition = { x, y };
COORD moveTo = { ++x, y };
updateGraphics(oldPosition, moveTo, ghost);
ghostPosition.X = ghostPosition.X++;
}
}
else if (randomDirection == 4)
{
if (ghostPosition.Y > 1)
{
COORD oldPosition = { x, y };
COORD moveTo = { x, --y };
updateGraphics(oldPosition, moveTo, ghost);
ghostPosition.Y = ghostPosition.Y--;
}
}
}
functions called inside loop:
int Maze::getRandomNumber(int minimum, int maximum)
{
int output;
std::random_device randomDevice;
std::mt19937 mersenneTwister(randomDevice());
std::uniform_real_distribution<double> distribution(minimum, maximum);
return distribution(mersenneTwister);
}
The only other function called (updateGraphics) simply modifies the console's output based on the parameters passed. (I don't think that this function is the issue)
I think you want this loop to execute while the player and ghost positions are different. But that's not the condition you've defined. Your loop executes while the player and ghost X positions are different AND the player and ghost Y positions are different. If either are the same, then the loop will stop. I think you want OR.
Hard to tell without some debugging information. Why don't you print out the contents of ghostPosition and playerPosition just before the close of the while loop and post them back.
Also, running the risk of sounding pedantic, you can simply your loop logic by using a swtich statement rather than a multitude of if/else statements.

KeyPressEvent stops working when QPushButton is visible

i have a problem with my overriden KeyPressEvent in my Arkanoid game.
I use it to control the paddle( left, right). If i loose a game, QPushButton is gettin visible and i can click it to reset the game, but after this i cant control my paddle. Whats wrong?
My keyPressEvent:
void MainWindow::keyPressEvent(QKeyEvent * event)
{
int x = ui->paletka->x();
int y = ui->paletka->y();
if( ui->paletka->x() > 2 )
if( event->key() == Qt::Key_Left)
ui->paletka->move(QPoint(x-8, y));
if( ui->paletka->x() < 898 )
if( event->key() == Qt::Key_Right)
ui->paletka->move(QPoint(x+8, y));
}
Have you tried clicking somewhere around a paddle to change the focus to the window?
Also... Your code is really unreadable, please use curly braces for if-s. ALWAYS. Also what is the point of these nested ifs. Isn't it better to use && instead?
void MainWindow::keyPressEvent(QKeyEvent * event)
{
int x = ui->paletka->x();
int y = ui->paletka->y();
if( ui->paletka->x() > 2 && event->key() == Qt::Key_Left)
{
ui->paletka->move(QPoint(x-8, y));
}
if( ui->paletka->x() < 898 && event->key() == Qt::Key_Right)
{
ui->paletka->move(QPoint(x+8, y));
}
}

Object coordinates

I'm working at QT application that have a OSGWidget. How could i get coordinates of picked object in osg viewer and transfer it to QT textEdit in Dialog window. Could you give advice how to extract osg::Vec3d of selected object from viewer convert it to string and show it off? For example. I have these scene:
And when i click on add button it opens a dialog window where i have a textEdit object. Transfer coords in this editText. How could be this done? Forget to add that this cubes are imported from ads file. Probably it can help in any place.
In order to retrieve an object's coordinates from your scene, you'll need to add new event handler to your viewer. Let's call it a PickHandler.
Here's a basic code that will get you started. You'll need to add the "includes" and modify it to suit your needs. (Please note that I haven't tested it. I wrote it "by memory", but if there are any errors the should be very easy to fix).
PickHandler.h
class PickHandler: public QObject, public osgGA::GUIEventHandler {
Q_OBJECT
public:
PickHandler();
virtual bool handle( const osgGA::GUIEventAdapter& ea,
osgGA::GUIActionAdapter& aa );
signals:
void query( osg::Vec3f );
protected:
virtual ~PickHandler()
{
}
bool pick( const double x, const double y, osgViewer::Viewer* viewer );
private:
bool getPickedPoint( const double x, const double y, float buffer,
osgViewer::Viewer* viewer, osg::Vec3f& point );
};
PickHandler.cpp
PickHandler::PickHandler() : osgGA::GUIEventHandler()
{
}
bool PickHandler::handle( const osgGA::GUIEventAdapter &ea,
osgGA::GUIActionAdapter &aa )
{
osgViewer::View* viewer = dynamic_cast<osgViewer::Viewer*>( &aa );
if( !viewer ) {
return false;
}
switch( ea.getEventType() ) {
default:
break;
case osgGA::GUIEventAdapter::RELEASE: {
if( pick(ea.getXnormalized(), ea.getYnormalized(),
viewer ) )
{
return true;
}
break;
}
}
return false;
}
bool PickHandler::pick( const double x, const double y,
osgViewer::Viewer* viewer )
{
if( !viewer->getSceneData() ) {
return false;
}
osg::Vec3f point;
float buffer = 0.005f;
if( getPickedPoint( x, y, buffer, viewer, point ) ) {
emit query( point );
}
return false;
}
bool PickHandler::getPickedPoint( const double x, const double y, float buffer,
osgViewer::Viewer* viewer,
osg::Vec3f& point )
{
osg::ref_ptr<osgUtil::PolytopeIntersector> intersector( 0 );
try {
intersector = new osgUtil::PolytopeIntersector(
osgUtil::Intersector::PROJECTION,
x - buffer, y - buffer, x + buffer,
y + buffer )
;
} catch( const std::bad_alloc& ) {
return false;
}
// DimZero = check only for points
intersector->setDimensionMask( osgUtil:: PolytopeIntersector::DimZero );
//
intersector->setIntersectionLimit( osgUtil::Intersector::LIMIT_NEAREST );
osgUtil::IntersectionVisitor iv( intersector );
viewer->getCamera()->accept( iv );
if( intersector->containsIntersections() ) {
osgUtil::PolytopeIntersector::Intersection intersection =
*( intersector->getIntersections().begin() )
;
const osg::Vec3f& P = intersection.intersectionPoints[ 0 ];
if( P.isNaN() ) {
return false;
}
point.set( P[ 0 ], P[ 1 ], P[ 2 ] );
return true;
}
return false;
}
I'm using a PolytopeIntersector, since I don't have any solid models, like the cessna in the OSG's example data; only a lot of points and using a LineIntersector (the fastest) is almost impossible to get a hit. The Polytope will build a frustrum volume intersects with anything in the area you have specified (with the parameters when constructing the Polytope).
Also, you might need to play with the parameters you send to the pick function, like the buffer size. I use ea.getXNormalized() and inside pick() an osgUtil::Intersector::PROJECTION value.
If you use, say an osgUtil::Intersector::WINDOW value, you don't need to normalize the mouse values. If you don't have any "strange" transformations in your view, most likely the PROJECTION value is what you need.
Last thing, this code is rather old. With newer osg versions, maybe some of it will be seen as deprecated. I'm not sure as I haven't updated yet my picker code.
Now, with this code, when an itersection is found, it retrieves the FIRST one and send the point values via an emit. You just have to connect this emit to your SLOT and receive the cliked point coords.
Lastly, for converting something to a string, you can use the Qt String function, QString::number(...).
For example:
const QString x = QString::number( point[0], 'd', 6 );
will stringify the x-coord of the point using fixed-point format with 6 decimal places.