How to render the game world off-centre and in a subset of the screen? (SOLVED)

I’m wondering how to set up rendering to achieve a game world that is off-centre, for a game that is mostly UI (see image below). The specific resolution numbers aren’t important, the main idea is that I want to have the game world off-centre.

The ideal solution would be that in terms of my programming of the game world, it’s as if the whole project is 300x300 starting at 0x0 (not whatever the offset position is in my image below). I don’t want to have to amend any game logic to account for this, if that makes sense. With a tweak of the render script, the position of the game world should be able to be changed, without affecting the game logic.

I tend to use Rendercam but would be happy to hear a solution that uses Orthographic as well.

Bonus: Would the considerations change for a pixel perfect retro style? For example, handling different resolutions.

Thanks!

2 Likes

One day messing with the render script I did something similar to what you want, but at this point I can’t quite remember nor explain how I did it XD. I’m gonna stick around and see how the pros would do it.

1 Like

I wrestled Fates of Ort into doing what I needed it to do, but it was a bad solution that caused me no end of grief throughout the development process. This isn’t the same scenario exactly but nevertheless, I would love to hear a good way to achieve this so I can do this project right. :slight_smile:

1 Like

Something like this?

2 Likes

Yes, I think so. I’ve read that post but not got deeply into it. Spent some time tonight looking at lowrezjam template, the render manual, and the documentation of both rendercam and orthographic. My head is spinning but I’m none the wiser :slight_smile:

I think what’s achieved in Hook, Line and Thinker is basically exactly what I’m asking about, though the UI solution is concerning as I’d normally like to use GUI objects for UI. In addition, I see there is some camera panning (e.g. on loading a new level), but I’m not sure the camera functionality described there is as complex as what I’m asking about (i.e. integration with rendercam or orthographic).

I’m happy to spend more time trying to figure this out, but if anyone has any other pearls of wisdom at this stage then I’m all ears!

If you look at the render script there’s three things that is set up before drawing anything:

  • The view using render.set_view(view_matrix)
  • The projection using render.set_projection(proj_matrix)
  • The viewport using render.set_viewport(x,y,w,h)

The view and projection is continuously updated by the camera you use (or set to fixed values in the render script init() function).

The viewport on the other hand is usually fixed and set to span the entire size of the screen. You should be able to change this to render in the area where you have the “game world” in your mockup above and call render.draw() to draw your sprites etc like normal.

You then need to reset the viewport to render on the entire screen before calling render.draw() on your gui.

4 Likes

@Alex_8BitSkull in my render_script I have the line:

render.set_viewport(left_corner, bottom_corner, viewport_width, viewport_height)

that I use in case the aspect ratio is so extremely tall I need to implement a letter box. If you need any help in this please ask.

2 Likes

Sorry it took so long to get back to you guys - been busy times…

So like @britzl and @roccosaienz are saying, it’s quite simple really. To begin with, anyway. I’ve begun working on a little tester project using Orthographic and have had some success.

Here’s what it looks like with a normal viewport (note: line & mouse cursor are explained later):

And here I’ve reduced the size of the viewport to a subset of the screen like I wanted:

As suggested above, all I’ve done is change the standard viewport:

render.set_viewport(0, 0, render.get_window_width(), render.get_window_height())

To something that has an offset and is half the width/height of the project:

render.set_viewport(100, 100, 740, 460)

After rendering the game world I set the projection back to normal for the GUI. All good so far.

The next step is to try and handle mouse input in this modified viewport. I draw a line from the middle of the “game” world to the cursor, which works fine in the normal project but not in the modified viewport.

(NOTE: My screen capture software must be showing my mouse in the wrong position in the video. The line is in fact drawn to cursor in the “normal” project, but is not for the version with the amended viewport.)

I think I need to be using screen_to_world (because it produces the right result in the normal project). The difference between screen/window never ceases to confuse me (same for action.x and action.screen_x).

Drilling into the orthographic module I see no reference to a viewport in the screen_to_world function:

--- Convert screen coordinates to world coordinates based
-- on a specific camera's view and projection
-- Screen coordinates are the scaled coordinates provided by action.x and action.y
-- in on_input()
-- @param camera_id
-- @param screen Screen coordinates as a vector3
-- @return World coordinates
-- http://webglfactory.blogspot.se/2011/05/how-to-convert-world-to-screen.html
function M.screen_to_world(camera_id, screen)
	assert(camera_id, "You must provide a camera id")
	assert(screen, "You must provide screen coordinates to convert")
	local view = cameras[camera_id].view or MATRIX4
	local projection = cameras[camera_id].projection or MATRIX4
	return M.unproject(view, projection, vmath.vector3(screen))
end

Drilling deeper into M.unproject similarly doesn’t reference the viewport:

--- Translate screen coordinates to world coordinates given a
-- view and projection matrix 
-- @param view View matrix
-- @param projection Projection matrix
-- @param screen Screen coordinates as a vector3
-- @return The mutated screen coordinates (ie the same v3 object)
-- translated to world coordinates
function M.unproject(view, projection, screen)
	assert(view, "You must provide a view")
	assert(projection, "You must provide a projection")
	assert(screen, "You must provide screen coordinates to translate")
	local inv = vmath.inv(projection * view)
	screen.x, screen.y, screen.z = unproject_xyz(inv, screen.x, screen.y, screen.z)
	return screen
end

Finally unproject_xyz:

local function unproject_xyz(inverse_view_projection, x, y, z)
	x = (2 * x / DISPLAY_WIDTH) - 1
	y = (2 * y / DISPLAY_HEIGHT) - 1
	z = (2 * z)
	local inv = inverse_view_projection
	local x1 = x * inv.m00 + y * inv.m01 + z * inv.m02 + inv.m03
	local y1 = x * inv.m10 + y * inv.m11 + z * inv.m12 + inv.m13
	local z1 = x * inv.m20 + y * inv.m21 + z * inv.m22 + inv.m23
	return x1, y1, z1
end

Here I’m thinking we’ve found something since it begins referencing display width/height. I’m guessing though since what I’m doing is non-standard, there’s no reason to handle viewport offsets and anything other than the display width/height (since the area covered by the camera is always going to match the display). Is that right?

So, what do I need to modify in order to enable Orthographic to handle a custom viewport a la @roccosaienz? That is:

render.set_viewport(left_corner, bottom_corner, viewport_width, viewport_height)

Thanks for any help!

6 Likes

First of all I note from the video with the reduced viewport that you are using a non uniform scaling: the circles are no more circles but ellipses… Is this intended? I would say no.

However, in general, since you are not rotating the image in the smaller viewport, it suffices to use a transformation of the type

logic_x = h * raw_x + a
logic_y = k * raw_y + b

with the right h, k, a, b, where raw_x, raw_y are the input coordinate you get from the touch action and logic_x, logic_y will be the coordinate to use in the game world (shown in the reduced viewport).

So the point is how to find h, k, a and b. Since I think you want to use uniform scaling, then h = k.

Now, in general, the scaling factor and a and b depends on your coordinate systems. For example I would prefer using (0,0) at the center of the screens; but you may as well put (0,0) in the left-bottom corner… In few words: it is hard to tell how to compute a and b without knowing the coordinate system.

There is however something that can be said: (a,b) correspond to the logic coordinates of (raw_x, raw_y) = (0,0); so (a,b) is just the origin of the screen coordinates expressed in the logic coordinates.

I know that all this is not that clear. If you provide me your coordinate systems I am more than happy to help with formulas for h, k, a and b. :slight_smile:

Ciao, Rocco.

5 Likes

You’re right! I was so tired last night that I didn’t even notice the non-uniform scaling!

I had the Orthographic projection set to DEFAULT. Changing it to FIXED_AUTO (“A fixed aspect ratio projection that automatically zooms in/out to fit the original viewport contents regardless of window size.”) has made it work well for the normal viewport, but the issue persists in the smaller viewport.

What do I want? I want uniform scaling, that’s for sure. I want the aspect ratio to remain the same at all times, and the area of game world rendered to be the same also. So basically something very similar to Hook, Line and Thinker:

Part of the problem was that I was misunderstanding how to set the viewport. I thought you had to add the bottom/left offset to top/right as well. For my test project which is 1280x760, I effectively did this:

render.set_viewport(100, 100, 1280*0.5+100, 720*0.5+100)
(i.e. defining the position of the four courners of a rectangle)

But it seems like this is right (i.e. defining the left/bottom origin and then the width/height):
render.set_viewport(100, 100, 1280*0.5, 720*0.5)

This makes the small viewport look correct when the game window is 1280x720, but not when the game window is resized in any way.

The issue of finding the correct projection (?) remains because I would like what’s in the viewport to be uniform and for the same game world area to be displayed, which doesn’t seem to be possible with the vanilla projections in Orthographic. At least not with the way I’ve modified I’ve modified the viewport (just hardcoding it in the render_script for now).

With regards to input. Your formulas make sense, but I haven’t been able to get them to work. My first instinct was that h/k should be 0.5 (for halfed width/height) and that a/b should be 100 (for the bottom/left offset), but that doesn’t seem to be doing the trick. I’ve tried a few naive variations (change 0.5 to 2 and do +/- 100) but no luck there either.

In case it helps, here is the test project I am working on.

orthographic_viewport.zip (647.2 KB)

The line is drawn in world.script, line 30. The mouse cursor position is calculated in the on_input function of the same script. The viewport is hardcoded in line 31 of the render_script.

3 Likes

I will download the project and try to make the input works later today.

But just a side note. Are you sure you need to set the viewport? I think it is possible to achieve the same effect just drawing the “console body” on top of the game world, without using an ad-hoc rendering with smaller viewport. Of course this results in an increased pixel filling since one draws twice the region of the screen with the “console body” on top.

1 Like

Thank you. I am not sure I need to set the viewport, no!

What I’m looking for in an ideal world is a solution that allows me to render the game world in a subset of the window in a way that:
a) meets the display criteria I mentioned previously (uniform scaling and same area of game world displayed, no matter the user resolution/window size)
b) doesn’t make me have to overly worry about game logic and modifying input constantly (your formula is a reasonable modification if I can just “set it and forget it”). I want to be able to ignore (within reason, of course) that I’ve messed around with the rendering.

Does that make sense?

Yes, it does! Indeed I use a similar solution for box-letter in case the aspect ratio is too tall. I have a function in a lua module that just translate the input from raw action.x/y to game x/y. And I forget this translation as you say!

So, I will come back with the right formula as soon as I get it! :slight_smile:

2 Likes

About the wrong scales.
I think that rendercam is adjusting its projection taking into account the aspect ratio of the window. But, in the small viewport the aspect ratio is fixed, so when you resize the window this results in a wrong x/y scale in the small viewport. A possible way of solving this is to use two distinct cameras as in this example

one camera for the whole window with the gui, another camera for the game world in the small viewport.

However I have never used rendercam so I cannot help that much… and, but this is only my taste, I would go for an ad-hoc solution without rendercam.

1 Like

That makes sense. I will look into this project and see what I can learn from it.

There are probably remnants of Rendercam in there from previous experimentation, but the test project uses Orthographic. I assume your point is the same, though!

Whether I use Rendercam/Orthographic or my own solution, seems this is slightly trickier than what I thought. The reason I wanted to use one of the pre-made solutions was to take advantage of things like input/position translation and quality of life features like following, screen shake, etc. At some point the cost of tinkering with pre-made solutions will outweigh the benefit of the easily accessible quality of life features. At that point it will make sense to roll my own solution, and perhaps borrowing the most important features from Rendercam/Orthographic.

I wanted to avoid what happened in Fates of Ort, where to this day the game does strange things at unusual resolutions and I’ve had issues trusting input. I figured if I made a slight tweak to a solid foundation then that would be a much safer solution.

3 Likes

Again, I am not sure, but I think that the double camera approach should automatically solve the touch coordinate problem. Indeed you just ask the right camera to convert raw x/y input to world x/y.

1 Like

A new branch by @britzl adds the functionality I need!

I have confirmed this works the way I want it to. Here’s my project, which uses orthographic to render the gameworld in a subset of the screen. Currently the rest of the screen is empty (with the exception of a few gui nodes) but you could easily fill this with GUI or whatever else you want.

orthographic_viewport.zip (821.6 KB)

7 Likes

Yes, the latest release of Orthographic supports camera viewports including working screen to world and world to screen calculations.

The new viewport functionality makes it super easy to create multi-player split screen games using multiple cameras with different viewports.

7 Likes