[SDL] Problem with text output using SDL_ttf (probably).

Started by
2 comments, last by rip-off 12 years, 7 months ago
Hey there
I have recently decided to make some simple game and browsing through internet I found this library - SDL. There were a lot of opinions about SDL and I think it has enough functions to realize my 2D project.
So, I have main application loop, which draws surface from Engine class onto main surface - the screen. The Engine class has a function named "drawText" which draws given string onto Engine's surface using SDL_ttf function. Though, after I run the application the screen itself is whole black. First I found out that I need to use SDL_Flip function to update the screen but it didn't result in anything. Below I am going to post my code, hopefully someone could re-view it and give me some tips how can I make it work properly.

main.cpp:
#include <stdio.h>
#include <SDL/SDL.h>

#include "../headers/engine.h"
#include "../headers/game.h"
#include "../headers/player.h"

const int TICKS = (int) 1000 / 60;

int main(int argc, char* argv[])
{
SDL_Surface *screen;
SDL_Event event;

Engine *engine = Engine::getInstance();
Game *game = Game::getInstance();
Player *player = Player::getInstance();

int keypress = 0;
if(SDL_Init(SDL_INIT_VIDEO) < 0)
return 1;

if(!(screen = SDL_SetVideoMode(800, 600, 32, SDL_HWSURFACE)))
{
SDL_Quit();
return 1;
}

SDL_WM_SetCaption("Testing Caption", "");
if(!engine->load())
{
SDL_Quit();
return 1;
}
engine->drawText("Herro, I am test message!", 100, 100, {0, 255, 0, 100}, 12);
SDL_Rect position = {0, 0, 800, 600};

int32_t ticks = SDL_GetTicks(), sleepTime = 0;
while(game->isRunning())
{
while(SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
game->setRunning(false);
break;
case SDL_KEYDOWN:
switch(event.key.keysym.sym)
{
case SDLK_ESCAPE:
game->setRunning(false);
break;
}
break;
}
}

SDL_BlitSurface(engine->getSurface(), &position, screen, &position);
SDL_Flip(screen);

ticks += TICKS;
sleepTime = ticks - SDL_GetTicks();
if(sleepTime > 0)
SDL_Delay(sleepTime);
}

SDL_Quit();
return 0;
}


engine.h:
#ifndef __ENGINE__
#define __ENGINE__

#include <SDL/SDL.h>
#include <SDL/SDL_ttf.h>
#include <iostream>
#include <string>

class Engine
{
public:
Engine();
~Engine();

static Engine *getInstance()
{
static Engine engine;
return &engine;
}

bool load();
SDL_Surface *getSurface() { return surface; }
void drawText(std::string, int x, int y, SDL_Color, int);

private:
SDL_Surface *surface;
SDL_Surface *textSurface;
TTF_Font *textFont[12];
};

#endif


#include "../headers/engine.h"

Engine::Engine()
{
TTF_Init();
surface = new SDL_Surface;
}

Engine::~Engine()
{
for(int i = 1; i <= 18; ++i)
TTF_CloseFont(textFont);

SDL_FreeSurface(textSurface);
SDL_FreeSurface(surface);
}

bool Engine::load()
{
for(int i = 1; i <= 18; ++i)
{
textFont = TTF_OpenFont("./resources/fonts/arial.ttf", i);
if(!textFont)
return false;
}

return true;
}

void Engine::drawText(std::string text, int x, int y, SDL_Color color, int fontSize)
{
if(fontSize < 1 || fontSize > 18)
return;

textSurface = TTF_RenderText_Shaded(textFont[fontSize], text.c_str(), color, {0, 0, 0});
SDL_Rect pos = {x, y, textSurface->w, textSurface->h};
SDL_BlitSurface(textSurface, &pos, surface, &pos);
}


I am not going to post Player and Game classes as Player class is actually empty and game contains about 2 functions which change/return one boolean variable, so I don't think it does matter in this case.

Regards,
Diath.
Advertisement
Don't create SDL_Surfaces with new. Use SDL_CreateRGBSurface if you want to create a new empty surface.

If you call drawText more than once you will have a memory leak.
Sorry for the late answer, it's been late evening my time when I made the thread.

So I replaced the "new" method with SDL_CreateRGBSurface and added SDL_FreeSurface in drawText on textSurface. Though, it still doesn't output anything.

This is my current code:
#include "../headers/engine.h"

Engine::Engine()
{
TTF_Init();
surface = SDL_CreateRGBSurface(SDL_HWSURFACE, 800, 600, 32, 0, 0, 0, 0);
}

Engine::~Engine()
{
for(int i = 1; i <= 18; ++i)
TTF_CloseFont(textFont);

//SDL_FreeSurface(textSurface);
SDL_FreeSurface(surface);
}

bool Engine::load()
{
for(int i = 1; i <= 18; ++i)
{
textFont = TTF_OpenFont("./resources/fonts/arial.ttf", i);
if(!textFont)
return false;
}

return true;
}

void Engine::drawText(std::string text, int x, int y, SDL_Color color, int fontSize)
{
if(fontSize < 1 || fontSize > 18)
return;

textSurface = TTF_RenderText_Shaded(textFont[fontSize], text.c_str(), color, {0, 0, 0});
SDL_Rect pos = {x, y, textSurface->w, textSurface->h};
SDL_BlitSurface(textSurface, &pos, surface, &pos);
SDL_FreeSurface(textSurface);
}
You're not testing the return values of TTF_Init() or SDL_CreateRGBSurface(). These are actually called before SDL_Init() in main(), which is a problem.

I suggest either encapsulating SDL initialisation completely in a class that is constructed at the start of main(), or doing it all in a free function that is called at the start of main(). Essentially, either tie SDL/TTF initialisation to the lifetime of a specific object (not necessarily the engine, but that is an option) or don't, but certainly don't do both.

The identifier __ENGINE__ is reserved for the compiler, as are any that begin with two underscores, an underscore with a capital letter. Identifiers beginning with a single underscore are reserved in the global scope. I'd recommend using ENGINE_H instead.

Your Engine constructor should zero initialise all variables, in case the destructor needs to run before you get a change to call init(). Another option is to move the "init" logic into the constructor. Remember you can use exceptions to signal errors from a constructor.

For performance reasons, you should probably avoid a software back-buffer in your engine class unless you have a good reason. Instead, use SDL_GetVideoSurface() or pass the pointer returned by SDL_SetVideoMode() to the constructor of the Engine class, so it can draw "directly" to the screen.

Your font array is too small, you overrun the bounds in the Engine constructor. I'd recommend using a named constant for the size, or ditching the size and using std::vector or std::map to handle fonts. std::map is a nice solution, you can maintain a "sparse" mapping, only loading fonts sizes when they are actually requested.

The variable "textSurface" should be local to drawText.

I would recommend against using singletons in this code, there is no need for them.

This topic is closed to new replies.

Advertisement