I'm working on a custom project to learn SFML and I have a problem. I want to do a 'clickable' class where I could add some fancy function, my first goal to write an 'onMouseOver' function which could be nearly the same as in Javascript. This is the class:
template <class object>
class clickable: public object{
private:
sf::Vector2i mouse;
object& aim;
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const{ target.draw(aim, states); }
public:
clickable(sf::Vector2i mouse,object& aim):mouse(mouse),aim(aim){}
bool onMouseOver(sf::Vector2i mouse) {
float width = aim.getLocalBounds().width;
float height = aim.getLocalBounds().height;
if ((mouse.x >= aim.getPosition().x) && (mouse.x <= aim.getPosition().x + width) && (mouse.y >= aim.getPosition().y) && (mouse.y <= aim.getPosition().y + height))
return true;
else return false;
}
};
And here is my main:
int main()
{
sf::Font font;
if (!font.loadFromFile("arial.ttf")) {
std::cout << "Error!";
}
sf::RenderWindow window(sf::VideoMode(800, 600), "Title", sf::Style::Default);
sf::Vector2i mouse = sf::Mouse::getPosition(window);
vector<sf::Text> objects;
objects.push_back(sf::Text("Test text.", font, 14));
objects[0].setPosition(20, 20);
clickable<sf::Text> stuff(mouse,objects[0]);
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
{
window.close();
}
if (event.type == sf::Event::MouseMoved) {
eger = sf::Mouse::getPosition(window);
}
}
window.clear();
window.draw(stuff);
if (stuff.onMouseOver(mouse)) objects[0].setFillColor(sf::Color::Red);
else objects[0].setFillColor(sf::Color::White);
window.display();
}
return 0;}
I tested this code (with different variable names, because I'm from Hungary) and it worked. My problem is that if I want to change the ' objects[0].setFillColor( sf::Color::Red ) ' to 'stuff.setFillColor(sf::Color::Red)' nothing happens on the screen. I assume that the problem is that I want to call an inherited parent-function from the child class, but I am not sure what could I do to solve the problem.
I hope it was understandable and sorry for the possible bad grammar, but I'm not a native speaker, as I mention above. Any help would be very appreciated!
Thanks in advance!
I have a very fast and easy quick fix for your problem. Simply add this line of code in your draw member function of your clickable class, before calling target.draw(aim, states); :
aim.setFillColor(this->getFillColor());
like this :
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const {
aim.setFillColor(this->getFillColor());
target.draw(aim, states);
}
or you can overload both setFillColor and getFillColor member functions :
void setFillColor(const sf::Color& c) {
aim.setFillColor(c);
}
const sf::Color& getFillColor() {
return aim.getFillColor();
}
Explanations :
You declared a variable stuff like this : clickable<sf::Text> stuff(mouse, objects[0]);
According to your template class definition, that means stuff is a clickable that inherits from sf::Text, so it's a clickable sf::Text, it's also a sf::Text.
Looking at the fields of your clickable class :
a sf::Vector2i mouse;
a object& aim;, the field aim is a reference at a object type, where object is your template parameter
so, your variable stuff holds all the data that a sf::Text object would hold, since stuff is a sf::Text, a sf::Vector2i, and a reference to a sf::Text object, which is not himself. So here, your stuff object can access 2 different sf::Text objects. Himself and the one referenced by aim.
You draw member function code shows that it draw the field aim, so the sf::Text referenced, and not the data held by himself
When, in your code, you do
objects[0].setFillColor( sf::Color::Red );
You call setFillColor method on the object that is referenced by the aim field of the stuff object, whereas that :
stuff.setFillColor(sf::Color::Red);
Changes the stuff object itself. And your draw function will draw the object referenced by the stuff object, but don't know at all modifications applied to the stuff object
So the solution is simple : all modifications applied to your stuff object need to be applied on the referenced object
You can do that when you want to draw your object ( in the draw method ) or when you ask to apply modifications ( when you call setFillColor method for example ).
By the way, Alex is right on the fact that your class is not really well designed. You can also not hold reference on sf::Text object and call the sf::Text draw method for (*this) ( or just don't overload the draw method in the clickable object and do proper copy of the sf::Text in your object )
You have two different objects of type sf::Text:
stuff
stuff's data member called aim
Calling the setFillColor method of stuff will set stuff's fill color, not aim's. But the draw method of stuff defers completely to the draw method of aim:
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const{ target.draw(aim, states); }
aim is ignorant of stuff's fill color, so window.draw(stuff) will do exactly the same thing as before.
The solution is to redesign your clickable class, and that depends on your intentions. Why do you want to both inherit from the template class and have a data member of that class type?
The simplest thing to do right now is probably to reimplement the setFillColor method of sf::Text as a new method of clickable:
void clickable::setFillColor( const Color & color ) {
aim.setFillColor( color );
}
Thanks you both of you, now I got it I understand! Both of explanations were good. I redesigned the class, for the following stucked programmers here is my solution:
template <class object>
class clickable: public object{
public:
clickable(const string& s, const sf::Font& f, unsigned int size) : object::Text(s, f, size) {}
void cmd_out() {
cout << (string) (*this).getString();
}
void graph_out(float x = 0, float y = 0) {
}
bool onMouseOver(const sf::Vector2i& eger) {
float width = (*this).getLocalBounds().width;
float height = (*this).getLocalBounds().height;
if ((eger.x >= (*this).getPosition().x) && (eger.x <= (*this).getPosition().x + width) && (eger.y >= (*this).getPosition().y) && (eger.y <= (*this).getPosition().y + height))
return true;
else return false;
}
};
Now it's working, but I can only make with this sf::Text clickable objects, but in case if I would need any other, it would be easy to add another contructor based on which is required.
Related
I've faced with The white square problem at SFML C++ while I'm trying to display a textured button. I've got an ImageButton.h which inherits from Button.h. The texture is loaded successfully (checked in debugger). But in the end, I see a white square. How to solve it?
Button.h
#ifndef BUTTON_H
#define BUTTON_H
#include<SFML/Graphics.hpp>
class Button
{
public:
Button();
Button(sf::Vector2f size, sf::Vector2f pos,sf::Color outlineColor, float sizeOutline);
void virtual draw(sf::RenderWindow* w) = 0;
protected:
sf::RectangleShape frame;
};
#endif // !BUTTON_H
Button.cpp
#include "Button.h"
Button::Button()
{
}
Button::Button(sf::Vector2f size, sf::Vector2f pos,sf::Color outlineColor, float sizeOutline)
{
frame.setPosition(pos);
frame.setSize(size);
frame.setOutlineColor(outlineColor);
frame.setOutlineThickness(sizeOutline);
}
ImageButton.h
#ifndef IMAGE_BUTTON_H
#define IMAGE_BUTTON_H
#include"Button.h"
class ImageButton : public Button
{
public:
ImageButton();
ImageButton(sf::Vector2f size, sf::Vector2f pos, sf::Color outlineColor, float sizeOutline, std::string path);
void draw(sf::RenderWindow* w);
private:
sf::Texture backTexture;
sf::Sprite background;
};
#endif // !IMAGE_BUTTON_H
ImageButton.cpp
#include "ImageButton.h"
ImageButton::ImageButton()
{
}
ImageButton::ImageButton(sf::Vector2f size, sf::Vector2f pos, sf::Color outlineColor, float sizeOutline, std::string path)
: Button(size,pos,outlineColor,sizeOutline)
{
backTexture.loadFromFile(path, sf::IntRect(sf::Vector2i(pos.x,pos.y),sf::Vector2i(size.x,size.y)));
backTexture.setSmooth(true);
background.setTexture(backTexture);
background.setPosition(pos);
}
void ImageButton::draw(sf::RenderWindow* w)
{
w->draw(this->background);
w->draw(this->frame);
}
programm.h
#ifndef PROGRAMM_H
#define PROGRAMM_H
#include<SFML/Graphics.hpp>
#include"ImageButton.h"
class programm
{
public:
programm();
void run();
private:
ImageButton b;
sf::RenderWindow* window;
sf::Event e;
void render();
void update();
};
#endif // !PROGRAMM_H
programm.cpp
#include "programm.h"
programm::programm()
{
this->window = new sf::RenderWindow(sf::VideoMode(600, 600), "Novel Editor", sf::Style::Close);
this->window->setPosition(sf::Vector2i(0, 0));
this->window->setFramerateLimit(60);
this->b = ImageButton(sf::Vector2f(50.f, 50.f), sf::Vector2f(50.f, 50.f), sf::Color::Yellow, 5.f, "images\\putin.png");
}
void programm::run()
{
while (this->window->isOpen())
{
while (this->window->pollEvent(e))
{
update();
}
}
}
void programm::render()
{
this->window->clear();
b.draw(this->window);
this->window->display();
}
void programm::update()
{
switch (e.type)
{
case sf::Event::Closed:
{
this->window->close();
break;
}
default:
break;
}
render();
}
Screenshot
programm::programm()
{
...
this->b = ImageButton(...); //< this line cause the bug
}
You are initializing your image button this->b by assigning from a local ImageButton. Now the sprite will have the texture reference of the local instance's texture, and when the local instance will "die" the texture will be freed. You need to maintain the lifetime of the texture
Solution 1: override the assignment operator and set texture there like this
ImageButton& operator=(const ImageButton& ref) {
backTexture = ref.backTexture;
background = ref.background;
background.setTexture(backTexture);
return *this;
}
Solution 2: Create a TextureManager and make it as your texture API for all your program, maintain your texture lifetime through it.
Solution 3: initialize the image button at programm constructor
programm::programm()
:b (sf::Vector2f(50.f, 50.f), sf::Vector2f(50.f, 50.f), sf::Color::Yellow, 5.f, "img.jpg")
{
...
}
Another bug
void ImageButton::draw(sf::RenderWindow* w)
{
w->draw(this->background);
w->draw(this->frame);
}
you're drawing your frame after the background, and your frame's fill color by default is white
Solution 1: draw frame first and draw background next
Solution 2: set your frame fill color alpha to 0
Button::Button(sf::Vector2f size, sf::Vector2f pos, sf::Color outlineColor, float sizeOutline)
{
frame.setPosition(pos);
frame.setSize(size);
frame.setOutlineColor(outlineColor);
frame.setFillColor(sf::Color(0, 0, 0, 0)); // <----
frame.setOutlineThickness(sizeOutline);
}
The problem you and others (on reddit,
on SO,
and elsewhere)
are facing is caused by the way the constructor and assignment operator of
sf::Sprite are (not) implemented: Since the developers decided to not
implement specialized functions and didn't make them private, the compiler
provides defaults that are not aware of the semantics of the m_texture
pointer and therefore doing a binary copy. This may be not the best approach but
it's documented.
So if you are using a sf::Sprite member in your class without taking
special measures for copying, the compiler will assume default assignment
semantics and therefore invoke the (invalid) assignment semantics of the
sf::Sprite class. The local ImageButton object you create in the
program invokes this default assignment of ImageButton
programm::programm()
{
this->window = new sf::RenderWindow(sf::VideoMode(600, 600), "Novel Editor", sf::Style::Close);
this->window->setPosition(sf::Vector2i(0, 0));
this->window->setFramerateLimit(60);
this->b = ImageButton(sf::Vector2f(50.f, 50.f), sf::Vector2f(50.f, 50.f), sf::Color::Yellow, 5.f, "images\\putin.png");
}
the Sprite in member b takes over the pointer texture from the
Sprite within the local ImageButton whose lifetime is limited to the
scope of the programm constructor. Beyond that, the sprite in your
member b holds a reference to a destroyed texture object which causes
is undefined behaviour (see also sf::Sprite Class Reference (SFML / Learn / 2.4.1 Documentation)):
If the source texture is destroyed and the sprite tries to use it,
the behavior is undefined.
By the way, I'd suggest directly using sf::RenderWindow window in the
program class not via pointer, so you could change your code to something
like this (note that I used button instead of b and shortened inlined
the implementation for the sake of brevity).
class programm
{
public:
programm():
window(sf::VideoMode(600, 600), "Novel Editor", sf::Style::Close);
button(sf::Vector2f(50.f, 50.f), sf::Vector2f(50.f, 50.f),
sf::Color::Yellow, 5.f, "images\\putin.png")
{
window->setPosition(sf::Vector2i(0, 0));
window->setFramerateLimit(60);
}
// ...
private:
// ...
sf::RenderWindow window;
ImageButton button;
};
Another option would be to provide a specialized assignment operator
(thakee nathees shows how)
or copy constructor for your ImageButton class that takes care of the
deep copy of the texture object.
Alternatively, you could think about implementing a wrapper class for
sf::Sprite to fix this issue and use this instead for you application(s).
I am sort of a noob in C++ and I am trying to make a simplegame. This is my problem:
I created class called sprite:
class Sprite
{
private:
Point2D sp_pos;
Point2D sp_vel;
SDL_Surface* sp_img;
Point2D sp_center;
Point2D sp_size;
double sp_radius;
bool sp_animated;
int sp_frames;
int sp_cur_frame;
public:
Sprite() {}
Sprite(Point2D pos, Point2D vel, SDL_Surface *img, ImageInfo info, bool animated = false, int frames = 0);
virtual void draw(SDL_Surface* screen);
virtual void update();
void setInfo (ImageInfo info);
void setPos( Point2D pos ) { sp_pos = pos; }
void setVel( Point2D vel ) { sp_vel = vel; }
void setImg (SDL_Surface* img) { sp_img = img; }
void setNextFrame() { sp_cur_frame++; }
void setFrame( int frame ) { sp_cur_frame = frame; }
void setAnimated(bool animated) { sp_animated = animated; }
void changeVelX (int c) { sp_vel.setX(c);}
void changeVelY (int c) { sp_vel.setY(c);}
void changePosX (int c) { sp_pos.setX(c);}
void changePosY (int c) { sp_pos.setY(c);}
SDL_Surface* getImg() { return sp_img; }
Point2D getPos() { return sp_pos; }
Point2D getVel() { return sp_vel; }
Point2D getCenter() { return sp_center; }
Point2D getSize() { return sp_size; }
double getRadius() { return sp_radius; }
int getCurFrame() { return sp_cur_frame; }
int getFrames() { return sp_frames; }
bool collide(Sprite &another_sprite);
};
Which has a method called "collide", this method detects a collision between two sprites, and works as follows:
bool Sprite::collide(Sprite &another_sprite)
{
double d = getPos().dist(another_sprite.getPos());
if ( d < ( getRadius() + another_sprite.getRadius() ) )
return true;
else
return false;
}
The method works fine. My problem arises with the following, I have implemented different classes that are subclasses of "Sprite" and will represent enemies in my game, so, for instance I would have objects: Class enemy1 : public Sprite, Class enemy2 : public Sprite, etc. They are different because they have different behaviors. I implemented other two helper functions called group_collide and group_group_collide, that work as follows:
bool group_collide(std::list<Sprite> &group, Sprite other_object)
{
bool collision = false;
for (std::list<Sprite>::iterator sprite = group.begin(), end = group.end(); sprite != end; ++sprite)
{
if (sprite->collide(other_object))
{
Sprite exp = Sprite(sprite->getPos(), Point2D(0, 0), exp_image, exp_info, true, 7);
exp_group.push_back(exp);
if( Mix_PlayChannel( -1, explosion, 0 ) == -1 )
{
//abort();
}
sprite = group.erase(sprite);
collision = true;
}
}
return collision;
}
int group_group_collide(std::list<Sprite> &group, std::list<Sprite> &other_group)
{
int scored = 0;
for (std::list<Sprite>::iterator it1 = group.begin(), end1 = group.end(); it1 != end1; ++it1)
{
if (group_collide(other_group, *it1))
{
it1 = group.erase(it1);
scored += 10;
}
}
return scored;
}
So, in fact, group collide will detect collisons between a sprite and a list of sprites, and group_group_collide will detect collisions between group of sprites (two different lists). The problem that arises is: There will be at least 4 types of enemies, and all of them are subclasses of my Sprite class, but I get compilation errors when I create a list of sprites and add elements that are subclasses of sprites. My solution was writing a method group_collide and group_group collide for all types of enemies, but this is quite inelegant. Is there a better way to approach this problem?
EDIT:
Thanks for your suggestions. I defined the list as a pointers list as you have suggested:
std::list<Sprite*> enemy_group;
And for instance, I am adding elements of class "Kamikaze" which is a subclass of sprite, in this way (the method update is different in this class):
enemy_group.push_back(new Kamikaze(enemy_pos, enemy_vel, 0, enemy_image, enemy_info));
However, when iterating over the list:
for (list<Sprite*>::iterator it = enemy_group.begin(), end = enemy_group.end(); it != end; ++it) {
(*it)->draw(screen);
(*it)->update();
if ((*it)->getPos().getY() > SCREEN_HEIGHT + 30)
{
delete *it;
it = enemy_group.erase(it);
}
}
The method update is called from the Sprite class, not Kamikaze class, therefore I also have object slicing problem with this approach, perhaps there is something wrong with what I have done?
I get compilation errors when I create a list of sprites and add elements that are subclasses of sprites.
All derived classes will be 'sliced' in order to fit into an object container like std::list. A std::list<Sprite*> //(Preferably a smart pointer) will avoid this problem, though the actual objects would have to be stored elsewhere.
As given in the answer by Laserbreath, you should replace std::list<Sprite> with std::list<Sprite*>. Currently, any passed subclass will be reduced to Sprite. Using a list of pointers you can avoid that.
You should use virtual functions to get the desired result.
In base class:
//This way you'll have a default collision method for a Sprite...
//...but you'll be able to re-define it in subclasses.
//I don't do THIS ONE often so I'm not sure if you'll have to perform additional steps
virtual bool Sprite::collide(Sprite &another_sprite)
{
...
}
//And this way you are making collied a pure virtual function and Sprite an abstract class...
//...meaning you can't instantiate it, but you can use it with a pointer or reference.
//Every subclass of Sprite must have a body of function collide defined
//you do this only in header of class Sprite
virtual bool virtual bool Sprite::collide(Sprite &another_sprite) = 0;
In sub-class
//if you have made collide pure virtual function, you can do this
//not sure about the first case though
bool SubSprite::collide(Sprite &another_sprite)
{
...
}
So, when collide method is called from a Sprite pointer or reference, the call will be re-directed to the designated function of a subclass.
Same goes for group_collide.
ADDITIONAL EXPLANATION:
//lets assumme you have 2 sprite types, Kamikaze and NotKamikaze
Sprite *sprite1 = new Kamikaze();
Sprite *sprite2 = new NotKamikaze();
//and lets assume collide uses pointer instead, just for this example, so it's shorter
virtual bool Sprite::collide(Sprite *another_sprite) = 0;
bool Kamikaze::collide(Sprite *another_sprite);
bool NotKamikaze::collide(Sprite *another_sprite);
//when you call collide from Sprite*, it **automatically** picks the sub-class collude method
//sprite1 is of type Sprite*, but allocated memory is of Kamikaze
//so the only function called is Kamikaze::collide
sprite1->collide(sprite2);
//sprite2 is of type Sprite*, but allocated memory is of NotKamikaze
//so the only function called is NotKamikaze::collide
sprite2->collide(sprite2);
So whenever a pure virtual function collide is called from pointer or reference, the program automatically selects the right one. If you want it to be different for every sprite type, then this is what you need.
This is where "object slicing" becomes the issue. Sprite can exist as a pointer or reference, but only to a non-abstract sub-class. So Sprite *Sprite sprite1 = new Sprite(); won't work, but Sprite *Sprite sprite1 = new SpriteSubClass(); works.
If you make Sprite::collide not purely virtual, Sprite would no longer be an abstract class, and you could make the method run from Sprite class itself, and you could do Sprite *Sprite sprite1 = new Sprite();. But I highly doubt you'll need that as you're only looking at subclasses.
Whenever a method name is shared, but its behavior differs by subclass, it must be virtual.
I'm currently trying to write a button class. It takes 2 textures, m_texture and m_onHover, and its caption, which should be automatically centered. The function update() takes care of selecting the correct texture.
class button : public sf::Drawable
{
private:
const sf::Texture *m_texture;
const sf::Texture *m_onHover;
sf::Sprite m_sprite;
public:
button();
sf::Text m_caption; // public to allow easy formating, see centerCaption()
bool mouseIsOver() const;
void update();
void setPosition(sf::Vector2f position);
void setPosition(float x, float y);
void centerCaption();
// Access functions
void setTexture(const sf::Texture &texture) { m_texture = &texture; m_sprite.setTexture(*m_texture); }
void setonHoverTexture(const sf::Texture &texture) { m_onHover = &texture; }
void setCaption(sf::String text) { m_caption.setString(text); centerCaption(); }
void setFontSize(unsigned int size) { m_caption.setCharacterSize(size); centerCaption(); }
void setFont(sf::Font& font) { m_caption.setFont(font); }
private:
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const;
};
bool button::mouseIsOver() const
{
if (m_sprite.getGlobalBounds().contains(sf::Vector2f(sf::Mouse::getPosition()))) // creating a float vector for contains() because getPosition gives int vector
{
return true;
}
else
{
return false;
}
}
Everything seems to be working, but the mouse position at which mouseIsOver() returns true seems to be moved 40 pixels above the sprite. The values in the rect from getGlobalBounds() seem to be correct when printed in the console.
Unluckily I dont have enough reputation to post a screenshot.
The cursor position should be translated to the proper coordinate system. Basically you need to use sf::RenderTarget::mapPixelToCoords (available in sf::Renderwindow by inheritance). For more details, have a look at the documentation and §Coordinates conversions of the official tutorial.
Also, you might want to consider making your button class inherit from sf::Transformable so that you don't have to manage the position/rotation/scale/... yourself. Have a look at Creating a SFML-like entity
I'm working with OpenCV and Qt 5. I need to pass a mouse callback to a namedwindow for some work I'm doing. However, I can't get it to see any of the private member variables of my class.
Here's some code:
class testWizard : public QWizard
{
Q_OBJECT
public:
testWizard();
~testWizard();
friend void mouseHandler(int, int, int, void*);
private:
cv::Mat preview;
bool drag;
cv::Rect rect;
};
The friend function:
void mouseHandler(int event, int x, int y, void* param)
{
cv::Point p1, p2;
if(event == CV_EVENT_LBUTTONDOWN && !drag)
{
p1 = cv::Point(x,y);
drag = true;
}
if(event == CV_EVENT_LBUTTONDOWN && drag)
{
cv::Mat temp;
preview.copyTo(temp);
}
}
I don't know what I'm doing wrong. I'm pretty sure this is the correct way to declare this. It is telling me that preview, and drag are undeclared identifiers. Unfortunately I need to do it this way since I need access to the private members and passing a pointer to a member function isn't possible because of the hidden this argument.
Can anyone help? Thank you!
With the friend declaration your function would have access to the members of a testWizard object. However, you still need to provide an object or a pointer to such an object to access the variables:
testWizard* wizard = getTestWizard(); // no idea how to do that
if(event == CV_EVENT_LBUTTONDOWN && !wizard->drag) { ... }
I've been trying to write something that will let me easily manage OpenGL using classes.
I took an approach of having a Drawable class [which shapes/etc would inherit from, and override a draw function], and then using a Controller class to iterate through a table of Drawable classes and draw them all.
The only issue that I've noticed that the draw() method is being called from the Drawable class, instead of the Rectangle class. ??
class Drawable {
public:
void draw();
};
class Rectangle : public Drawable {
public:
void draw();
};
class Controller {
public:
Drawable ents[200];
int ent_count;
void frame();
void append(Drawable item); // this will add an object onto the list
Controller();
};
void Drawable::draw() {
// this is the default drawing function, which should be overridden
}
void Rectangle::draw() {
// gl functions here
}
void Controller::frame() {
for(int i=0;i<ent_count,i++) {
ents[i].draw(); // draw all entities on the list
}
// here, a timer would loop back the frame() function
}
void Controller::append(Drawable item) {
ents[ent_count++]=item;
}
int main(void) {
Controller main_controller; // create a controller
Rectangle rect; // create a rectangle
main_controller.append(rect); // insert rectangle into controller list
main_controller.frame(); // start the frame loop
}
[if there are minor typing errors in that, it is because it was written as a summary of the method.]
This method that I've tried to use has not been very successful, and I'm pretty sure it has to do with inheritance.
Any ideas?
Entire source code:
#include <iostream>
#include <GL/glfw.h>
#include <GL/gl.h>
class Drawable {
public:
int x,y;
void draw();
void frame();
void create();
void destroy();
};
void Drawable::create() {
}
void Drawable::draw() {
}
class Rectangle : public Drawable {
public:
int w,h;
unsigned short r,g,b;
Rectangle(int x,int y, int w, int h, unsigned short r, unsigned short g, unsigned short b);
void draw();
};
void Rectangle::draw() {
glColor3ub(r,g,b);
glBegin(GL_QUADS);
glVertex2i(x,y);
glVertex2i(x+w,y);
glVertex2i(x+w,y+h);
glVertex2i(x,y+h);
glEnd();
}
Rectangle::Rectangle(int x,int y, int w, int h, unsigned short r, unsigned short g, unsigned short b) {
this->x=x;
this->y=y;
this->w=w;
this->r=r;
this->g=g;
this->b=b;
}
class Controller {
public:
Controller(int w,int h,int fsaa,bool fs,bool vsync,const char* title);
bool running;
int frame_limit;
Drawable entity[200];
int entity_count;
void fev();
void begin();
void bind();
void append(Drawable item);
};
Controller::Controller(int w,int h,int fsaa,bool fs,bool vsync,const char* title) {
int fullscreen= (fs ? GLFW_FULLSCREEN : GLFW_WINDOW);
bool window=glfwOpenWindow(w,h,0,0,0,0,10,10,fullscreen);
glfwSetWindowTitle(title);
frame_limit=120;
entity_count=0;
std::cout << (window ? "Successfully initialized a window.\n" : "Error initializing window!\n");
}
void Controller::begin() {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0,640,480,0,0,5);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glClearColor(0.4f,0.4f,0.4f,1.0f);
running=true;
fev();
}
void Controller::append(Drawable item) {
entity[entity_count++]=item;
}
void Controller::fev() {
glClear(GL_COLOR_BUFFER_BIT);
for (int i=0;i<entity_count;++i) {
entity[i].draw();
}
glfwSwapBuffers();
if (frame_limit>0) {
glfwSleep(1000/frame_limit*0.001);
}
if (running) {
if (glfwGetKey(GLFW_KEY_ESC) || !glfwGetWindowParam(GLFW_OPENED)) {
running=false;
}
fev();
} else {
std::cout << "terminated!";
}
}
int main(void) {
glfwInit();
Controller main(640,480,0,false,false,"WindTitle");
Rectangle rect(50,50,50,50,50,50,50);
main.append(rect);
main.begin();
}
I've been trying to write something that will let me easily manage OpenGL using classes.
Newbies often try this. But OpenGL really doesn't translate well into OOP. The problem is, that it's a finite state machine and to map properly to OOP you'd have to to a lot of state tracking between the different classes and instances.
I myself tried at least 3 times to abstract OpenGL into a OOP scheme. It always broke in some way.
Which is not to say that you can not use OOP with OpenGL. You can't just map OpenGL concepts 1:1 into classes.
Regarding your actual problem: Use virtual functions.
As others have mentioned, it would be best to try to use some of the existing wrappers.
That said, you need to use pointers for your list of entities. You are having issues with slicing.
As some of the comments mentioned, you need to make Drawable::draw() virtual so calling draw() on a Drawable will call through to the child implementation. That said, because you are adding your Drawables to a an list of objects, instead of a list of pointers to objects, the objects are being sliced. This means that they are being converted from the child type back into Drawable objects, removing the extra information about specific types.
So instead of this:
Drawable ents[200];
You should do something like this:
std::vector<Drawable*> ents;
or if you have C++11 or Boost:
std::vector<std::shared_ptr<Drawable>> ents;
And your append method would take a reference.
void Controller::append(Drawable &item) {
ents[ent_count++] = &item;
}
or
void Controller::append(Drawable &item) {
ents.push_back(&item);
}
or
void Controller::append(std::shared_ptr<Drawable> item) {
ents.push_back(item);
}
And your render loop would be like this:
for (int i = 0; i < ents.size(); ++i) {
ents[i]->draw();
}
This loop could also be cleaned up to use iterator.
You might want to have a look at Coin3D. It is a large rich set of C++ class wrappers around OpenGL. You clould either look at how they do things of just use it. There are libraries for integration with various windowing systems (Qt, Windows, Cocoa, ...).