Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Background image placement at a low ratio #741

Closed
Giwayume opened this issue Sep 7, 2015 · 11 comments
Closed

Background image placement at a low ratio #741

Giwayume opened this issue Sep 7, 2015 · 11 comments
Labels
Milestone

Comments

@Giwayume
Copy link
Contributor

Giwayume commented Sep 7, 2015

This is a continuation of #737.

Upon testing with a background image of ratio 0.1 (mostly sticks to the view but slightly moves), it seems the lower I place the background in Tiled (high y values), the higher it ends up in the game.

So if I place the background at the top of the screen in tiled it will be at the bottom of the screen in the game and vice a versa.

@parasyte
Copy link
Collaborator

parasyte commented Sep 7, 2015

Yep, I saw the same thing. It's caused by the way the viewport extents are scaled. I'll see what I can do to make it better.

@parasyte
Copy link
Collaborator

parasyte commented Sep 8, 2015

To solve this one, I'm going to define some test cases, and derive the expected equation that way. There is likely a better approach, but this will do for now.

The Goal

I have three rectangles:

  1. A viewport boundary
  2. A viewport "window"
  3. An image layer

The viewport window size is <= the viewport boundary, and is allowed to move freely inside the viewport boundary. The image layer is allowed to be any size, and is also allowed to be positioned arbitrarily, including outside of the viewport bounds.

The goal is to adjust the position of the image layer based on three inputs:

  1. The viewport window position
  2. A scrolling ratio (unit vector)
  3. An anchor point (unit vector)

The Problem

Having three variables control the image layer position adjustment is a bit painful. Imagining the image layer's final position as an offset of its initial position simplifies things (removes the initial position as another variable).

The viewport window position and scrolling ratio are very easy to deal with; just multiply them together. The anchor point is where things get tricky, because the image layer is required to scroll backwards when anchored to the bottom/right corner of the viewport bounds... as we will see.

The Anchor Point

The Platformer 2 example was created in part to demonstrate an image layer being anchored to the bottom of the viewport bounds; the bottom of the dark blue mountain background needs to be aligned with the bottom of the viewport bounds when the viewport window is scrolled to the bottom. And thus when the viewport scrolls up, the background scrolls up as well. This presents the first challenge:

We need a way to define the image layer position with pixel perfect precision when the viewport window is scrolled to the bottom.

This is counter to what other maps will need; imagine a vertical level where the player starts at the top and goes down. In this case, a parallax background most certainly needs to be anchored to the top:

We also need a way to define the image layer position with pixel perfect precision when the viewport window is scrolled to the top.

Our unit vector anchor point can give us this flexibility. But how does the math work?

The Test Cases

Below are two data sets (along the Y-axis), followed by description of the state (image layer final position) expected for these configurations when the viewport is at the top and the bottom. These test cases show the complexity that is added by the anchor point input.

layer position   = 256
layer height     = 384
layer ratio      = 0.5
layer anchor     = 0
viewport extents = 450
viewport height  = 600
  • When viewport position == 0, layer position should be 256 (i.e same as layer initial position)
  • When viewport position == 450, layer position should be 256 + 450 * 0.5 = 481
layer position   = 538
layer height     = 512
layer ratio      = 0.25
layer anchor     = 1
viewport extents = 450
viewport height  = 600
  • When viewport position == 0, layer position should be 538 - 450 * 0.25 = 425.5
  • When viewport position == 450, layer position should be 538 (i.e same as layer initial position)

For all final states, subtract the viewport position to translate it to screen coordinates. (Historically, me.ImageLayer has been implemented is screen coordinates. There is no technical reason remaining for this.)

In other words, the initial position is only used when the ratio between (the viewport window's position and the viewport bounds) matches the anchor point.

The Process

Given the above goals and test cases, let's define an equation that matches our needs:

  1. Take the initial layer position
    • offset.y
  2. Define a subtractive expression for anchorPoint == 1
    • offset.y - (subtractive * ay)
  3. Define an additive expression for anchorPoint == 0
    • offset.y - (subtractive * ay) + (additive * (1 - ay))
  4. Apply the additive expression only when the viewport is scrolled to the bottom
    • offset.y - (subtractive * ay) + (vpos.y * additive * (1 - ay))
  5. Apply the subtractive expression only when the viewport is scrolled to the top
    • offset.y - ((bh - viewport.height - vpos.y) * subtractive * ay) + (vpos.y * additive * (1 - ay))
  6. Subtract viewport position (translate to screen coordinates)
    • offset.y - ((bh - viewport.height - vpos.y) * subtractive * ay) + (vpos.y * additive * (1 - ay)) - vpos.y

The additive and subtractive expressions are now just a matter of scaling the viewport position by the inverse of the scrolling ratio. We arrive at the final equation:

this.offset.y - (bh - viewport.height - vpos.y) * (1 - ry) * ay + vpos.y * (1 - ry) * (1 - ay) - vpos.y

Simplified by Wolfram Alpha:

ay * (ry - 1) * (bh - viewport.height) + this.offset.y - ry * vpos.y

End

All that's left to do is remove the "position is the anchor point" hack from #737 (because they are distinct inputs). With that, here's a patch:

diff --git a/src/level/TMXLayer.js b/src/level/TMXLayer.js
index 5bc11ec..b5662a3 100644
--- a/src/level/TMXLayer.js
+++ b/src/level/TMXLayer.js
@@ -99,6 +99,8 @@
             // displaying order
             this.pos.z = settings.z || 0;

+            this.offset = new me.Vector2d(x, y);
+
             /**
              * Define the image scrolling ratio<br>
              * Scrolling speed is defined by multiplying the viewport delta position (e.g. followed entity) by the specified ratio.
@@ -123,9 +125,6 @@
             }

             if (typeof(settings.anchorPoint) === "undefined") {
-                var aw = me.game.viewport.bounds.width - this.imagewidth,
-                    ah = me.game.viewport.bounds.height - this.imageheight;
-
                 /**
                  * Define how the image is anchored to the viewport bounds<br>
                  * By default, its upper-left corner is anchored to the viewport bounds upper left corner.<br>
@@ -142,10 +141,7 @@
                  * @default <0.0,0.0>
                  * @name me.ImageLayer#anchorPoint
                  */
-                this.anchorPoint.set(
-                    aw ? x / aw : 0,
-                    ah ? y / ah : 0
-                );
+                this.anchorPoint.set(0, 0);
             }
             else {
                 if (typeof(settings.anchorPoint) === "number") {
@@ -277,8 +271,9 @@
                  * direction when anchored to the bottom or right sides of the
                  * viewport boundary.
                  */
-                x = ~~(-ax * (1 - rx) * (bw - viewport.width) + ax * (bw - width) - vpos.x * rx),
-                y = ~~(-ay * (1 - ry) * (bh - viewport.height) + ay * (bh - height) - vpos.y * ry);
+                x = ~~(ax * (rx - 1) * (bw - viewport.width) + this.offset.x - rx * vpos.x),
+                y = ~~(ay * (ry - 1) * (bh - viewport.height) + this.offset.y - ry * vpos.y);


             // Repeat horizontally; start drawing from left boundary

@Giwayume Please let me know if this resolves the issue.

@parasyte parasyte added the Bug label Sep 8, 2015
@parasyte parasyte added this to the 3.0.0 milestone Sep 8, 2015
@obiot
Copy link
Member

obiot commented Sep 8, 2015

is the wolfram calculator/optimizer (or whatever it should be called) really reliable ?

@parasyte
Copy link
Collaborator

parasyte commented Sep 8, 2015

@Giwayume
Copy link
Contributor Author

Giwayume commented Sep 8, 2015

The ratios have flipped back again to 1 being attached to the viewport. Is that intentional?

Also you can't get rid of the width/height variables because of the background repeating code.

This works, but in practice it's a little odd because all the backgrounds need to be placed in relation to <0,0> in the room. This is fine if you're imagining the viewport being positioned there when constructing the level. Hope the Tiled guys work on that camera feature!

@parasyte
Copy link
Collaborator

parasyte commented Sep 8, 2015

I've made the correction to the patch for the width/height variables.

Not sure about the ratio being flipped. If you're talking about the background moving at the same speed as the viewport when ratio == 1, that is intentional, yes. The ratio defines how fast the background moves in relation to the viewport. ratio == 1 is a 1:1 ratio; they both move at the same speed. ratio == 0.5 is a 1:2 ratio; the viewport moves 2x faster than the background, and ratio == 0.1 is a 1:10 ratio, with the viewport moving 10x faster. This all works as expected, correct?

Background positioning with parallax is not easy to imagine. I agree having this feature in the editor is the best way to make a level designer's life easier. Until then, you have to make a mental map of the viewport "window" while designing, and configure the anchorPoint accordingly. This is easiest when the viewport is at the extremes, because you just use anchorPoint = 0 | 1 depending on which side of the map the viewport is on when the background layer needs to be aligned to its designated location.

It's much more difficult when the viewport is somewhere in the center of the map. But if you have an idea of where the viewport is going to be, the exact anchorPoint is easy to calculate. (It's the same math as before.) You could even simulate it a bit if you want a quick hack; Create a semitransparent image to represent your viewport (it should be roughly the same size as your viewport), and insert it into the map as an image layer. You can position the layer where you expect the viewport to be located at runtime, and fill in the variables in the following equation:

anchorPoint.x = (map.width - viewport.width) / viewport.pos.x;
anchorPoint.y = (map.height - viewport.height) / viewport.pos.y;

Hide the viewport image when you're done (if you want to reuse it later) and set the background layer's anchorPoint according to the equation result. This will position the background as shown in Tiled when the melonJS viewport is in the same location as your simulated viewport.

Does that help at all?

@Giwayume
Copy link
Contributor Author

Giwayume commented Sep 8, 2015

I only mentioned them being flipped because you flipped them in the last patch, intentionally or not. 0 was acting as 1 and 1 as 0.

I get how it's supposed to work I'm just thinking it may be a bit confusing to new users. I may go make an attempt at that camera patch on the Tiled side...

@parasyte
Copy link
Collaborator

parasyte commented Sep 8, 2015

I didn't notice the behavior of anchorPoint changing. The patch in #737 just sets the anchorPoint value (does not change its meaning), and this patch attempts to reinterpret the anchorPoint while retaining its purpose for positioning the layer relative to the viewport's initial position. The main difference is now the layer's initial position is required (opposed to ignored).

There is more configuration involved (it was attempting to be purely automated previously). So if you miss that extra configuration step of positioning the layer when you set the anchorPoint, then you might perceive the scrolling as doing something unexpected; Basically it will be grossly offset. That's what I meant when I said adding the position does not do what users expect; well, I've just flipped the rules! Now NOT setting the position does not do what users expect.

Really, we're just trying to find the right compromise. It's still not going to be super easy to reason about, but that's what the editor is for.

@Giwayume
Copy link
Contributor Author

Giwayume commented Sep 8, 2015

No, the ratio was flipped, not the anchorPoint. Sorry if I'm being confusing. There was a 1-rx and 1-ry.

@parasyte
Copy link
Collaborator

parasyte commented Sep 8, 2015

Oh, my mistake... I tested the ratio quite heavily. Like I commented, it works as intended; ratio == 1 makes the viewport and background scroll at the same speed. And down toward 0 makes the background scroll slower (until at 0 is does not move at all). Is that what you're referring to? Or do you mean the use of (1 - ry) in the equation?

@parasyte
Copy link
Collaborator

Well dammit. You were right all along. ratio == 1 is static with this patch. I don't know why I thought otherwise. I'll make sure it is fixed before this patch is committed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants