Difference between revisions of "TLfres"

m (Added link to forum thread)
m (Example)
(2 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
== About ==
 
== About ==
TLfres aims to make it extremely easy to make [http://dl.dropbox.com/u/3713769/web/Love/TLTools/TLfres%20pic.png any game run at any screen resolution]. Using it doesn't even require you to rewrite your graphics code!
+
TLfres aims to make it easy to make any game run at any screen resolution. Using it doesn't even require you to rewrite your graphics code!
  
 
It's under the [http://www.opensource.org/licenses/zlib-license.php ZLIB license].
 
It's under the [http://www.opensource.org/licenses/zlib-license.php ZLIB license].
 
=== Download ===
 
[http://dl.dropbox.com/u/3713769/web/Love/TLTools/TLfres.lua Direct from Dropbox]
 
 
=== Contact ===
 
*[http://love2d.org/forums/viewtopic.php?f=5&t=2900 Forum thread]
 
 
  
 
== Setup ==
 
== Setup ==
Everything in the module is contained in the TLfres namespace, so don't worry about it messing with your global variables. Assuming your game was built to use the Love-default resolution of 800x600:
+
Everything in the module is contained in the TLfres namespace, so don't worry about it messing with your global variables. Assuming your game was built to use the Love-default resolution of 800×600:
  
# Put TLfres.lua in your game's folder
+
# Put tlfres.lua in your game's folder
# At the top of main.lua, add the line <code>require"TLfres"</code>
+
# At the top of main.lua, add the line <code>local TLfres = require "tlfres"</code>
# At the beginning of love.draw(), add the line <code>TLfres.transform()</code>
+
# At the beginning of love.draw(), add the line <code>TLfres.beginRendering(800, 600)</code>
# At the bottom of love.draw() add the line <code>TLfres.letterbox(4,3)</code>
+
# At the bottom of love.draw() add the line <code>TLfres.endRendering()</code>
 +
# Whenever you need to know the current scaling factor, call <code>TLfres.getScale(800, 600)</code>
 +
# Whenever you would call love.mouse.getPosition, instead call <code>TLfres.getMousePosition(800, 600)</code>
 +
# Whenever you would call love.graphics.setPointSize(x), instead call <code>love.graphics.setPointSize(x * TLfres.getScale())</code> (Love doesn't scale point sizes automatically)
  
 
== FAQ ==
 
== FAQ ==
Line 24: Line 20:
  
 
;Q) What resolutions / aspect ratios does it support?
 
;Q) What resolutions / aspect ratios does it support?
:A) Theoretically, any. However, letterboxes act weird if you use a screen taller than it is wide.
+
:A) Any.
  
 
;Q) I made my game to use a non-default resolution. Will I have to change everything to make TLfres work with it?
 
;Q) I made my game to use a non-default resolution. Will I have to change everything to make TLfres work with it?
:A) Nope! Just call TLfres.setScreen() with the resolution you built your game for (see below).
+
:A) Nope! Just call TLfres.beginRendering() with the resolution you built your game for (see below).
  
 
== Functions ==
 
== Functions ==
====TLfres.setScreen====
+
====TLfres.beginRendering====
<source lang="lua">TLfres.setScreen(mode, extent, centered, stretch)</source>
+
<source lang="lua">TLfres.beginRendering(width, height, centered)</source>
Call this INSTEAD of [[love.graphics.setMode]]! This will both configure TLfres and change resolution. If your game was originally made to use the default Love screen of 800x600, you don't need to use any parameters.
+
Call this at the very beginning of [[love.draw]](), before drawing anything. This scales and translates the screen coordinates as needed by the current screen resolution. If you want to draw something at absolute screen coordinates, do it before this function (or after endRendering()).
{{param|table|mode| }}This table specifies screen geometry. It may contain the following data: w (the width of the screen, in pixels), h (the height of the screen, in pixels), full (whether to use fullscreen or not), vsync (whether to use vsync or not), aa (how many antialiasing samples to use). Defaults to <code>{w=800, h=600, full=false, vsync=false, aa=0}.</code>
+
{{param|number|width|Width of the target canvas.}}
{{param|number|extent (800)| }}The width of the screen that the game was originally programmed to use. Alternately, if you're making a new game using TLfres, this number can be whatever you want to represent the width of the screen in your code. For instance, I like using the number 1, so all coordinates are normalized decimals (this makes the most sense to me). You could use 100 if you prefer to think in percentages (drawing something at x=50 would be the middle of the screen in this case), etc..
+
{{param|number|height|Width of the target canvas.}}
{{param|boolean|centered (false)| }}If false, the coordinate 0,0 is located at the top-left of the screen, as in default Love. If true, 0,0 is the center of the screen. This should be false if you're adding TLfres to an existing game, but may be set to taste when programming a new game.
+
{{param|boolean|centered (false)|If true, (0, 0) is the middle of the canvas, otherwise it is the top-left corner.}}
{{param|boolean|stretch (false)| }}If true, graphics will be stretched to fill the entire screen at any res. If the aspect ratio the game was programmed to use is different from the aspect ratio of the screen, stretching will occur. Some people prefer this to letterboxing, so it's a matter of personal taste. If you don't stretch, make sure to call TLfres.letterbox() at the end of love.draw().
+
 
 +
====TLfres.endRendering====
 +
<source lang="lua">TLfres.endRendering(letterboxColor)</source>
 +
Call this at the end of [[love.draw]](), after drawing everything. This draws letterboxes to trim any graphics which should be out of the screen. It defaults to black letterboxes.
 +
{{param|table|letterboxColor ({0,0,0, 255})|The color of the letterboxes, given in RGBA (just like [[love.graphics.setColor#Function 2|love.graphics.setColor]]). Defaults to black.}}
 +
 
 +
====TLfres.getScale====
 +
<source lang="lua">TLfres.getScale(width, height)</source>
 +
 
 +
{{param|number|width|The canvas width expected by your code.}}
 +
{{param|number|height|The canvas height expected by your code.}}
 +
 
 +
====TLfres.getMousePosition====
 +
<source lang="lua">TLfres.getMousePosition(width, height)</source>
 +
Use this any time you would normally call [[love.mouse.getPosition]]. The returned position is scaled to the given dimensions.
 +
 
 +
{{param|number|width|The canvas width expected by your input code.}}
 +
{{param|number|height|The canvas height expected by your input code.}}
 +
 
 +
==tlfres.lua==
 +
<source lang="lua">
 +
local lwGetMode    = _G.love.window.getMode
 +
local lgPush        = _G.love.graphics.push
 +
local lgPop        = _G.love.graphics.pop
 +
local lgTranslate  = _G.love.graphics.translate
 +
local lgScale      = _G.love.graphics.scale
 +
local lgRectangle  = _G.love.graphics.rectangle
 +
local lgSetColor    = _G.love.graphics.setColor
 +
local lmGetPosition = _G.love.mouse.getPosition
 +
local min = math.min
 +
 
 +
local TLfres = {}
 +
 
 +
local lastMouseX, lastMouseY = 0, 0
 +
local currentlyRendering
 +
 
 +
-- Internal helper function
 +
local function _getRawMousePosition(width, height)
 +
  local x, y = lmGetPosition()
 +
  local w, h = lwGetMode()
 +
  local scale = min(w/width, h/height)
 +
  return (x - (w - width * scale) * 0.5)/scale, (y - (h - height * scale) * 0.5)/scale
 +
end
 +
 
 +
-- Use this any time you would normally call love.mouse.getPosition.
 +
-- The returned position is scaled to the given dimensions.
 +
function TLfres.getMousePosition(width, height)
 +
  local x, y = _getRawMousePosition(width, height)
 +
  if x >= 0 and x <= width and y >= 0 and y <= height then
 +
      lastMouseX, lastMouseY = _getRawMousePosition(width, height)
 +
  end
 +
  return lastMouseX, lastMouseY
 +
end
 +
 
 +
-- Calculate the current scale based on the desired dimensions and current ones
 +
-- If called within a rendering block, width and height are optional.
 +
function TLfres.getScale(width, height)
 +
  if currentlyRendering then
 +
      width  = width  or currentlyRendering[1]
 +
      height = height or currentlyRendering[2]
 +
  end
 +
  local w, h = lwGetMode()
 +
  return min(w/width, h/height)
 +
end
 +
 
 +
-- Zooms and centers to fit width×height into the current window.
 +
-- 0,0 is at the top-left of the canvas, or the middle if centered is true.
 +
-- Use love.graphics.push before this and love.graphics.pop after done rendering
 +
function TLfres.beginRendering(width, height, centered)
 +
  if currentlyRendering then
 +
      error("Must call tlfres.endRendering before calling beginRendering.")
 +
      return
 +
  end
 +
  currentlyRendering = {width, height}
 +
  lgPush()
 +
 
 +
  local w, h = lwGetMode()
 +
  local scale = min(w/width, h/height)
 +
  lgTranslate((w - width * scale) * 0.5, (h - height * scale) * 0.5)
 +
  lgScale(scale)
 +
  if centered then
 +
      lgTranslate(0.5 * width, 0.5 * height)
 +
  end
 +
  return scale
 +
end
 +
 
 +
local _black = {0, 0, 0, 255}
 +
 
 +
-- Pops out of the transform; if letterboxColor is true, draws black letterbox
 +
-- bars. letterboxColor can also be any {r, g, b, a} table.
 +
function TLfres.endRendering(letterboxColor)
 +
  if not currentlyRendering then
 +
      error("Must call tlfres.beginRendering before calling endRendering.")
 +
      return
 +
  end
 +
  local width, height = currentlyRendering[1], currentlyRendering[2]
 +
  currentlyRendering = nil
 +
  lgPop()
 +
 
 +
  local w, h = lwGetMode()
 +
  local scale = min(w/width, h/height)
 +
  width, height = width * scale, height * scale
 +
 
 +
  lgSetColor(letterboxColor or _black)
 +
  lgRectangle("fill", 0, 0,  w, 0.5 * (h - height)) -- top
 +
  lgRectangle("fill", 0, h,  w, -0.5 * (h - height)) -- bottom
 +
  lgRectangle("fill", 0, 0, 0.5 * (w - width), h)  -- left
 +
  lgRectangle("fill", w, 0, -0.5 * (w - width), h)  -- right
 +
end
 +
 
 +
return TLfres
 +
</source>
 +
 
 +
==Example==
 +
<source lang="lua">
 +
local TLfres = require "tlfres"
 +
 
 +
local CANVAS_WIDTH = 800
 +
local CANVAS_HEIGHT = 600
 +
local POINT_SIZE = 1
 +
 
 +
function love.mouse.getPosition() -- Override the standard function with our helper function
 +
  return TLfres.getMousePosition(CANVAS_WIDTH, CANVAS_HEIGHT)
 +
end
 +
 
 +
function love.draw()
 +
  tlfres.beginRendering(CANVAS_WIDTH, CANVAS_HEIGHT)
  
====TLfres.transform====
+
      love.graphics.setPointSize(tlfres.getScale()*POINT_SIZE) -- Point size doesn't scale automatically, so multiply it manually.
<source lang="lua">TLfres.transform()</source>
+
      love.graphics.points(400, 300) -- Will draw at the center of the canvas no matter how the screen is resized.
Call this at the very beginning of [[love.draw]](), before drawing anything. This scales and translates the screen coordinates as needed by the current screen resolution. If you want to draw something at absolute screen coordinates, you may want to use [[love.graphics.push]]() before calling TLfres.transform().
 
  
====TLfres.letterbox====
+
  tlfres.endRendering() -- Draw black letterbox
<source lang="lua">TLfres.letterbox(w, h, c)</source>
+
end
If not stretching the graphics (see TLfres.setScreen()), call this at the end of [[love.draw]](), after drawing everything. This draws letterboxes to trim any graphics which should be out of the screen. It defaults to the aspect ratio 4:3 with black letterboxes.
+
</source>
{{param|number|w (4)|The width of the aspect ratio the game is meant to have.}}
 
{{param|number|h (3)|The height of the aspect ratio the game is meant to have.}}
 
{{param|table|c {0,0,0, 255}|The color of the letterboxes, given in RGBA (just like [[love.graphics.setColor#Function 2|love.graphics.setColor]]). Defaults to black.}}
 
  
{{#set:LOVE Version=0.7.0}}
+
{{#set:LOVE Version=0.10.2}}
 
{{#set:Description=Lets games run at any resolution, easily and without stretching}}
 
{{#set:Description=Lets games run at any resolution, easily and without stretching}}
 
{{#set:License=ZLIB license}}
 
{{#set:License=ZLIB license}}
 
{{#set:Author=User:Taehl}}
 
{{#set:Author=User:Taehl}}
 
[[Category:Libraries]]
 
[[Category:Libraries]]

Revision as of 23:25, 31 October 2017

About

TLfres aims to make it easy to make any game run at any screen resolution. Using it doesn't even require you to rewrite your graphics code!

It's under the ZLIB license.

Setup

Everything in the module is contained in the TLfres namespace, so don't worry about it messing with your global variables. Assuming your game was built to use the Love-default resolution of 800×600:

  1. Put tlfres.lua in your game's folder
  2. At the top of main.lua, add the line local TLfres = require "tlfres"
  3. At the beginning of love.draw(), add the line TLfres.beginRendering(800, 600)
  4. At the bottom of love.draw() add the line TLfres.endRendering()
  5. Whenever you need to know the current scaling factor, call TLfres.getScale(800, 600)
  6. Whenever you would call love.mouse.getPosition, instead call TLfres.getMousePosition(800, 600)
  7. Whenever you would call love.graphics.setPointSize(x), instead call love.graphics.setPointSize(x * TLfres.getScale()) (Love doesn't scale point sizes automatically)

FAQ

Q) Why should I use this instead of love.graphics.scale?
A) Because even stretching the screen to a given res requires tricky math, which TLfres can do for you. Furthermore, most people hate stretched graphics - that's why you should use TLfres's letterboxing option. Instead of stretching the screen, it resizes it in the correct ratio and draws black boxes at the top and bottom if needed.
Q) What resolutions / aspect ratios does it support?
A) Any.
Q) I made my game to use a non-default resolution. Will I have to change everything to make TLfres work with it?
A) Nope! Just call TLfres.beginRendering() with the resolution you built your game for (see below).

Functions

TLfres.beginRendering

TLfres.beginRendering(width, height, centered)

Call this at the very beginning of love.draw(), before drawing anything. This scales and translates the screen coordinates as needed by the current screen resolution. If you want to draw something at absolute screen coordinates, do it before this function (or after endRendering()).

number width
Width of the target canvas.
number height
Width of the target canvas.
boolean centered (false)
If true, (0, 0) is the middle of the canvas, otherwise it is the top-left corner.

TLfres.endRendering

TLfres.endRendering(letterboxColor)

Call this at the end of love.draw(), after drawing everything. This draws letterboxes to trim any graphics which should be out of the screen. It defaults to black letterboxes.

table letterboxColor ({0,0,0, 255})
The color of the letterboxes, given in RGBA (just like love.graphics.setColor). Defaults to black.

TLfres.getScale

TLfres.getScale(width, height)
number width
The canvas width expected by your code.
number height
The canvas height expected by your code.

TLfres.getMousePosition

TLfres.getMousePosition(width, height)

Use this any time you would normally call love.mouse.getPosition. The returned position is scaled to the given dimensions.

number width
The canvas width expected by your input code.
number height
The canvas height expected by your input code.

tlfres.lua

local lwGetMode     = _G.love.window.getMode
local lgPush        = _G.love.graphics.push
local lgPop         = _G.love.graphics.pop
local lgTranslate   = _G.love.graphics.translate
local lgScale       = _G.love.graphics.scale
local lgRectangle   = _G.love.graphics.rectangle
local lgSetColor    = _G.love.graphics.setColor
local lmGetPosition = _G.love.mouse.getPosition
local min = math.min

local TLfres = {}

local lastMouseX, lastMouseY = 0, 0
local currentlyRendering

-- Internal helper function
local function _getRawMousePosition(width, height)
   local x, y = lmGetPosition()
   local w, h = lwGetMode()
   local scale = min(w/width, h/height)
   return (x - (w - width * scale) * 0.5)/scale, (y - (h - height * scale) * 0.5)/scale
end

-- Use this any time you would normally call love.mouse.getPosition.
-- The returned position is scaled to the given dimensions.
function TLfres.getMousePosition(width, height)
   local x, y = _getRawMousePosition(width, height)
   if x >= 0 and x <= width and y >= 0 and y <= height then
      lastMouseX, lastMouseY = _getRawMousePosition(width, height)
   end
   return lastMouseX, lastMouseY
end

-- Calculate the current scale based on the desired dimensions and current ones
-- If called within a rendering block, width and height are optional.
function TLfres.getScale(width, height)
   if currentlyRendering then
      width  = width  or currentlyRendering[1]
      height = height or currentlyRendering[2]
   end
   local w, h = lwGetMode()
   return min(w/width, h/height)
end

-- Zooms and centers to fit width×height into the current window.
-- 0,0 is at the top-left of the canvas, or the middle if centered is true.
-- Use love.graphics.push before this and love.graphics.pop after done rendering
function TLfres.beginRendering(width, height, centered)
   if currentlyRendering then
      error("Must call tlfres.endRendering before calling beginRendering.")
      return
   end
   currentlyRendering = {width, height}
   lgPush()

   local w, h = lwGetMode()
   local scale = min(w/width, h/height)
   lgTranslate((w - width * scale) * 0.5, (h - height * scale) * 0.5)
   lgScale(scale)
   if centered then
      lgTranslate(0.5 * width, 0.5 * height)
   end
   return scale
end

local _black = {0, 0, 0, 255}

-- Pops out of the transform; if letterboxColor is true, draws black letterbox
-- bars. letterboxColor can also be any {r, g, b, a} table.
function TLfres.endRendering(letterboxColor)
   if not currentlyRendering then
      error("Must call tlfres.beginRendering before calling endRendering.")
      return
   end
   local width, height = currentlyRendering[1], currentlyRendering[2]
   currentlyRendering = nil
   lgPop()

   local w, h = lwGetMode()
   local scale = min(w/width, h/height)
   width, height = width * scale, height * scale

   lgSetColor(letterboxColor or _black)
   lgRectangle("fill", 0, 0,  w,  0.5 * (h - height)) -- top
   lgRectangle("fill", 0, h,  w, -0.5 * (h - height)) -- bottom
   lgRectangle("fill", 0, 0,  0.5 * (w - width), h)   -- left
   lgRectangle("fill", w, 0, -0.5 * (w - width), h)   -- right
end

return TLfres

Example

local TLfres = require "tlfres"

local CANVAS_WIDTH = 800
local CANVAS_HEIGHT = 600
local POINT_SIZE = 1

function love.mouse.getPosition() -- Override the standard function with our helper function
   return TLfres.getMousePosition(CANVAS_WIDTH, CANVAS_HEIGHT)
end

function love.draw()
   tlfres.beginRendering(CANVAS_WIDTH, CANVAS_HEIGHT)

      love.graphics.setPointSize(tlfres.getScale()*POINT_SIZE) -- Point size doesn't scale automatically, so multiply it manually.
      love.graphics.points(400, 300) -- Will draw at the center of the canvas no matter how the screen is resized.

   tlfres.endRendering() -- Draw black letterbox
end