Mostrando entradas con la etiqueta Scrolling. Mostrar todas las entradas
Mostrando entradas con la etiqueta Scrolling. Mostrar todas las entradas

martes, 29 de diciembre de 2015

Parallax Scrolling in Love2d. Part 3: Different Camera Spaces

Hi!

This is the last installment of the series on parallax scrolling in Love2d. In the previous posts, we first discussed how we could implement this technique by defining a camera component and the concept of layers, and how we could reason about the two different coordinate systems that we had: the world and the camera coordinate systems. Consider reading these previous posts before going on, because this installment builds upon them.

We will see now how we can represent an object that is in a given camera space in another camera space, which can be useful for multiplayer games. The problem statement is depicted in Figure 1. We see that we have two cameras, each one representing a fragment of the same world. One of the cameras (camera 1) is in the position (x1, y1), whereas camera 2 is in (x2, y2). Again, for the sake of simplicity, we will focus on the X dimension, but the same reasoning applies to the Y dimension. Each camera follows a different player, namely player 1 and player 2, which have their positions in their camera spaces posPlayer1_cam1 and posPlayer2_cam2, respectively. Now, assume that we want to represent a ghost version of  player 2 in the camera space of player 1, so that player 1 can see the position of player 2. Which is the position of player 2 in the reference system of camera 1, that is, which is the value of posPlayer2_cam1?


 Figure 1. Problem statement

At first sight, the problem seems trivial, and for most cases, it actually is. We simply need to represent the (world) position of player 2 in relation to the camera. Therefore, if the position of player 2 is posPlayer2, and as usual we assume that players belong to layers with scale = 1, we know that:

posPlayer2_cam1 = posPlayer2 - posCamera1 (1) 

Likewise, the position where the player will be drawn would be:

posPlayer2_draw = posCamera1 + posPlayer2 - posCamera1 = posPlayer2 (2)

And that's it. This is what works in most of the cases.

However, there is a situation that we need to consider. Assume that there are obstacles with which the players can collide. If these obstacles belong to a layer with scale = 1, then everything works as we've just described. However, if obstacles belong to layers with a different scale, weird things happen. Suppose that obstacles belong to a layer with scale = 2. Then, as we saw in the previous post, advancing the camera u units result in the object being moved 2u units to the left. Therefore, if we used simply (1) or (2), player 1 would have the impression that player 2 collides (stops) before reaching the obstacle, as depicted in Figure 2. 


 Figure 2. Player 2 reaches the obstacle before it is perceived by player 1

This is happening because whereas the obstacles are moving with a rate of 2u units to the left, player 2 is being moved in camera 1 at a rate of 1u unit to the right. Therefore, player 2 reaches the obstacle in camera 2 before it reaches it in camera 1. The solution is moving player 2 in camera 1 at the same rate as the obstacles move but in opposite direction, that is, 2u units to the right. 

If obstacles belong to layers with scale = n, 0<n<1, then player 2 would reach the obstacle in camera 1 before it reaches it in camera 2, and player 1 would therefore perceive as if player 2 had run through the obstacle, as depicted in Figure 3.


Figure 3. Player 1 perceives that player 2 run through the obstacle

Therefore, the solution consists of correcting this displacement: player 2 has to move in camera 1 at the same rate than obstacles. If camera 1 remains still and camera 2 advance u units, player 2 has to be moved 2u units to the right in camera 1. If camera 2 remains still and camera 1 advance u units, player 2 has to be moved 2u units to the left in camera 1. So the general pattern is: for every unit u of difference between the positions of camera 2 and camera 1, the position of player 2 has to advance 2u units. 

posPlayer2_cam1 = posPlayer2_cam2 + 2*(x2 - x1) = posPlayer2 - x2 + 2*(x2 - x1)  (3)
posPlayer2_draw = x1 + posPlayer2_cam1 = x1 + posPlayer2 - x2 + 2*(x2-x1) (4)

We can simplify (3) and (4) as follows:

posPlayer2_cam1 = posPlayer2 + x2 - 2 * x1
posPlayer2_draw = posPlayer2 + x2 - x1

Finally, we can generalize the formulas for a player in a layer with scale = n, and obstacles in a layer with scale = m:

posPlayer2_cam1 = posPlayer2 + (m - n)*x2 - m*x1
posPlayer2_draw = posPlayer2 + (m - n)*x2 + (1 - m)*x1

And this is all! Hope you enjoyed this series of posts as much as I enjoyed writing it. As I mentioned in the first post, these are the considerations that I took into account while developing the parallax scrolling system for our game to come, Breaking Fast, the result of which you can watch in the following video:



See you!

jueves, 10 de diciembre de 2015

Parallax Scrolling in Love2d. Part 2: Testing Collisions in different Layers

Hi all!

This is the second installment of the series on parallax scrolling for Love2d. In the previous post, we discussed how we could implement parallax scrolling in Love2d, by defining a camera component that was in charge of managing the different layers. In this post, we are going to provide further insight on this implementation and we are going to discuss how we can detect collisions happening between objects that belong to different layers. The gist here is noticing that we have two different, interrelated coordinate systems, and that in order to detect collisions, we must compare the positions of the objects in the same reference frame. 

Let's start by defining the two coordinate systems that we have, namely the camera coordinate system, and the world coordinate system. An object will have different values of its position in each system, although it is possible to move from one system to the other by simple mathematical operations. Let's assume that we follow up the example of the last post, in which we wanted the camera to track the player in the center of the screen. Figure 1 shows a diagram that relates the world coordinates with the camera coordinates of the player. We will focus only on the X dimension, but the same reasoning would apply to the Y dimension. Note that we define a world of 10000 x 10000 pixels, where the origin of such world is at the top left corner of the screen. The camera has a dimension of 1920 x 1080 pixels, which corresponds with the standard resolution of most screens today. Every object contained in the camera (the red rectangle) will be visible. As you can see, assuming that the position of the camera in the world is (x, y), the player will have different coordinates in camera space (960) and in world space (x + 960).

Figure 1. The player position in camera and world spaces 

In our example, we assume that the camera is updated as a consequence of the movement of the player (which is logical if we want the camera to track the player). Therefore, this is what happens in each frame:
  1. The player presses the right key, which increases the world position of the player by a rate depending on the speed we want the character to have. 
  2. As a consequence of the update on the character position, we have to update the world position of the camera, which is advanced the same units as the player has moved. That is, if the player has moved 50 units, the camera will move 50 units. The fact that the camera moves at the same rate as the player is due to the fact that the player belongs to a layer with scale = 1, as we will analyse further in the following.
Given that we update the camera when we update the player, we can consider that the world position* of the camera is a function of the world position of the player, and this function is as follows:

posCamera = posPlayer - 960 (1)

* From now on, we will simply say position when we refer to world position.

In general, if we have an object that belongs to a layer with a scale = n, the relationship between the position of the object in world and camera space is given by:

posObject_cam = posObject - posCamera * n (2)

In particular, think of the player, who belongs to a layer of scale = 1. From (1), we have that:
  
posPlayer = posCamera + 960

Substituting this in (2), we have:

posPlayer_cam = posCamera + 960 - posCamera * 1 = 960

As we can see, the position of the player in camera space does not depend on the position of the camera, because it is always the same: the center of the screen. Any other object that belongs to a layer with scale = 1 will meet (from (2)):

posObject_cam = posObject - posCamera (2a)

This means that the position in camera space of the object will change at the same rate that the camera moves. Consider for example a stationary object at position 50. If the camera initially is at position 0, then the object position in camera space will be 50. Now if the camera moves 10 units to the right, the position of the object in camera space would be 50 - 10 = 40. Moving the camera forward u units is equivalent to moving the object backward u units. In the case of the player, it is not a stationary object, but it moves at the same rate as the camera moves, and in particular, the position of the player in camera space is 960 because the difference between the position of the player and the position of the camera is always 960.

Consider now the case of stationary objects that belong to a layer with scale = 0. Then, we would have:

posObject_cam = posObject - posCamera * 0 = posObject (3)

This means that positions of objects that belong to these layers are not affected by changes in the position of the camera: their positions in camera space correspond always to their positions in world space. Or said another way: if a stationary object has the position 10 in camera space, it will keep that same position forever, being the net effect that the object moves at the same rate and same direction as the camera. 

Think now of stationary objects that belong to a layer with scale = 2. Then, we have:

posObject_cam = posObject - posCamera * 2 (4)

Let us suppose that the camera moves 10 units to the right. This means that the object in camera space moves -20 units (or 20 units to the left). In general, moving u units the camera to the right is equivalent to moving the object 2u units to the left.

Once we know how the position of any object in camera space is calculated, let's see something different. Now, we are going to calculate where the objects are actually drawn on the screen. The diagram in Figure 2 helps explaining this:


Figure 2. Object positions in different spaces

Let posObject_draw be the position where an object is drawn on the screen. Clearly, from the diagram above, we have the following:

posObject_draw = posCamera + posObject_cam 

, where posCamera in the X dimension is x.

And if we substitute posObject_cam from (2), we get:

posObject_draw = posCamera + posObject - posCamera * n (5)

, where n is the scale to which the object belongs.

Let us instantiate (5) for the player:

posPlayer_draw = posCamera + posPlayer - posCamera * 1 = posPlayer

This means that the player is drawn exactly in its world coordinates. If the player advances u units to the right, the player will be drawn these same u units to the right. In the case of stationary objects, this means that they will be drawn always in the same position. Therefore, if the camera advances u units to the right, in order for the object to be in the same position, the object will be drawn u units to the left. Note that this is consistent with the analysis performed on (2a).

If we instantiate (5) for a stationary object that belongs to a layer with scale = 0, then we have:

posObject_draw = posCamera + posObject - posCamera * 0 = posCamera + posObject

This means that the position where this object is drawn moves at the same rate as the camera and in the same direction. The net effect is that the position of the object is not affected by the position of the camera and the object seems to be still. This is consistent with what (3) expressed.

If we now instantiate an object that belongs to a layer with scale = 2, we would have:

posObject_draw = posCamera + posObject - posCamera * 2 = posObject - posCamera

This is expressing that moving u units the camera to the right causes the object to be moved u units to the left, being the net effect that the object is moved 2u units to the left, as the analysis in (4) concluded.

Once we understand how everything works, it is time to detect collisions among objects in different layers. The key idea here is that we must compare positions of the same type. Let us suppose that we want to check whether two objects A and B, belonging to layers with scales n and m, respectively, are colliding. We have two options:
  1. Compare positions in camera space.
  2. Compare positions where the objects are actually drawn.
In option 1, the steps would be as follows:

posA_cam = posA - posCamera * n
posB_cam = posB - posCamera * m
if (colliding(posA_cam, posB_cam)) then ...

In option 2, we would perform the following steps:

posA_draw = posA + posCamera * (1 - n)
posB_draw = posB + posCamera * (1 - m)
if (colliding(posA_draw, posB_draw)) then ...

Note that given that world coordinates of the objects do not account for the displacement that occurs each frame because of the parallax effect, we cannot use these world coordinates directly for the collision detection. For example, consider two stationary objects A and B that are initially placed in the same (world) position: 500, but A belongs to a layer with scale = 1 whereas B belongs to a layer with scale = 2. If we used the world positions to detect collisions between these objects and any other object C that moves toward them, we would detect that C collides with them at the same time, but this is a mistake. Actually, C will reach B in half the time that it will reach A, because B moves twice as faster than A in relation to the camera. In summary: we are using the world position only as an intermediate value to compute the actual position where the objects are drawn (or their position in camera space).

And this is all for now. In the last installment of the series, we will discuss how we can represent the same object in different camera spaces. This is useful for multiplayer games, where each player has a different view of the world.

Hope you found this interesting and see you soon!

viernes, 27 de noviembre de 2015

Parallax Scrolling in Love2d. Part 1: Data Structures and Programming

Hi all!

This is the first post of a series in which I'll explain how we can implement parallax scrolling in Love2d. The code that I'll share is similar to the one I'm using for Breaking Fast, our next game to come.

The implementation builds on the tutorial that you can find here. Actually, I advise you to take a look at this tutorial first, because it covers the fundamental principles behind the implementation. Then, we will perform some extensions, so come back here. 

The implementation starts with the idea of creating a data structure that represents the camera. So now, we will focus on a camera.lua file that implements the camera data and functionalities.

camera.lua
 local camera = {}  
 camera.__index = camera  
 return camera  

What data must a camera have? Well, the first and most important one is the (x,y) coordinates that represent its position. It could also hold data regarding scaling in both directions, and its orientation. If we want the camera to support parallax (and we certainly do), it must also contain a list of layers, where each layer will contain its own objects to be drawn. With this in mind, we can create the camera constructor, as follows:

camera.lua

 local function construct()  
  local self = setmetatable({x = 0, y = 0, scaleX = 1, scaleY = 1, rotation = 0, layers={}}, camera)  
  return self  
 end  
   
 setmetatable(camera, {__call = construct})  

As you can see, we are creating a camera with an empty list of layers. Also, with the setmetatable function, we are instructing that if, from some client code, we use the table camera as a function, the method to which __call points is to be invoked. This means that if we want to create two different cameras from client code, we only need to do the following:

some client code.lua
 local Camera = require "camera"  
 myCamera1 = Camera()  
 myCamera2 = Camera()  

Love2d uses three functions to transform the current coordinate system:

love.graphics.rotate( rotation )
love.graphics.scale( x_scale, y_scale )
love.graphics.translate ( dx, dy )

Let's focus on the last one. According to the documentation: When this function is called with two numbers, dx, and dy, all the following drawing operations take effect as if their x and y coordinates were x+dx and y+dy.

So, assume we have a square in the coordinate (1, 0), as shown on the left part of Figure 1. After performing love.graphics.translate( -1, 0 ), we would have the square in the coordinate (1 + (-1), 0 + 0) = (0, 0), because even when we are actually moving the coordinate system, this is equivalent to moving the square in the opposite direction, as depicted on the right side of Figure 1.


 Figure 1

This is the fundamental mechanism that we can use in order to implement traditional scrolling, where the dx and dy values correspond to the position of the camera with negative sign. The same applies for the scaling and the rotation. We can encapsulate all this in two functions as follows:

camera.lua

 function camera:set()  
  love.graphics.push()  
  love.graphics.rotate(-self.rotation)  
  love.graphics.scale(1 / self.scaleX, 1 / self.scaleY)  
  love.graphics.translate(-self.x, -self.y)  
 end  
   
 function camera:unset()  
  love.graphics.pop()  
 end  

The push() function saves the current transformation on top of a stack, whereas pop() sets the current transformation to the one on top of the stack. The strategy to draw is therefore something similar to this:

camera.lua

 function camera:draw()  
   self:set()
   -- draw stuff  
   self:unset()  
 end  

Obviously, prior to calling self:set(), we could modify properties of the camera, in such a way that each draw call behaves different in terms of scaling, rotation or movement/position.

Let's focus now on the parallax effect. For such effect, we have to turn our attention to the layers. What data must a layer have? First, the layer must be able to draw itself, so it must have a reference to a function that will draw the objects in such a layer. Given that we want that each layer can move at different rates (in order to achieve the parallax effect), each layer must hold a rate or scale value. Finally, it would be interesting that we could decide a relative order among layers, in such a way that we can specify the order in which different layers are drawn. The function that builds a new layer is shown next:

camera.lua

 function camera:newLayer(order, scale, func)  
  local newLayer = {draw = func, scale = scale, order = order}  
  table.insert(self.layers, newLayer)  
  table.sort(self.layers, function(a,b) return a.order < b.order end)  
  return newLayer  
 end  

Note that after inserting the new layer into the table of layers, we order them according to the order value. Now, we can complete the draw function as follows:

camera.lua 

 function camera:draw()  
  local bx, by = self.x, self.y  
  for _, v in ipairs(self.layers) do  
   self.x = bx * v.scale  
   self.y = by * v.scale  
   self:set()  
   v.draw()  
   self:unset()  
  end  
  self.x, self.y = bx, by  
 end  

The draw function iterates over all the layers defined for the camera, it then applies a layer scale to the current position of the camera, which makes each layer to be drawn in a possibly different position, depending on the value of this scale. Finally, the draw function of the layer is called. In order to preserve the original position of the camera, we use the temporary variables bx and by.

Note that given that we are using the product of the layer scale and the current position, stationary objects in layers with scale = 0 will have no movement. Therefore, any static background of the game will belong to a layer with such a scale. A layer with scale = 1 will contain objects that move at the same rate as the camera (but in the opposite direction). If we want to track a character controlled by the player, a good strategy is including it in a layer with scale = 1 and moving it at the same rate as the camera, as we will see later. Anything that moves at a faster or slower rates than the camera will belong to layers with scales greater or lower than 1, respectively. The following post in the series will provide further insight on these statements, but for now, let's see now how some client code can use all of this. Let's assume that we have four layers, that we want to draw in the following order:
  1. Static background layer.
  2. Background layer that moves slowly.
  3. Player layer.
  4. Foreground layer with objects moving much faster than the rate at which the player moves.
The order is important because elements in the layer n+1 will occlude elements in the layer j, 1<=j<n+1 (basically, in all previous layers).

some client code.lua

 myCamera1 = Camera()  
 myCamera1:newLayer(-10, 0, function()    
                love.graphics.setColor(255, 255, 255)   
                love.graphics.draw(staticBackground)  
              end)  
 myCamera1:newLayer(-5, 0.3, function()   
                 love.graphics.setColor(255, 255, 255)  
                 love.graphics.draw(slowBackground)   
                end)  
 myCamera1:newLayer(0, 1.0, function()   
                 love.graphics.setColor(255, 255, 255)  
                 love.graphics.draw(player)   
                end)  
 myCamera1:newLayer(10, 1.5, function()   
                 love.graphics.setColor(255, 255, 255)  
                 love.graphics.draw(foreground)   
                end)  

Of course this is not the end of the story. Client code is responsible for updating and drawing the contents of the camera. Fortunately, this is easy, as it is shown next:

some client code.lua

 function love.draw()  
   myCamera1:draw()  
 end  
   
 function love.update(dt)  
  myCamera1:update(dt, player1.posX)  
 end  

love.draw and love.update(dt) are two framework callbacks provided by Love2d and which developers can override in order to customize their behaviours. The former will essentially call the draw function that we implemented in the camera.lua module, whereas the update function will in turn call an update function in the camera module, which we haven't discussed yet. In this example, we assume that we want the camera to follow the player when it is in the center of the screen. Achieving this requires adding the following lines to the camera.lua file:

camera.lua

 local intendedWidth = 1920  
   
 function camera:setPosition(x, y)  
  self.x = x or self.x  
  self.y = y or self.y  
 end  
   
 function camera:update(dt, posX)  
  if posX > intendedWidth / 2 then  
   self:setPosition(posX - intendedWidth / 2)  
  else  
   self:setPosition(0)  
  end  
 end  

Assuming a resolution (intendedWidth) of 1920 pixels in the horizontal dimension, we want to reposition the camera as soon as the player moves past half of this resolution (center of the screen). From that moment onwards, the camera is repositioned each frame to center the player on the screen. As the player belongs to a layer with scale = 1, the player will remain always in the same position in relation to the camera. This again will be further discussed and mathematically proved in the following post of the series.

And this is for now! I hope you found this tutorial useful. In the next installments of the tutorial, I intend to discuss and provide further insight on three aspects: reasoning about layers scales and their relation with the positions of objects, detecting collisions among objects in different layers and representing objects in different cameras, for example, for local multiplayer games.

See you!

lunes, 30 de septiembre de 2013

Fundamentals of Scrolling (part II)

Hi all!

This is the follow-up of a previous post about scrolling.

In this case, we are going to talk about parallax scrolling. This technique provides greater realism as it simulates some feeling of depth. To understand it, think when you're in a car and you look out the window. Obviously the closest objects (e.g. the stripes of the the road) move far much faster than the objects in the horizon (e.g. the mountains or clouds). 

The way you can implement this is pretty straightforward. Look at Figure 1. 


Figure 1.- Parallax Scrolling: layers and objects. The closest layer (the fastest one) is the one of the palms and floor. The furthest layer (i.e. the slowest one) is the one with the moon. 

The idea is simply to create several layers or views and make the furthest layers to move slower than the closest layers. Therefore, in pseudo-code, you would have something like the following:

Level::createLevel() {
    Mountain* m = new Mountain("mountain.png");
    Cloud* farCloud = new Cloud("cloud.png");
    Cloud* closeCloud = new Cloud("cloud.png");
    Platform* p = new Platform("platform.png");

    //velocityFarLayer < velocityMidLayer < velocityCloseLayer
    Layer* farLayer = new Layer(velocityFarLayer);
    farLayer -> addObject(farCloud);
    farLayer -> addObject(m);

    Layer* midLayer = new Layer(velocityMidLayer);
    midLayer -> addObject(closeCloud);

    Layer* closeLayer = new Layer(velocityCloseLayer);
    closeLayer -> addObject(p);
}

Level::update() {
   for (Layer *l: layersInLevel) {
       l -> update();
   }
}

Layer::update() {
   for (Object *o: objectsInLayer) {
       //Each object knows how to update itself in response to physics or input
       o -> update();
   }
   //We move the layer too
   this -> move (layerVelocity);
}

Layer::move(Vector &velocity) {
  for (Object *o: objectsInLayer) {
     o -> move (velocity);
  }
}

I think that the code is more or less self-explanatory. A level is composed of layers, each of which has a different velocity. Each layer, in turn, is composed of objects. When you design a level, you first specify the number of layers and each layer velocity. Then, you associate each object to a level. When the level is updated, each layer is updated, which means that each object in the layer will be updated and moved according to the layer velocity.

Well, that's all for now. Hope you find this information useful. Don't hesitate to leave comments or suggestions or questions.

See you!

viernes, 7 de junio de 2013

Fundamentals of Scrolling

Hi all!

After the last two posts on integration, I think that we can take a breath before finishing the hard physics stuff. So in this post I will talk about something that it is not that difficult to implement and is basic for any platform game (and many other games too): scrolling.

Most games, from the very beginning, have had some sort of scrolling. Scrolling consists of moving the scenario in such a way that your character is always visible, and usually, by the center of the screen. If this movement is on the horizontal axis, it is called 'horizontal scrolling', and if it takes place on the vertical axis, we would have 'vertical scrolling'. Most games use both of them.

Technically, however, it is more accurate to say that what we really move is the view along the level. That is, the idea is that we create a very big level (let's say 3000 x 3000 pixels) and then we set a view to our screen resolution, such as 1280 x 800 pixels. Then, we basically move this view (which you can think of as a rectangle) along the level in two dimensions, as depicted in Figure 1.

Figure 1.- View vs Screen Images. For scrolling, you move the view rectangle along the level rectangle

The way to achieve this in your game will obviously depend on the library or framework you are using. As I told you in this post, I will be using the SFML library, so the code explanations will be shown in C++ and using this library. However, the concept itself is the most important thing you should understand, and not any concrete technical implementation.

void LevelScreen::update(sf::Time elapsedTime) {

//We update all the objects in the screen
std::vector<Entity *>::iterator iter;
for(iter = gameObjects.begin(); iter != gameObjects.end(); iter++) {
        Entity *e = *iter;
        e->update(elapsedTime);


//Now we update the view
//Horizontal scrolling
if (player->getPosition().getX() > SCREEN_WIDTH / 2) {
         view.move(player->getVelocity().getX() * elapsedTime.asSeconds(), 0);
}

//Vertical scrolling
if (player->getPosition().getY > SCREEN_HEIGHT / 2) {
        view.move(0, player->getVelocity().getY() * elapsedTime.asSeconds());

}

Here, we basically update all the objects in the screen in the update method of the level (I'll give more details on the overall engine architecture in a following post). Then, we update the velocity of the view using the velocity of the player (the player variable holds a pointer to the player object). The update of the view will take place only if the player is located beyond the half of the screen in any dimension. Finally, in the draw method, we should set the view and draw the objects onto this view, as shown next:

void LevelScreen::draw(sf::RenderWindow &window) {

//Set the view to be used on the window (the window is where we draw the objects)
window.setView(view); 


//Draw the objects
std::vector<Entity *>::iterator iter;
for(iter = gameObjects.begin(); iter != gameObjects.end(); iter++) {
     Entity *e = *iter;
     e->draw(window);
}
}

This is the basic background you need in order to implement more advanced scrolling features, such as parallax scrolling. This technique will provide a higher feeling of realism with relatively little effort. I will explain how to implement it in the following post.

EDIT: you can go to the next post on parallax scrolling directly here

See you!