Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Scrolling backgrounds / wrapping sprites at screen edge  (Read 13681 times)

0 Members and 2 Guests are viewing this topic.

Sauce

  • Newbie
  • *
  • Posts: 8
    • View Profile
Scrolling backgrounds / wrapping sprites at screen edge
« on: April 25, 2010, 07:51:08 am »
I'm working on a simple platformer game with a few people for an assessment and I've hit a brick wall. We have a number of large textures (1080p) that we are using for the backgrounds, which we would like to tile throughout the entire level. Rather than manually place instances of the texture at every 1080 units horizontally, i've opted to write a class deriving from sf::Sprite that will automatically handle wrapping the texture when it reaches the edge of the screen.

I've tried numerous methods of achieiving this, including manipulating sf::View's before drawing the sprite, and manually calculating the appropriate coordinates, however I just haven't had any success. There's just too many factors to take into account, including sprite position in the world, View position, sprite's scale, etc (rotation will be ignored).

Does anyone have a suggestion (or better yet, working implementation) that could help me out? I've already wasted far too much time on this feature alone.

Ashenwraith

  • Sr. Member
  • ****
  • Posts: 270
    • View Profile
Scrolling backgrounds / wrapping sprites at screen edge
« Reply #1 on: April 25, 2010, 08:07:10 am »
There's only so many texture that can be on the screen at once.

You can do this simply putting background in an array and move them together with an offset, but if that's too hard you can use a master drawable that includes several drawables.

see: http://www.sfml-dev.org/forum/viewtopic.php?t=2508

Sauce

  • Newbie
  • *
  • Posts: 8
    • View Profile
Scrolling backgrounds / wrapping sprites at screen edge
« Reply #2 on: April 25, 2010, 08:13:05 am »
the array method wont really help me much, i need the texture to tile indefinitely as we are working on multiple levels, each of a different lengths. We should just be able to determine the coordinates to offset the sprites by on a per-frame basis but the math for accomplishing this escapes me.

It's quite sad because the art our team has produced is brilliant and isn't being put to good use at the moment.

Ashenwraith

  • Sr. Member
  • ****
  • Posts: 270
    • View Profile
Scrolling backgrounds / wrapping sprites at screen edge
« Reply #3 on: April 25, 2010, 08:53:49 am »
I think I posted everything you need.

BTW you shouldn't be scaling the background if you are, you should be zooming.

Sauce

  • Newbie
  • *
  • Posts: 8
    • View Profile
Scrolling backgrounds / wrapping sprites at screen edge
« Reply #4 on: April 25, 2010, 09:24:20 am »
I'm not scaling the background, though I may need to in future. That's why its important that I also take that into account. I've already read that thread multiple times when researching this problem and it wasn't of any help to me.

Ashenwraith

  • Sr. Member
  • ****
  • Posts: 270
    • View Profile
Scrolling backgrounds / wrapping sprites at screen edge
« Reply #5 on: April 25, 2010, 09:46:07 am »
If you can't figure it out try using the mappy add on.

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Scrolling backgrounds / wrapping sprites at screen edge
« Reply #6 on: April 25, 2010, 11:29:41 am »
First, you shouldn't derive from sf::Sprite. Your tile manager is obviously no sprite, nor can it be used in every context a sf::Sprite is requested. Therefore, public inheritance is not appropriate. Aggregation is an alternative (if you indeed need to store sf::Sprites).

You can get the left-upper corner of the current view, using sf::RenderWindow::GetView(). If you want the background to move, you can compute the left upper position of the first tile as follows:
Code: [Select]
beginLeft = static_cast<int>(viewLeft) / TileSize * TileSize;
beginTop = static_cast<int>(viewTop) / TileSize * TileSize;

By integer-dividing, the decimal part is cut, so beginLeft and beginTop are a multiple of the tile size. In case you want to have a static background, it's even easier, just begin drawing at (viewLeft, viewTop).

As soon as you have managed this, you can think about scaling.
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Sauce

  • Newbie
  • *
  • Posts: 8
    • View Profile
Scrolling backgrounds / wrapping sprites at screen edge
« Reply #7 on: April 25, 2010, 12:49:16 pm »
This isn't my "tile manager". This is just a specialisation of a sprite. Thus I've chosen public inheritance. I want to use it in the same context as a sprite. The idea is that the sprite simply wraps at the screen edge. is that so hard to understand?

Also, I'm perfectly aware of how to get the rect of the current view. If it was as simple as that then I wouldn't be asking how to do it. It's the combination of all those factors that's making the math difficult.

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Scrolling backgrounds / wrapping sprites at screen edge
« Reply #8 on: April 25, 2010, 01:57:38 pm »
Quote from: "Sauce"
The idea is that the sprite simply wraps at the screen edge.
Yes, and that's not what a sf::Sprite is designed for. You can't map multiple equal textures to one sprite. You can't let a sprite reference two parts of a sf::Image. That's why you need at least two sprites, and thats why inheritance is inappropriate. The only way to use one sprite is to rotate the sf::Image pixels every frame, which is terribly inefficient.

You can still derive from sf::Drawable and create your WrappingSprite class. Like this, it's possible to use your class in a similar way as sf::Sprite.

Quote from: "Sauce"
Also, I'm perfectly aware of how to get the rect of the current view. If it was as simple as that then I wouldn't be asking how to do it.
Okay, but you have to consider there are a lot of trivial questions in this forum.

Quote from: "Sauce"
It's the combination of all those factors that's making the math difficult.
What do you expect? As long as you don't tell us what exactly bothers you, we can't really help you. And if I brought a concrete suggestion, you would probably again find it too easy.

Quote from: "Sauce"
is that so hard to understand?
It's hard to understand what is complicated. At least, wrapping around a sf::Sprite alone is not. If you want to have everything at once, it may be.

But I already suggested you to implement the features stepwise. First handle the default view, then regard translations, scales and so on... Did you try this? What did you manage, and where did you get stuck?
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

gsaurus

  • Sr. Member
  • ****
  • Posts: 262
    • View Profile
    • Evolution Engine
Scrolling backgrounds / wrapping sprites at screen edge
« Reply #9 on: April 25, 2010, 02:38:24 pm »
I think what you need is a circular buffer of sprites. Use a bidirectional queue of sprites (can be represented by a list, a vector, whatever). Each time one edge of the queue becomes out of the screen, move it to the other edge.

Let's assume you have only one image of the size of the screen, and you want to tile it when scrolling. In this case you need two sprites, one on side of the other.
When the right side of the left sprite is behind the left side of the view, it means it went out of the screen. Thus, overpass that sprite over the one on it's side.
Code: [Select]
   if (spr.GetPosition().x + spr.GetSize().x < viewLeft)
        spr.Move(spr.GetSize().x * 2, 0);

If you scroll left, up or down, do a similar test (if scrolling in all directions you need 4 sprites)

GetSize already takes in account the scale, so it should work fine.


If your sprites are smaller than the screen or you want to display more than one sprite in a row, you can do the same with more sprites, using a bidirectional queue. You need only to test the sprite of one of the edges:
Code: [Select]
   sf::Sprite& leftSprite = mySpritesQueue.getLeftSprite();
    if (leftSprite .GetPosition().x > viewLeft){
        // a hole was created on the left side of the view,
        // place the sprite of the right edge of the layer (which is out of the screen) behind the left edge
        // (...) respective code here
    }else if (leftSprite .GetPosition().x + leftSprite .GetSize().x < viewLeft){
         // leftSprite is out of the screen, place it into the other edge of the queue
        // (...) respective code here
}

If all sprites are of same size, moving a sprite to the other edge is simply
Code: [Select]
sprite.Move(sprite.GetSize().x * numSpritesOnTheQueue); // or negative move if moving right edge to the left Otherwise store the length of the buffer in pixels somewhere.

If you need multiple sprites to tile in all directions you need a circular matrix.
Pluma - Plug-in Management Framework

Sauce

  • Newbie
  • *
  • Posts: 8
    • View Profile
Scrolling backgrounds / wrapping sprites at screen edge
« Reply #10 on: April 25, 2010, 02:50:46 pm »
My current (failed) implementation is the method using sf::View (which doesnt work, at all) I don't have the old code which worked so long as the view didnt change as I overwrote it >_<

You seem to be under the impression that I'm trying to draw more than one sprite (with more than one image) within the same sprite. This is not the case. What I'm trying to do is wrap the sprite when it gets to the screen edge, so for example the sprite goes over the right edge of the screen, and loops back to the other side of the screen. That's all I want to do.

edit: thanks gsaurus. I thought getSize() returned the scaling factor, not the size in pixels. That will make things easier.

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Scrolling backgrounds / wrapping sprites at screen edge
« Reply #11 on: April 25, 2010, 03:07:48 pm »
Quote from: "Sauce"
You seem to be under the impression that I'm trying to draw more than one sprite (with more than one image) within the same sprite. This is not the case. What I'm trying to do is wrap the sprite when it gets to the screen edge, so for example the sprite goes over the right edge of the screen, and loops back to the other side of the screen. That's all I want to do.
No, I did understand that correctly. Maybe I didn't express myself clearly, sorry in this case. The fact is, you can't draw one sf::Sprite only once to achieve what you want. Either move it or have multiple sprites.

If performance matters, you could cut the sprites at the window edges with sf::Sprite::SetSubRect(). Like this, the pixels outside the window aren't looped through.

Quote from: "Sauce"
I thought getSize() returned the scaling factor, not the size in pixels.
The other thing is GetScale(). ;)
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Sauce

  • Newbie
  • *
  • Posts: 8
    • View Profile
Scrolling backgrounds / wrapping sprites at screen edge
« Reply #12 on: April 25, 2010, 03:40:13 pm »
Okay, sorry about the communication error :P

I was already aware I'd need to use more than one sprite (or reuse the same one) to achieve it. Here's what I've got so far;

Code: [Select]
void WrappingSprite::Render(sf::RenderTarget &target) const
{
FloatRect screenRect = target.GetView().GetRect();
float width = screenRect.Right - screenRect.Left;
float height = screenRect.Bottom - screenRect.Top;

sf::Vector2f pos;
pos.x = screenRect.Left + fmod(pos.x, width);
pos.y = screenRect.Top + fmod(pos.y, height);

sf::Sprite temp(*this);
temp.SetPosition(pos);
target.Draw(temp);

if(GetPosition().x < screenRect.Left)
{
temp.Move(GetSize().x * 1.0f, 0);
}

if(GetPosition().x + GetSize().x > screenRect.Right)
{
temp.Move(GetSize().x * -1.0f, 0);
}

target.Draw(temp);
}


The problem (as I was having before) is that I can only get it to wrap twice... No matter what I do! I would have thought fmod() would have done the trick, but it's still no good.

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Scrolling backgrounds / wrapping sprites at screen edge
« Reply #13 on: April 25, 2010, 04:06:01 pm »
Code: [Select]
sf::Vector2f pos;
pos.x = screenRect.Left + fmod(pos.x, width);
pos.y = screenRect.Top + fmod(pos.y, height);
You access pos.x and pos.y, which are both zero (because the vector has been default-constructed). fmod(0, anything) should return 0, so this has not much effect.


Code: [Select]
sf::Sprite temp(*this);
temp.SetPosition(pos);
target.Draw(temp);
Why create a temporary sprite? Is your class now deriving from sf::Sprite? If so, just invoke the base method:
Code: [Select]
sf::Sprite::Render(target);

Code: [Select]
if(GetPosition().x < screenRect.Left)You might replace if with while. And don't forget to draw after each translation. You should also make sure that the sprite isn't moved back in -x direction after it has been moved forward (use else if instead of the second if).


Code: [Select]
temp.Move(GetSize().x * 1.0f, 0);Is GetSize().x not enough? Or why * 1.0f? :P
Also, instead of GetSize().x * -1.0f, use -GetSize().x. ;)
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Sauce

  • Newbie
  • *
  • Posts: 8
    • View Profile
Scrolling backgrounds / wrapping sprites at screen edge
« Reply #14 on: April 25, 2010, 04:32:35 pm »
Quote from: "Nexus"
Code: [Select]
sf::Vector2f pos;
pos.x = screenRect.Left + fmod(pos.x, width);
pos.y = screenRect.Top + fmod(pos.y, height);
You access pos.x and pos.y, which are both zero (because the vector has been default-constructed). fmod(0, anything) should return 0, so this has not much effect.


Code: [Select]
sf::Sprite temp(*this);
temp.SetPosition(pos);
target.Draw(temp);
Why create a temporary sprite? Is your class now deriving from sf::Sprite? If so, just invoke the base method:
Code: [Select]
sf::Sprite::Render(target);

Code: [Select]
if(GetPosition().x < screenRect.Left)You might replace if with while. And don't forget to draw after each translation. You should also make sure that the sprite isn't moved back in -x direction after it has been moved forward (use else if instead of the second if).


Code: [Select]
temp.Move(GetSize().x * 1.0f, 0);Is GetSize().x not enough? Or why * 1.0f? :P
Also, instead of GetSize().x * -1.0f, use -GetSize().x. ;)


lol woops about the zero thing with the vector. It was meant to be initialised to the value of GetPos(). Serves me right for watching tv while coding. As with the * 1.0 and -1.0 thing, I just left it there in case i wanted to quickly set it back to 2 and -2 :P

Unfortunately the problem still exists. It just refuses to wrap more than twice. For reference:

Code: [Select]
void WrappingSprite::Render(sf::RenderTarget &target) const
{
FloatRect screenRect = target.GetView().GetRect();
float width = screenRect.Right - screenRect.Left;
float height = screenRect.Bottom - screenRect.Top;

sf::Vector2f pos = GetPosition();
pos.x = screenRect.Left + fmod(pos.x, width);
pos.y = screenRect.Top + fmod(pos.y, height);

sf::Sprite temp(*this);
temp.SetPosition(pos);
target.Draw(temp);

if(GetPosition().x < screenRect.Left)
{
temp.Move(GetSize().x, 0);
}

if(GetPosition().x + GetSize().x > screenRect.Right)
{
temp.Move(-GetSize().x, 0);
}

target.Draw(temp);
}