SDL - drawing 'negative' circles (Fog of War) - c++

I have this 800x600square I want to draw to the screen. I want to 'cut' circles in it (where alpha would be 0). Basically I'm drawing this whole rectangle over a map so in these 'circles' I drew, you can see the map, otherwise you see the grey square

So, I assume you're trying to add fog of war to one of you game?
I had a small demo I made for a local University a few weeks ago to show A* pathfinding, so I thought I could add fog of war to it for you. Here's the results:
Initial map
First, you start with a complete map, totally visible
Fog
Then, I added a surface to cover the entire screen (take note that my map is smaller than the screen, so for this case I just added fog of war on the screen, but if you have scrolling, make sure it covers each map pixel 1:1)
mFogOfWar = SDL_CreateRGBSurface(SDL_HWSURFACE, in_Width, in_Height, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);
SDL_Rect screenRect = {0, 0, in_Width, in_Height};
SDL_FillRect(mFogOfWar, &screenRect, 0xFF202020);
Then, you need to draw it... I added this call after drawing the game objects and before drawing the UI
DrawSurface(mFogOfWar, 0, 0);
Where
void RenderingManager::DrawSurface(SDL_Surface* in_Surface, int in_X, int in_Y)
{
SDL_Rect Dest = { in_X, in_Y, 0, 0 };
SDL_BlitSurface(in_Surface, NULL, mScreen, &Dest);
}
Which should give you the following result:
"Punch Surface"
I then created a 32 bits .png that looks like this (checkerboard shows alpha)
When rendering my main character, I added this call:
gRenderingManager.RemoveFogOfWar(int(mX) + SPRITE_X_OFFSET, int(mY) + SPRITE_Y_OFFSET);
The offset is only there to center the punch with the sprite, basically, what I'm passing to RemoveFogOfWar is the center of my sprite.
Remove Fog Of War
Now the meat of the fog of war. I did two versions, one where Fog of War is removed permanently and one where the fog of war is reset. My fog of war reset relies on my punch surface to have a contour where the alpha is reset to 0 and the fact that my character moves of less pixels than the contour contains per frame, otherwise I would keep the Rect where my punch was applied and I would refill it before drawing again the new punch.
Since I couldn't find a "multiply" blend with SDL, I decided to write a simple function that iterates on the punch surface and updates the alpha on the fog of war surface. The most important part is to make sure you stay within the bounds of your surfaces, so it takes up most of the code... there might be some crop functions but I didn't bother checking:
void RenderingManager::RemoveFogOfWar(int in_X, int in_Y)
{
const int halfWidth = mFogOfWarPunch->w / 2;
const int halfHeight = mFogOfWarPunch->h / 2;
SDL_Rect sourceRect = { 0, 0, mFogOfWarPunch->w, mFogOfWarPunch->h };
SDL_Rect destRect = { in_X - halfWidth, in_Y - halfHeight, mFogOfWarPunch->w, mFogOfWarPunch->h };
// Make sure our rects stays within bounds
if(destRect.x < 0)
{
sourceRect.x -= destRect.x; // remove the pixels outside of the surface
sourceRect.w -= sourceRect.x; // shrink to the surface, not to offset fog
destRect.x = 0;
destRect.w -= sourceRect.x; // shrink the width to stay within bounds
}
if(destRect.y < 0)
{
sourceRect.y -= destRect.y; // remove the pixels outside
sourceRect.h -= sourceRect.y; // shrink to the surface, not to offset fog
destRect.y = 0;
destRect.h -= sourceRect.y; // shrink the height to stay within bounds
}
int xDistanceFromEdge = (destRect.x + destRect.w) - mFogOfWar->w;
if(xDistanceFromEdge > 0) // we're busting
{
sourceRect.w -= xDistanceFromEdge;
destRect.w -= xDistanceFromEdge;
}
int yDistanceFromEdge = (destRect.y + destRect.h) - mFogOfWar->h;
if(yDistanceFromEdge > 0) // we're busting
{
sourceRect.h -= yDistanceFromEdge;
destRect.h -= yDistanceFromEdge;
}
SDL_LockSurface(mFogOfWar);
Uint32* destPixels = (Uint32*)mFogOfWar->pixels;
Uint32* srcPixels = (Uint32*)mFogOfWarPunch->pixels;
static bool keepFogRemoved = false;
for(int x = 0; x < destRect.w; ++x)
{
for(int y = 0; y < destRect.h; ++y)
{
Uint32* destPixel = destPixels + (y + destRect.y) * mFogOfWar->w + destRect.x + x;
Uint32* srcPixel = srcPixels + (y + sourceRect.y) * mFogOfWarPunch->w + sourceRect.x + x;
unsigned char* destAlpha = (unsigned char*)destPixel + 3; // fetch alpha channel
unsigned char* srcAlpha = (unsigned char*)srcPixel + 3; // fetch alpha channel
if(keepFogRemoved == true && *srcAlpha > 0)
{
continue; // skip this pixel
}
*destAlpha = *srcAlpha;
}
}
SDL_UnlockSurface(mFogOfWar);
}
Which then gave me this with keepFogRemoved = false even after the character had moved around
And this with keepFogRemoved = true
Validation
The important part is really to make sure you don't write outside of your pixel buffer, so watch out with negative offsets or offsets that would bring you out of the width or height. To validate my code, I added a simple call to RemoveFogOfWar when the mouse is clicked and tried corners and edges to make sure I didn't have a "off by one" problem
case SDL_MOUSEBUTTONDOWN:
{
if(Event.button.button == SDL_BUTTON_LEFT)
{
gRenderingManager.RemoveFogOfWar(Event.button.x, Event.button.y);
}
break;
}
Notes
Obviously, you don't need a 32 bits texture for the "punch", but it was the clearest way I could think of to show you how to do it. It could be done using as little as 1 bit per pixel (on / off). You can also add some gradient, and change the
if(keepFogRemoved == true && *srcAlpha > 0)
{
continue; // skip this pixel
}
To something like
if(*srcAlpha > *destAlpha)
{
continue;
}
To keep a smooth blend like this:
3 State Fog of War
I thought I should add this... I added a way to create a 3 state fog of war: visible, seen and fogged.
To do this, I simply keep the SDL_Rect of where I last "punched" the fog of war, and if the alpha is lower than a certain value, I clamp it at that value.
So, by simply adding
for(int x = 0; x < mLastFogOfWarPunchPosition.w; ++x)
{
for(int y = 0; y < mLastFogOfWarPunchPosition.h; ++y)
{
Uint32* destPixel = destPixels + (y + mLastFogOfWarPunchPosition.y) * mFogOfWar->w + mLastFogOfWarPunchPosition.x + x;
unsigned char* destAlpha = (unsigned char*)destPixel + 3;
if(*destAlpha < 0x60)
{
*destAlpha = 0x60;
}
}
}
mLastFogOfWarPunchPosition = destRect;
right before the loop where the fog of war is "punched", I get a fog of war similar to what you could have in games like StarCraft:
Now, since the "seen" fog of war is semi transparent, you will need to tweak your rendering method to properly clip "enemies" that would be in the fog, so you don't see them but you still see the terrain.
Hope this helps!

Related

How to account for spacing between tiles in a tile sheet

My tile-sheet has tiles that are 64x64, however between each tile there is a 10px gap and i need to account for that gap when setting the texture rectangle in the image in order to draw that tile
I tried simply adding the space upon setting the texture rectangle but the image still looks distorted
for (auto y = 0u; y < map.getTileCount().y; ++y)
{
for (auto x = 0u; x < map.getTileCount().x; ++x)
{
auto posX = static_cast<float>(x * map.getTileSize().x);
auto posY = static_cast<float>(y * map.getTileSize().y);
sf::Vector2f position(posX, posY);
tileSprite.setPosition(position);
auto tileID = tiles[y * map.getTileCount().x + x].ID; //the id of the current tile
if (tileID == 0)
{
continue; //empty tile
}
auto i = 0;
while (tileID < tileSets[i].getFirstGID())
{
++i;
}
auto relativeID = tileID - tileSets[i].getFirstGID();
auto tileX = relativeID % tileSets[i].getColumnCount();
auto tileY = relativeID / tileSets[i].getColumnCount();
textureRect.left = tileX * tileSets[i].getTileSize().x; //i am guessing this is where
// i should account for the spacing
textureRect.top = tileY * tileSets[i].getTileSize().y;
tileSprite.setTexture(mTextureHolder.get(Textures::SpriteSheet));
tileSprite.setTextureRect(textureRect);
mMapTexture.draw(tileSprite);
}
}
The code itself is working and its drawing the tiles in the correct sizes, if i use a normal 64x64 tileset without any spacing the final image looks right however with spacing included the tiles are cut out.
How do i add the gap between the tiles when setting the texture rectangle?
this is how it looks:
this is how it should look:
(NOTE: The "how it should look" image is from the Tiled editor )
Removing the spaces with a python script i found and gimp fixed the problem, however if anyone knows how to account for the spacing feel free to answer as i might need it someday

Zooming towards center of Camera on 2d Plane

Once again, camera zooming on a 2D-Plane. I searched a lot and know that there are similar questions, but I am obviously way too stupid to apply what I was able to find.
Basically I multiply the distance of all elements to the origin by mouseDelta, which is a double between 0.5 and 1. works fine for all elements, but since the anchor of the camera (camX, camY) are the upper left corner of the camera, the objects in the focus of the cam change their position in relation to the focus. I want to scroll "towards" the focus. Here is what I got, but it behaves really weird:
camX and camY, as mentioned, are the coordinates for the upper left of the cam.
mouseDelta is the zoom-level thats stored globally and is changed by each wheel-event.
screenX is the width of the screen/window (fullscreen anyways)
screenY is the height of the screen/window
if (newEvent.type == sf::Event::MouseWheelMoved) //zoom
{
mouseDelta += ((double)newEvent.mouseWheel.delta)/20;
if (mouseDelta > 1) { mouseDelta = 1; }
else if (mouseDelta < 0.5) { mouseDelta = 0.5; }
//resize graphics
for (int i = 0; i < core->universe->world->nodes.size(); i++) {
core->universe->world->nodes.at(i).pic->setSize(mouseDelta);
}
for (int i = 0; i < core->universe->world->links.size(); i++) {
core->universe->world->links.at(i).pic->setSize(mouseDelta);
}
camX = (camX + screenX/2) - (camX + screenX/2)*mouseDelta;
camY = (camY + screenY/2) - (camY + screenY/2)*mouseDelta;
}

SDL Infinite Tile Background

I'm trying to create a tiled background in SDL, one that scrolls and continues to scroll indefinitely.
So, I came up with some code and tested it out. It works well enough, but only can travel 1920 pixels along the x axis and 1080 along the Y.
Here's my code:
void Background::render(SDL_Renderer *renderer){
int Xoffset = 0;
int Yoffset = 0;
for(int y = 0; (y * 411) < 1080; y++){
for(int x = 0; (x * 405) < 1920; x++){
Xoffset = 0;
Yoffset = 0;
if(GameManager::GetInstance().getGlobalX() + (405 * x) + 405 < 0){
Xoffset = 1920;
}
if(GameManager::GetInstance().getGlobalY() + (411 * y) + 411 < 0){
Yoffset = 1080;
}
SDL_Rect backRect = {GameManager::GetInstance().getGlobalX() + (405 * x) + Xoffset, GameManager::GetInstance().getGlobalY() + (411 * y) + Yoffset, 405, 411};
SDL_RenderCopy(renderer, ResourceManager::GetInstance().getTexture("background"), 0, &backRect);
}
}
}
The getGlobalX() and getGlobalY() are where the object should be relative to the player.
You should be able to draw the 1920x1080 background more than once.
The algorithm would look something like this.
Draw a background starting at (-1920,0) (completely out of the screen)
Draw another copy of the background, this time starting at (0,0).
Every frame, draw both backgrounds one pixel to the right, so you'll have a scrolling illusion, the end of the background exiting the right will come out from the left.
Once your background at step 1 has come to (0,0), draw another background at (-1920,0) and keep scrolling.
So basically, you push two backgrounds to the right and keep putting one on the left every time you need to. This should be simple to code.

Blitting a surface onto another surface (SDL2, C++, VS2015)

I'm working on a small game for school. I tiled an image on screen, but every time my character moves I have to re-tile it (the tiles are behind the character, because it's a grid and the character moves in the cells). I tried to tile everything onto a different surface, and then have that surface blit onto my screen surface to avoid having to retile it every single time and save on process time.
It didn't really work, it's like the surface that I tile on forgets what was tiled onto it. It doesn't error it, it just doesn't display the tiled surface on my window surface.
Here's my code (the relevant part at least)
void postaviTiles() {
SDL_BlitSurface(cell, NULL, polje, &offsetcell); //cell
for (int i = 0; i < 89; i++) {
SDL_Delay(5);
if (offsetcell.x < 450) {
offsetcell.x += 50;
SDL_BlitSurface(cell, NULL, polje, &offsetcell);
}
else {
offsetcell.x = 9;
offsetcell.y += 50;
SDL_BlitSurface(cell, NULL, polje, &offsetcell);
}
SDL_UpdateWindowSurface(okno);
}
poljezrisano = true;
}
//--------------------------------------------------------------//
void tileCells() {
if (poljezrisano == false) {
postaviTiles();}
SDL_BlitSurface(polje, NULL, oknoSurface, NULL); //cell
SDL_UpdateWindowSurface(okno);
}
//--------------------------------------------------------------//
Worth mentioning is that tiling it every single time works fine, but I want to tile it once, have that on a surface and then just blit that surface onto my screen surface.
P.S.: Sorry about most of the variables and function names not being in English
The SDL_BlitSurface takes in a source surface, a clip of that source surface, then the destination surface and a position where you want to display (blit) your source.
The last parameter thats passed to SDL_BlitSurface ignores the width and height, it just takes in the x an y.
Here is a quote from the documentation:
The width and height in srcrect determine the size of the copied rectangle. Only the position is used in the dstrect (the width and height are ignored).
And the prototype for the function:
int SDL_BlitSurface(SDL_Surface* src,
const SDL_Rect* srcrect,
SDL_Surface* dst,
SDL_Rect* dstrect)
That's one thing to keep in mind, not sure if that applies to your case, since your variable names aren't English.
But essentially with this line:
SDL_BlitSurface(cell, NULL, polje, &offsetcell);
You are telling SDL that you want all of cell placed inside polje at the position offsetcell.x and offsetcell.y with the width of cell.w and the height of cell.h.
If you wanted to place cell inside polje using the width and height of offsetcell then you would have to use another blit function, namely SDL_BlitScaled
Here is how I would blit tiles inside a grid (map).
SDL_Surface* grid; // assuming this is the new grid surface that holds the blited tiles
SDL_Surface* tile; // assuming this is a single tile of width 50, height 50
SDL_Surface* windowSurface;
SDL_Window* window;
int TileWidth = 50;
int TileHeight = 50;
int NumTiles = 90;
int TileColumns = 450 / TileWidth; // = 9, so we have a grid of 9X10
bool isFillGridTiles = false;
void FillGridTiles()
{
for (int i = 0;i < NumTiles; i++)
{
auto y = i / TileColumns; // divide to get the y position and...
auto x = i % TileColumns; // the remainder is the x position inside the grid
SDL_Rect srcClip;
srcClip.x = 0;
srcClip.y = 0;
srcClip.w = TileWidth;
srcClip.h = TileHeight;
SDL_Rect dstClip;
dstClip.x = x * TileWidth;
dstClip.y = y * TileHeight;
dstClip.w = TileWidth;
dstClip.h = TileHeight;
SDL_BlitSurface(tile, &srcClip, grid, &dstClip); //since we have the same width and height, we can use SDL_BlitSurface instead of SDL_BlitScaled
}
isFillGridTiles = true;
}
void BlitOnScreen()
{
if(!isFillGridTiles)
{
FillGridTiles();
}
SDL_BlitSurface(grid, NULL, windowSurface, NULL);
SDL_UpdateWindowSurface(window);
}
Not sure if the code is complete as posted, but it seems you are not initializing offsetcell. That fits the symptom of having nothing show up. Explicit definition of offsetcell might be better than the incremental method you've provided. For example:
for( offsetcell.x = 0; offsetcell.x < 450; offsetcell.x += 50) {
for( offsetcell.y = 0; offsetcell.y < 450; offsetcell.y += 50) {
...
}
}

How to change image's alpha value without completely reloading every pixel each frame/ OPTIMIZATION

so I'm trying to bring an image to visibility in sfml 1.6 by changing it's alpha value every frame. unfortunately there isn't an overall alpha value for the image, so i have to go through each pixel, one by one and change it's alpha value.
This is extremely slow however, so I wondering how I could possibly optimize my simple code, or if there was another sfml specific way to handle this.
anyway here's the code:
Each new frame I Recolor a sprite with a added alpha value of 1.7.
// #Return Ptr: a pointer to the stack allocated image so the
// user can deallocate it later
sf::Image* RecolorSprite(sf::Sprite& sprite, sf::Color filter, bool subtract){
// the image has to survive so it's put ont he stack
sf::Image* image = new sf::Image;
*image = *sprite.GetImage();
RecolorImage(*image, filter, subtract);
sprite.SetImage(*image);
return image;
}
void RecolorImage(sf::Image& image, sf::Color filter, bool subtract){
for( int x= 0; x< image.GetWidth(); x++){
for(int y= 0; y< image.GetHeight(); y++){
if(subtract){
sf::Color pixel = image.GetPixel(x, y);
SubtractColor(pixel, filter);
image.SetPixel(x, y, pixel);
}
else
image.SetPixel(x, y, image.GetPixel(x, y) + filter);
}
}
}
// int used to stop illegal operations on unsigned chars
void SubtractColor(sf::Color& col1, sf::Color& col2){
int diff = ((int)col1.r) - ((int)col2.r);
if(diff >= 0)
col1.r -= col2.r;
else
col1.r = 0;
diff = ((int)col1.g) - ((int)col2.g);
if(diff >= 0)
col1.g -= col2.g;
else
col1.g = 0;
diff = ((int)col1.b) - ((int)col2.b);
if(diff >= 0)
col1.b -= col2.b;
else
col1.b = 0;
diff = ((int)col1.a) - ((int)col2.a);
if(diff >= 0)
col1.a -= col2.a;
else
col1.a = 0;
}
Unless I'm misunderstanding your question, you should be able to use sf::Drawable::SetColor for this, giving white as the argument but with a differing alpha value. For instance, to set sprite's alpha to 50%, you could do the following:
sprite.SetColor(sf::Color(255, 255, 255, 128));