I'm using C++ with the SDL2 library to create a game. I'm using the SDL_ttf extension to be able to use ttf fonts and I'm trying to create my own class that would be more effective for multiple texts on the screen. The code I currently have starts out good, then crashes after about 15 seconds of running. I added more text and now it crashes after about 5 or 7 seconds. I'm looking for advice on how to solve this problem. my full Font class is as follows:
Font.h
#pragma once
#include "Graphics.h"
#include <string>
class Font
{
public:
Font(std::string path, SDL_Renderer* renderer);
~Font();
void FreeText();
void LoadText(int size, RGB_COLOR color, std::string text);
void Draw(int x, int y, Graphics& gfx, int size, RGB_COLOR color, std::string text);
private:
int width,height;
TTF_Font* font;
SDL_Texture* mTexture;
SDL_Renderer* renderer;
std::string path;
};
Font.cpp
#include "Font.h"
Font::Font(std::string path, SDL_Renderer* renderer)
:
font(NULL),
mTexture(NULL),
renderer(renderer),
path(path)
{
printf("Font con..\n");
}
Font::~Font()
{
}
void Font::LoadText(int size, RGB_COLOR color, std::string text)
{
font = TTF_OpenFont(path.c_str(), size);
SDL_Color c = {color.RED, color.GREEN, color.BLUE};
SDL_Surface* loadedSurface = TTF_RenderText_Solid(font, text.c_str(), c);
mTexture = SDL_CreateTextureFromSurface(renderer, loadedSurface);
width = loadedSurface->w;
height = loadedSurface->h;
SDL_FreeSurface(loadedSurface);
}
void Font::FreeText()
{
SDL_DestroyTexture(mTexture);
mTexture = NULL;
}
void Font::Draw(int x, int y, Graphics& gfx, int size, RGB_COLOR color, std::string text)
{
FreeText();
LoadText(size, color, text);
SDL_Rect rect = {x, y, width * gfx.GetGameDims().SCALE, height * gfx.GetGameDims().SCALE};
gfx.DrawTexture(mTexture, NULL, &rect);
}
My Graphics class just handles the actual drawing as well as dimensions of the game (screen size, tile size, color struct, gamestates, etc) So when I'm calling gfx.Draw it calls SDL_RenderCopy function.
Within my Game class I have a pointer to my Font class. (its called in my Game constructor) Then font->Draw() is called every frame; which destroys the original SDL_Texture, Loads the new text, then renders it on the screen.
My ultimate goal is to have my font class set up to where I choose the color and size from my draw function. Not sure what to check from this point on..
Any suggestions? Ideas?
This is what I get (which is what I want) but then it crashes.
I've managed to get it working. After searching a little more on SDL_ttf, I realized that in my FreeFont() function I was clearing out the SDL_Texture, however I did nothing with the TTF_Font.
Adding these lines in that function did the trick:
TTF_CloseFont(font);
font = NULL;
Related
I've been following LazyFoo's SDL tutorials (and also adding my own organization and coding style). When I got to his animation tutorial I decided to make a separate class to store the variables and methods related to the animation algorithm, rather than having global variables. He uses an array of SDL_Rects to define the boundaries of different sprites on a sprite sheet, so I used an SDL_Rect pointer to store the array in my custom class. When I compiled everything I didn't see an animation, when I compiled the original source code I did. When I started debugging things, I found that when I was rendering the sprites, the rects were actually full of garbage, even though when I initialize them the rects are just fine. I've tried to simplify the problem so many times, but every approach I take to recreate the bug in a simpler environment actually works as expected! So with that in mind I apologize for the large amount of code, because I can't seem to reduce the problem.
texture.h
#ifndef TEXTURE_H
#define TEXTURE_H
#include <SDL2/SDL.h>
#include <string>
class Animation {
public:
Animation(SDL_Renderer* renderer);
~Animation();
void load(std::string path, int frames, SDL_Rect* clips),
free(),
render(int x, int y),
next_frame();
private:
SDL_Renderer* _renderer=NULL;
SDL_Rect* _clips=NULL;
SDL_Texture* _texture=NULL;
int _frame=0, _frames=0, _width=0, _height=0;
};
#endif
texture.cpp
#include <stdio.h>
#include <SDL2/SDL_image.h>
#include "texture.h"
#include "error.h"
Animation::Animation(SDL_Renderer* renderer) {
_renderer = renderer;
}
Animation::~Animation() {
free();
_renderer = NULL;
}
void Animation::load(std::string path, int frames, SDL_Rect* clips) {
free();
SDL_Texture* texture = NULL;
SDL_Surface* surface = IMG_Load(path.c_str());
if (!surface)
throw ErrorIMG("Could not load image "+path);
SDL_SetColorKey(surface, SDL_TRUE,
SDL_MapRGB(surface->format, 0, 0xFF, 0xFF));
texture = SDL_CreateTextureFromSurface(_renderer, surface);
if (!texture)
throw ErrorSDL("Could not create texture from image "+path);
_width = surface->w;
_height = surface->h;
SDL_FreeSurface(surface);
_frames = frames;
_clips = clips;
printf("clips[%d]: w: %d h: %d\n", 0, _clips[0].w, _clips[0].h);
}
void Animation::free() {
if (_texture) {
SDL_DestroyTexture(_texture);
_texture = NULL;
_clips = NULL;
_frames = 0;
_frame = 0;
_width = 0;
_height = 0;
}
}
void Animation::render(int x, int y) {
SDL_Rect crect = _clips[_frame/4];
printf("in render (clips[%d]): w: %d, h: %d\n", _frame/4, crect.w, crect.h);
SDL_Rect render_space = {x, y, crect.w, crect.h};
SDL_RenderCopy(_renderer, _texture, &_clips[_frame], &render_space);
}
void Animation::next_frame() {
SDL_Rect crect = _clips[_frame/4];
printf("in next frame (clips[%d]): w: %d, h: %d\n", _frame/4, crect.w, crect.h);
++_frame;
if (_frame/4 >= _frames)
_frame = 0;
}
game.h
#ifndef GAME_H
#define GAME_H
#include "texture.h"
class Game {
public:
Game();
~Game();
void main();
private:
void load_media();
SDL_Window* _window=NULL;
SDL_Renderer* _renderer=NULL;
Animation* _anim=NULL;
const int SCREEN_WIDTH=640, SCREEN_HEIGHT=480;
};
#endif
game.cpp
#include <SDL2/SDL_image.h>
#include "game.h"
#include "error.h"
void Game::main() {
load_media();
bool has_quit = false;
SDL_Event event;
while (!has_quit) {
while (SDL_PollEvent(&event))
if (event.type == SDL_QUIT)
has_quit = true;
SDL_SetRenderDrawColor(_renderer, 0xff, 0xff, 0xff, 0xff);
SDL_RenderClear(_renderer);
_anim->render(100, 100);
_anim->next_frame();
SDL_RenderPresent(_renderer);
}
}
Game::Game() {
if (SDL_Init(SDL_INIT_VIDEO))
throw ErrorSDL("SDL could not initialize");
_window = SDL_CreateWindow("SDL Tutorial", SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH,
SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
if (!_window)
throw ErrorSDL("Window could not be created");
Uint32 render_flags = SDL_RENDERER_ACCELERATED;
render_flags |= SDL_RENDERER_PRESENTVSYNC;
_renderer = SDL_CreateRenderer(_window, -1, render_flags);
if (!_renderer)
throw ErrorSDL("Renderer could not be created");
SDL_SetRenderDrawColor(_renderer, 0xff, 0xff, 0xff, 0xff);
if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG))
throw ErrorIMG("SDL_image could not initialize");
}
Game::~Game() {
delete _anim;
SDL_DestroyRenderer(_renderer);
SDL_DestroyWindow(_window);
_renderer = NULL;
_window = NULL;
IMG_Quit();
SDL_Quit();
}
void Game::load_media() {
const int nclips = 4;
SDL_Rect clips[nclips];
for (int i=0; i < nclips; i++) {
clips[i].x = i*64;
clips[i].y = 0;
clips[i].w = 64;
clips[i].h = 164;
}
_anim = new Animation(_renderer);
_anim->load("sheet.png", nclips, &clips[0]);
}
You're storing a pointer to a temporary. The SDL_Rect* clips pointer that you pass to Animation::load is assigned to the member _clips used after the function returns. For this to work correctly, the data that is pointed to needs to live for as long as the Animation class is using it. The problem arises here:
void Game::load_media() {
const int nclips = 4;
SDL_Rect clips[nclips];
...
_anim->load("sheet.png", nclips, &clips[0]);
}
In this piece of code, clips is a local variable. That means it gets destroyed at the end of load_media(), and the memory contents at that location will become garbage.
There are a number of ways you could fix this. A simple one would be to use std::vector<SDL_Rect> instead of SDL_Rect*. std::vector can safely be copied and manages its internals for you. Your new code could look like:
class Animation {
...
std::vector<SDL_Rect> _clips;
...
}
void Animation::load(std::string path, int frames, std::vector<SDL_Rect> clips) {
...
_clips = clips;
...
}
void Game::load_media() {
const int nclips = 4;
std::vector<SDL_Rect> clips;
clips.resize(nclips);
...
_anim->load("sheet.png", nclips, clips);
}
And dont forget to #include <vector>. Documentation for std::vector is here. Note that std::vector has a size() method that can probably replace frames everywhere it appears.
The stack-allocated Game::load_media()::clips array disappears when it goes out of scope. Make a copy in Animation::load() instead of only storing a pointer.
The problem with my code is that I am making a pong game in SDL 2.0 in c++. I did everything until creating the movement. When the player paddle moves, it leaves behind a trail in the same color as the paddle. I watched some videos on YouTube, but when they do the movement it's nice and clear and for me to fix this but I need to recolor the background every time the player moves, which makes it being all flashy and if I hold the button I don't see the paddle at all.
#include<iostream>
#include<SDL2/SDL.h>
#include<SDL2/SDL_image.h>
#include<windows.h>
#define width 800
#define height 600
using namespace std;
bool run = true;
class Player{
private:
SDL_Window* window = SDL_CreateWindow("Pong!", SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_RESIZABLE);
SDL_Surface* Screen = SDL_GetWindowSurface(window);
Uint32 screen_color = SDL_MapRGB(Screen->format, 0, 0, 0);
Uint32 In_game_RGB = SDL_MapRGB(Screen->format, 255, 255, 255);
SDL_Rect Pl;
SDL_Rect AI;
SDL_Rect Ball;
SDL_Rect ClearP;
SDL_Rect ClearAI;
public:
Player(){
//Player parameters
Pl.x = 60;Pl.y = 225;Pl.w = 25;Pl.h = 200;
//AI parameters
AI.x = 720;AI.y = 225;AI.w = 25;AI.h = 200;
//Ball parameters
Ball.x = width/2;Ball.y = height/2+10;Ball.w = 25;Ball.h = 25;
//Recoloring parameters
ClearP.x = 0;ClearP.y = 0; ClearP.w = 375;ClearP.h = height;
ClearAI.x = 425;ClearAI.y = 0;ClearAI.w = 375;ClearAI.h = height;
//Make the screen color black
SDL_FillRect(Screen, NULL, screen_color);
}
void scrUpdate(){
SDL_UpdateWindowSurface(window);
}
void drawPlayer(){
SDL_FillRect(Screen, &Pl, In_game_RGB);
}
void drawComputer(){
SDL_FillRect(Screen, &AI, In_game_RGB);
}
void ball(){
SDL_FillRect(Screen, &Ball, In_game_RGB);
}
void Movement(){
if(GetAsyncKeyState(VK_DOWN)){
Pl.y += 2;
SDL_FillRect(Screen,&ClearP,screen_color);
}
if(GetAsyncKeyState(VK_UP)){
SDL_FillRect(Screen,&ClearP,screen_color);
Pl.y -= 2;
}
}
};
void EventCheck(){
SDL_Event event;
if(SDL_PollEvent(&event)){
if(event.type == SDL_QUIT){
run = false;
}
}
}
int main( int argc, char *argv[] )
{
SDL_Init(SDL_INIT_EVERYTHING);
Player Play;
//Player Computer();
while(run){
Play.scrUpdate();
Play.drawPlayer();
Play.drawComputer();
Play.ball();
Play.Movement();
EventCheck();
}
SDL_Quit();
return EXIT_SUCCESS;
}
It would help to show some code or an example of what you have been doing, or a link to one of the videos you have been watching:
Youtube tutorial
but I suggest taking a look at:
screen = SDL_SetVideoMode(800, 600, 32, SDL_HWSURFACE); and SDL_Flip(screen) as those have to do with screen buffering and drawing.
Another possibilty is that you are running an outdated version of SDL, or an incompatible one with your current system.
To be able to give a more complete and proper answer, I'd highly suggest adding more information about your code, screenshots of results and your version of SDL and operating system.
Also, you said it was flashy when you hold the paddle. I think it must be that you are performing your logic to move the paddle and you redraw the paddle once it's still. If you are redrawing the entire screen constantly, consider double buffering.
I was following Lazy Foo' tutorial to create text using ttf font, and everything was fine, but I needed to create several text lines in several different places with different font size and color, so I decided to use vector. Here is my code of TextTexture (mostly copy of Lazy Foo tutorial):
#ifndef TEXT_TEXTURE_HPP
#define TEXT_TEXTURE_HPP
#include "graphics.hpp"
#include "vector2.hpp"
#include <SDL2/SDL_ttf.h>
#include <string>
class TextTexture {
public:
TextTexture(
Graphics& graphics,
TTF_Font* font,
std::string textureText,
SDL_Color textColor,
Vector2 coordinates
);
~TextTexture();
void draw( Graphics& graphics );
private:
SDL_Texture* mTexture;
int mWidth;
int mHeight;
int mX;
int mY;
};
#endif // TEXT_TEXTURE_HPP
And .cpp file for it:
#include "text_texture.hpp"
#include "vector2.hpp"
#include <iostream>
#include <unistd.h>
TextTexture::TextTexture (
Graphics& graphics,
TTF_Font* font,
std::string textureText,
SDL_Color textColor,
Vector2 coordinates
) :
mTexture(NULL),
mWidth(0),
mHeight(0),
mX(0),
mY(0)
{
//Render temp surface
SDL_Surface* tempSurface = TTF_RenderUTF8_Blended (font, textureText.c_str(), textColor);
if ( tempSurface == NULL ) {
std::cout << "Unable to render text surface! SDL_ttf Error: " << TTF_GetError() << std::endl;
} else {
this -> mTexture = SDL_CreateTextureFromSurface(graphics.getRenderer(), tempSurface);
if ( this -> mTexture == NULL ) {
std::cout << "Unable to create texture from rendered text! SDL Error: " << SDL_GetError() << std::endl;
} else {
//Get image dimensions
mWidth = tempSurface -> w;
mHeight = tempSurface -> h;
// Get coordinates
this -> mX = coordinates.getX();
this -> mY = coordinates.getY();
}
SDL_FreeSurface (tempSurface);
tempSurface = NULL;
}
}
TextTexture::~TextTexture() {
//Free texture if it exists
if ( mTexture != NULL ) {
SDL_DestroyTexture( mTexture );
}
mTexture = NULL;
mWidth = 0;
mHeight = 0;
}
// FIXME somewhy affects previous dest rects
void TextTexture::draw (Graphics& graphics) {
//Set rendering space and render to screen
SDL_Rect destinationRectangle = { mX, mY, this -> mWidth, this -> mHeight };
//Render to screen
graphics.blitSurface( mTexture, NULL, &destinationRectangle );
}
I created simple Text Manager to handle vector of texts:
#ifndef TEXT_MANAGER_HPP
#define TEXT_MANAGER_HPP
#include "graphics.hpp"
#include "text_texture.hpp"
#include "vector2.hpp"
#include <string>
#include <vector>
enum fontSize {
SMALL = 16,
NORMAL = 32,
BIG = 48,
TITLE = 72
};
enum fontColor {
WHITE,
ORANGE,
BLACK
};
class TextManager {
public:
TextManager(Graphics& graphics);
~TextManager();
void addText(std::string, fontSize, fontColor, Vector2);
void draw();
void clearText();
private:
Graphics& graphics;
std::vector <TextTexture> gText;
};
#endif // TEXT_MANAGER_HPP
and .cpp file:
#include "text_manager.hpp"
#include <iostream>
TextManager::TextManager(Graphics& graphics) :
graphics(graphics)
{}
TextManager::~TextManager() {}
void TextManager::addText(std::string text, fontSize size, fontColor color, Vector2 coordinates) {
TTF_Font* tempFont = TTF_OpenFont( "resources/fonts/pixel.ttf", fontSize::TITLE );
SDL_Color tempColor = { 255, 255, 255 };
// Switch removed for shorter code
this -> gText.emplace_back(graphics, tempFont, text, tempColor, coordinates);
TTF_CloseFont(tempFont);
tempFont = NULL;
}
// FIXME
void TextManager::draw() {
std::vector<TextTexture>::iterator it;
for(it = gText.begin(); it != gText.end(); ++it) {
it -> draw(graphics);
}
}
void TextManager::clearText() {
gText.clear();
}
But when I start the application, I see something like this:
Second string is printed, but font and bonding rectangle of first line is saved, hovewer
Later I added input handler that added second line of text after pressing a button, and when there is only one line of text, everything fine, but when you add second, something weird is beginning - sometimes first text disappears, sometimes 'both' of them is shoved. As I understand, second surface of text somehow affects first one, by copying it texture on the place of the first's destination.
Here is my graphics.blitSurface, if it will help:
void Graphics::blitSurface(SDL_Texture* texture, SDL_Rect* sourceRectangle, SDL_Rect* destinationRectangle)
{
SDL_RenderCopy ( this -> _renderer, texture, sourceRectangle, destinationRectangle );
}
Where is my mistake? Sorry for bad english, I hope you will get my problem.
I figured it out somehow randomly. The thing is that when I adding object to vector, it's calls a destructor.
Here is why:
Why does my class's destructor get called when I add instances to a vector?
class Renderer{
public:
void text_to_sdl_surface(Text text, Colour colour);
Font get_font(std::string font_name, int font_size);
private:
SDL_Surface * text_surface;
};
void Renderer::text_to_sdl_surface(Text text, Colour colour) {
glColor4f(1,1,1,1);
SDL_Color text_colour;
text_colour.r = Uint8(colour.get_colour_as_int()[0]);
text_colour.g = Uint8(colour.get_colour_as_int()[1]);
text_colour.b = Uint8(colour.get_colour_as_int()[2]);
TTF_Font * temp_font = get_font(text.get_fontname(), text.get_fontsize()).get_font();
text_surface = TTF_RenderText_Blended(temp_font, text.get_string().c_str(), text_colour);
}
I am using SDL_TTF to draw text and have a memory leak that I have narrowed down to the above function, specifically this line:
text_surface = TTF_RenderText_Blended(temp_font, text.get_string().c_str(), text_colour);
After using the surface in opengl I am calling SDL_FreeSurface before the function above is run again however it is still leaking.
i'm new and still learning OOP and SDL for educational purpose.
so, i have a variable SDL_Renderer renderer. this variable needs to be initiated only once, and i initiate it in GameManager class.
and i have another class named Texture that needs that renderer.
this Texture will be used frequently.
so how do i pass this renderer? do i have to call GameManager in the Texture class? but if i do that, it means that i makeGameManager everytime i use the Texture right? or there is another way around?
thank you for helping me, i'm really sorry if my question is vague or not clear.
EDIT
this is Texture class
class Texture
{
public:
Texture();
~Texture();
int getWidth();
int getHeight();
bool loadFromFile(std::string path);
bool loadTextFromFile(std::string text, SDL_Color textColor, TTF_Font* font);
void render(int x, int y, SDL_Rect* clip = NULL);
void free();
bool lockTexture();
bool unlockTexture();
void* getPixels();
int getPitch();
private:
int vWidth;
int vHeight;
SDL_Texture* vTexture;
SDL_Renderer* renderer;
void* pPixels;
int pPitch;
};
this is the initiator
Texture::Texture()
{
vTexture = NULL;
vWidth = 0;
vHeight = 0;
renderer = GameManager::getRenderer();
}
this is GameManager class
class GameManager
{
public:
GameManager();
~GameManager();
bool intializeGame();
void gameLoop();
static SDL_Renderer* getRenderer();
private:
SDL_Window* window = NULL;
static SDL_Renderer* renderer;
TTF_Font* font = NULL;
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
};
the getRenderer() just a getter to pass the renderer
and this is my Main
int main(int argc, char* args[])
{
GameManager gameManager;
gameManager.intializeGame();
Texture charTexture;
SDL_Rect rect;
bool text = charTexture.loadFromFile("foo.png");
if (!text)
{
printf("texture not loaded");
}
rect.x = 0;
rect.y = 0;
rect.w = charTexture.getWidth();
rect.h = charTexture.getHeight();
while (true)
{
SDL_SetRenderDrawColor(GameManager::getRenderer(), 0xFF, 0xFF, 0xFF, 0xFF);
SDL_RenderClear(GameManager::getRenderer());
charTexture.render(10, 10, &rect);
SDL_RenderPresent(GameManager::getRenderer());
}
return 0;
}
i hope it's not confusing.
Disclaimer : I've never used SDL. This might be terrible, but it's based on what you gave me.
The important thing is the ownership. This is a shared ownership example. It's quite simple and takes the burden of figuring out when to destroy SDL_Renderer off of you.
class GameManager {
//BLABGLABLA
public:
std::shared_ptr<SDL_Renderer> getRenderer();
private:
std::shared_ptr<SDL_Renderer> renderer
}
class Texture
{
public:
Texture(std::shared_ptr<SDL_Renderer> theRenderer, //some other args)
private:
std::shared_ptr<SDL_Renderer> renderer;
}
So based on the name of the classes alone, you probably want GameManager to own the renderer, but you also want Texture to have access to it.
One way you can do that is to have a shared_ptr member in both classes, and to pass the renderer to texture in its constructor.
Basically the renderer object you initialized in GameManager, will only be destroyed when the last shared_ptr pointing to it is destroyed.