Instead of pre-deciding on a fixed resolution with black bars around, I adapt to any screen size and orientation. Scaling only by integer multiples (for square pixels), but drawing the upscaled canvas such that the edgemost 1 pixel on all sides "bleeds" outside the screen. That is, the view fills the whole screen, always, but the edgemost pixels are cut off.
You set the
minimum view size you want, on the
shorter axis of the screen. The resolution is scaled up as much as possible, but not beyond your minimum size. The calculations then give you the
actual view size you get.
This code conceptually splits your screen into two parts: A square that fills your screen - this is your "main view" or "safe drawing area" that is the same for any screen orientation; And the rectangle that is left over, which can be used for a GUI (but isn't guaranteed to have any minimum size). The main view square is centered and the side view rectangle split into two on very wide screens.
Code: Select all
function love.draw()
-- DIMENSIONAL MATH:
screen_x, screen_y, screen_w, screen_h = love.window.getSafeArea()
short_s = math.min(screen_w, screen_h)
if force_resolution >= 1 then
target_resolution = force_resolution
elseif target_view_s then
target_resolution = math.max(1, math.ceil(short_s / (target_view_s * 2 -1)))
else
target_resolution = 1
end
if resolution ~= target_resolution then
resolution = target_resolution
view_w = math.ceil(screen_w / resolution)
view_h = math.ceil(screen_h / resolution)
view = love.graphics.newCanvas(view_w, view_h)
view:setFilter("nearest", "nearest")
end
short_s = math.ceil(short_s / resolution)
main_x, main_y, main_w, main_h = 0, 0, short_s, short_s
if view_w > view_h then -- wide
side_x, side_y = main_x + main_w, 0
side_w, side_h = view_w - main_w, view_h
else -- tall
side_x, side_y = 0, main_y + main_h
side_w, side_h = view_w, view_h - main_h
end
split = false
if force_split == true or (force_split ~= false and math.min(side_w, side_h) > (short_s / 2)) then
split = true
end
if split then
if view_w > view_h then
-- wide
side_w = math.floor(side_w / 2)
main_x = main_x + side_w
side_x = side_x + side_w
-- fix odd
if (side_w * 2 + main_w) < view_w then
main_w = main_w + 1
side_x = side_x + 1
end
else
-- tall
side_h = math.floor(side_h / 2)
main_y = main_y + side_h
side_y = side_y + side_h
-- fix odd
if (side_h * 2 + main_h) < view_h then
main_h = main_h + 1
side_y = side_y + 1
end
end
end
view_offs_x = math.floor((screen_w-(view_w*resolution))/2)
view_offs_y = math.floor((screen_h-(view_h*resolution))/2)
mouse_x, mouse_y = love.mouse.getPosition()
mouse_x = (mouse_x - view_offs_x) / resolution
mouse_y = (mouse_y - view_offs_y) / resolution
-- GAME DRAWING:
-- note: the edgemost 1 pixel border on all sides may be partially outside visible screen.
-- game
love.graphics.setCanvas(view)
love.graphics.origin()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setBlendMode("alpha")
love.graphics.setShader()
draw_game()
-- blit
love.graphics.setCanvas()
love.graphics.origin()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setBlendMode("alpha")
love.graphics.setShader()
love.graphics.draw(view, view_offs_x,view_offs_y, 0, resolution, resolution)
end
function love.resize(w, h)
resolution = 0 -- force canvas reset
end
The key input variables here are:
- target_view_s - This is your minimum view size in pixels on the shorter axis of the screen.
- force_resolution - Integer multiple pixel size. If not nil, overrides adaptive resolution. E.g. set to 1 for actual pixels. Can be changed over time to create dynamic pixelation effects.
- force_split - If true or false, forces the side view to either split or not split, respectively.
And the output variables you'll use:
- view, view_w, view_h - The canvas you draw to and its size. At least target_view_s in both dimensions. May be smaller than actual screen because of scaling.
- main_x, main_y, main_w, main_h - Position and size of main view square on the canvas. This is where you draw your game world.
- side_x, side_y, side_w, side_h - Position and size of side view rectangle on the canvas. Draw GUI here.
- split - Whether the side view is wide enough to be split into two.
- mouse_x, mouse_y - Mouse coordinates on the canvas.
- resolution - Integer multiple pixel size. That is, this is how big your pixels are. The scale of the view canvas.
- draw_game() - The function you write that does the actual drawing.
To recap: Set target_view_s to your
minimum view size. 'view' is the canvas you draw to in draw_game(). Use 'main' and 'side' coordinates to position your elements on the screen. Note that they change size and position to adapt to any screen size and orientation. The edgemost 1 pixel border may be partially outside screen and thus cut off.
For example, if I set my target_view_s to 480, and my screen is 1920x1080, I'll get an effective resolution of 2x, with a main view of 1080x1080, and a side panel of 840x1080, which is split into two 420x1080 panels around the centered main square.
If the screen is taller than wide (portrait orientation, i.e. probably smart phone), then the side panel is horizontal and below the main square (for good thumb access).