Skip to content

Commit

Permalink
Added easily adjustable camera zoom.
Browse files Browse the repository at this point in the history
Modified projectors to accommodate for new zoom feature.
  • Loading branch information
britzl committed Feb 17, 2018
1 parent fd968ab commit 2d9feff
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 68 deletions.
68 changes: 52 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,14 @@ Select the script component attached to the ```camera.go``` to modify the proper
#### near_z (number) and far_z (number)
This is the near and far z-values used in the projection matrix, ie the near and far clipping plane. Anything with a z-value inside this range will be drawn by the render script.

#### zoom (number)
This is the zoom level of the camera. Modify it by calling ```camera.zoom_to()```. Read it using ```camera.get_zoom()``` or using ```go.get(camera_id, "zoom")```.

#### projection (hash)
The camera can be configured to support different kinds of orthographic projections. The default projection (aptly named ```DEFAULT```) uses the same orthographic projection matrix as in the default render script (ie aspect ratio isn't maintained and content is stretched). Other projections are available out-of-the box:

* ```FIXED``` - A fixed aspect ratio projection that zooms in/out to fit the original viewport contents regardless of window size.
* ```FIXED_NOZOOM``` - A fixed aspect ratio projection without any zoom.
* ```FIXED_ZOOM_2``` - A fixed aspect ratio projection zoomed 2x
* ```FIXED_ZOOM_3``` - A fixed aspect ratio projection zoomed 3x
* ```FIXED_ZOOM_4``` - A fixed aspect ratio projection zoomed 4x
* ```FIXED_ZOOM_5``` - A fixed aspect ratio projection zoomed 5x
* ```FIXED_ZOOM_6``` - A fixed aspect ratio projection zoomed 6x
* ```FIXED_ZOOM_7``` - A fixed aspect ratio projection zoomed 7x
* ```FIXED_ZOOM_8``` - A fixed aspect ratio projection zoomed 8x
* ```FIXED_ZOOM_9``` - A fixed aspect ratio projection zoomed 9x
* ```FIXED_ZOOM_10``` - A fixed aspect ratio projection zoomed 10x
* ```FIXED_AUTO``` - A fixed aspect ratio projection that automatically zooms in/out to fit the original viewport contents regardless of window size.
* ```FIXED_ZOOM``` - A fixed aspect ratio projection with zoom.

Note: For the above projections to work you need to pass the window dimensions from your render script to the camera. See [the section on render script integration](#render_script_integration).

Expand Down Expand Up @@ -91,7 +85,7 @@ It is recommended to send the window width and height from the render script to
### Example render script
The orthographic/render folder contains a render script that does the above mentioned integration of the Orthographic Camera API. Use it as it is or copy it into your project and make whatever modifications that you need.

## The Orthographic Camera API
## The Orthographic Camera API - functions
The API can be used in two ways:

1. Calling functions on the ```camera.lua``` module
Expand All @@ -113,6 +107,25 @@ Stop shaking the camera.
**PARAMETERS**
* ```camera_id``` (hash|url)


### camera.get_zoom(camera_id)
Get the current zoom level of the camera.

**PARAMETERS**
* ```camera_id``` (hash|url)

**RETURN**
* ```zoom``` (number) The current zoom of the camera


### camera.zoom_to(camera_id, zoom)
Change the zoom level of the camera, with optional animation.

**PARAMETERS**
* ```camera_id``` (hash|url)
* ```zoom``` (number) The new zoom level of the camera


### camera.follow(camera_id, target, [lerp])
Follow a game object.

Expand Down Expand Up @@ -243,30 +256,53 @@ Get the display size, as specified in game.project.
* ```width``` (number) - Display width.
* ```height``` (number) - Display height.

## The Orthographic Camera API - messages
Most of the functions of the API have message equivalents that can be sent to the camera component.

### shake
Message equivalent to ```camera.shake()```. Supports ```intensity```, ```duration``` and ```direction```.
Message equivalent to ```camera.shake()```. Accepted message keys: ```intensity```, ```duration``` and ```direction```.

msg.post("camera", "shake", { intensity = 0.05, duration = 2.5, direction = "both" })

### stop_shaking
Message equivalent to ```camera.stop_shaking()```.

msg.post("camera", "stop_shaking")

### shake_complete
Message sent back to the sender of a ```shake``` message when the shake has completed.

### follow
Message equivalent to ```camera.follow()```. Supports ```target``` and ```lerp```.
Message equivalent to ```camera.follow()```. Accepted message keys: ```target``` and ```lerp```.

msg.post("camera", "follow", { target = hash("player"), lerp = 0.7 })

### unfollow
Message equivalent to ```camera.unfollow()```.

msg.post("camera", "unfollow")

### deadzone
Message equivalent to ```camera.deadzone()```. Supports ```left```, ```right```, ```bottom```, ```top```.
Message equivalent to ```camera.deadzone()```. Accepted message keys: ```left```, ```right```, ```bottom``` and ```top```.

msg.post("camera", "deadzone", { left = 10, right = 200, bottom = 10, top = 100 })

### bounds
Message equivalent to ```camera.bounds()```. Supports ```left```, ```right```, ```bottom```, ```top```.
Message equivalent to ```camera.bounds()```. Accepted message keys: ```left```, ```right```, ```bottom``` and ```top```.

msg.post("camera", "bounds", { left = 10, right = 200, bottom = 10, top = 100 })

### zoom_to
Message equivalent to ```camera.zoom_to()```. Accepted message keys: ```zoom```.

msg.post("camera", "zoom_to", { zoom = 2.5 })

### enable
Enable the camera. While the camera is enabled it will update it's view and projection and send these to the render script.

msg.post("camera", "enable")

### disable
Disable the camera.

msg.post("camera", "disable")
21 changes: 10 additions & 11 deletions example/camera_controls.gui_script
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,7 @@ function init(self)
self.bounds_enabled = false
self.deadzone_enabled = false
self.zoomlevel = 1.0

camera.add_projector(hash("FREEZOOM"), function(camera_id, near_z, far_z)
local window_width, window_height = camera.get_window_size()
local display_width, display_height = camera.get_display_size()
local projected_width = window_width / self.zoomlevel
local projected_height = window_height / self.zoomlevel
local xoffset = -(projected_width - display_width) / 2
local yoffset = -(projected_height - display_height) / 2
return vmath.matrix4_orthographic(xoffset, xoffset + projected_width, yoffset, yoffset + projected_height, near_z, far_z)
end)
camera.use_projector(CAMERA_ID, hash("FREEZOOM"))
msg.post("#", "get_zoomlevel")
end


Expand All @@ -31,6 +21,13 @@ function update(self, dt)
end


function on_message(self, message_id, message, sender)
if message_id == hash("get_zoomlevel") then
self.zoomlevel = camera.get_zoom(CAMERA_ID)
gui.set_text(gui.get_node("zoomlevel"), tostring(self.zoomlevel))
end
end

function on_input(self, action_id, action)
if action_id == hash("touch") and action.released then
if gui.pick_node(gui.get_node("unfollow/button"), action.x, action.y) then
Expand Down Expand Up @@ -66,9 +63,11 @@ function on_input(self, action_id, action)
end
elseif gui.pick_node(gui.get_node("zoomin/button"), action.x, action.y) then
self.zoomlevel = self.zoomlevel + 0.25
msg.post("camera", "zoom_to", { zoom = self.zoomlevel } )
gui.set_text(gui.get_node("zoomlevel"), tostring(self.zoomlevel))
elseif gui.pick_node(gui.get_node("zoomout/button"), action.x, action.y) then
self.zoomlevel = math.max(0.25, self.zoomlevel - 0.25)
msg.post("camera", "zoom_to", { zoom = self.zoomlevel } )
gui.set_text(gui.get_node("zoomlevel"), tostring(self.zoomlevel))
end
end
Expand Down
7 changes: 6 additions & 1 deletion example/example.collection
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@ instances {
}
component_properties {
id: "script"
properties {
id: "zoom"
value: "1.0"
type: PROPERTY_TYPE_NUMBER
}
properties {
id: "projection"
value: "FIXED_NOZOOM"
value: "FIXED_ZOOM"
type: PROPERTY_TYPE_HASH
}
}
Expand Down
72 changes: 33 additions & 39 deletions orthographic/camera.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,8 @@ M.SHAKE_VERTICAL = hash("vertical")

M.PROJECTOR = {}
M.PROJECTOR.DEFAULT = hash("DEFAULT")
M.PROJECTOR.FIXED = hash("FIXED")
M.PROJECTOR.FIXED_NOZOOM = hash("FIXED_NOZOOM")
M.PROJECTOR.FIXED_ZOOM_2 = hash("FIXED_ZOOM_2")
M.PROJECTOR.FIXED_ZOOM_3 = hash("FIXED_ZOOM_3")
M.PROJECTOR.FIXED_ZOOM_4 = hash("FIXED_ZOOM_4")
M.PROJECTOR.FIXED_ZOOM_5 = hash("FIXED_ZOOM_5")
M.PROJECTOR.FIXED_ZOOM_6 = hash("FIXED_ZOOM_6")
M.PROJECTOR.FIXED_ZOOM_7 = hash("FIXED_ZOOM_7")
M.PROJECTOR.FIXED_ZOOM_8 = hash("FIXED_ZOOM_8")
M.PROJECTOR.FIXED_ZOOM_9 = hash("FIXED_ZOOM_9")
M.PROJECTOR.FIXED_ZOOM_10 = hash("FIXED_ZOOM_10")
M.PROJECTOR.FIXED_AUTO = hash("FIXED_AUTO")
M.PROJECTOR.FIXED_ZOOM = hash("FIXED_ZOOM")

local DISPLAY_WIDTH = tonumber(sys.get_config("display.width"))
local DISPLAY_HEIGHT = tonumber(sys.get_config("display.height"))
Expand All @@ -44,50 +35,30 @@ local projectors = {}

-- the default projector from the default render script
-- will stretch content
projectors[M.PROJECTOR.DEFAULT] = function(camera_id, near_z, far_z)
projectors[M.PROJECTOR.DEFAULT] = function(camera_id, near_z, far_z, zoom)
return vmath.matrix4_orthographic(0, DISPLAY_WIDTH, 0, DISPLAY_HEIGHT, near_z, far_z)
end

-- setup a fixed aspect ratio projection that zooms in/out to fit the original viewport contents
-- regardless of window size
projectors[M.PROJECTOR.FIXED] = function(camera_id, near_z, far_z)
local zoom_factor = math.min(WINDOW_WIDTH / DISPLAY_WIDTH, WINDOW_HEIGHT / DISPLAY_HEIGHT)
projectors[M.PROJECTOR.FIXED_AUTO] = function(camera_id, near_z, far_z, zoom)
local zoom_factor = math.min(WINDOW_WIDTH / DISPLAY_WIDTH, WINDOW_HEIGHT / DISPLAY_HEIGHT) * zoom
local projected_width = WINDOW_WIDTH / zoom_factor
local projected_height = WINDOW_HEIGHT / zoom_factor
local xoffset = -(projected_width - DISPLAY_WIDTH) / 2
local yoffset = -(projected_height - DISPLAY_HEIGHT) / 2
return vmath.matrix4_orthographic(xoffset, xoffset + projected_width, yoffset, yoffset + projected_height, near_z, far_z)
end

-- setup a fixed aspect ratio projection without any zoom
projectors[M.PROJECTOR.FIXED_NOZOOM] = function(camera_id, near_z, far_z)
local projected_width = WINDOW_WIDTH
local projected_height = WINDOW_HEIGHT
-- setup a fixed aspect ratio projection with a fixed zoom
projectors[M.PROJECTOR.FIXED_ZOOM] = function(camera_id, near_z, far_z, zoom)
local projected_width = WINDOW_WIDTH / zoom
local projected_height = WINDOW_HEIGHT / zoom
local xoffset = -(projected_width - DISPLAY_WIDTH) / 2
local yoffset = -(projected_height - DISPLAY_HEIGHT) / 2
return vmath.matrix4_orthographic(xoffset, xoffset + projected_width, yoffset, yoffset + projected_height, near_z, far_z)
end

local function create_fixed_zoom_projector(zoom_factor)
return function(camera_id, near_z, far_z)
local projected_width = WINDOW_WIDTH / zoom_factor
local projected_height = WINDOW_HEIGHT / zoom_factor
local xoffset = -(projected_width - DISPLAY_WIDTH) / 2
local yoffset = -(projected_height - DISPLAY_HEIGHT) / 2
return vmath.matrix4_orthographic(xoffset, xoffset + projected_width, yoffset, yoffset + projected_height, near_z, far_z)
end
end

projectors[M.PROJECTOR.FIXED_ZOOM_2] = create_fixed_zoom_projector(2)
projectors[M.PROJECTOR.FIXED_ZOOM_3] = create_fixed_zoom_projector(3)
projectors[M.PROJECTOR.FIXED_ZOOM_4] = create_fixed_zoom_projector(4)
projectors[M.PROJECTOR.FIXED_ZOOM_5] = create_fixed_zoom_projector(5)
projectors[M.PROJECTOR.FIXED_ZOOM_6] = create_fixed_zoom_projector(6)
projectors[M.PROJECTOR.FIXED_ZOOM_7] = create_fixed_zoom_projector(7)
projectors[M.PROJECTOR.FIXED_ZOOM_8] = create_fixed_zoom_projector(8)
projectors[M.PROJECTOR.FIXED_ZOOM_9] = create_fixed_zoom_projector(9)
projectors[M.PROJECTOR.FIXED_ZOOM_10] = create_fixed_zoom_projector(10)


--- Add a custom projector
-- @param projector_id Unique id of the projector (hash)
Expand Down Expand Up @@ -142,7 +113,7 @@ end
local function calculate_projection(camera_id)
local camera = cameras[camera_id]
local projector_fn = projectors[camera.projector_id] or projectors[hash("DEFAULT")]
return projector_fn(camera_id, camera.near_z, camera.far_z)
return projector_fn(camera_id, camera.near_z, camera.far_z, camera.zoom)
end

local function calculate_view(camera_id, camera_world_pos, offset)
Expand Down Expand Up @@ -171,6 +142,7 @@ function M.init(camera_id, settings)
assert(settings.near_z, "You must provide a near z-value")
assert(settings.far_z, "You must provide a far z-value")
assert(settings.projector_id, "You must provide a projector id")
settings.zoom = settings.zoom or 1
cameras[camera_id] = settings
cameras[camera_id].view = calculate_view(camera_id, go.get_world_position(camera_id))
cameras[camera_id].projection = calculate_projection(camera_id)
Expand Down Expand Up @@ -362,6 +334,28 @@ function M.stop_shaking(camera_id)
end


--- Set the zoom level of a camera
-- @param camera_id
-- @param zoom The zoom level of the camera
function M.set_zoom(camera_id, zoom)
assert(camera_id, "You must provide a camera id")
assert(zoom, "You must provide a zoom level")
cameras[camera_id].zoom = zoom
cameras[camera_id].view = calculate_view(camera_id, go.get_world_position(camera_id))
cameras[camera_id].projection = calculate_projection(camera_id)
end


--- Get the zoom level of a camera
-- @param camera_id
-- @return Current zoom level of the camera
function M.get_zoom(camera_id)
assert(camera_id, "You must provide a camera id")
return cameras[camera_id].zoom
end



--- Get the projection matrix for a camera
-- @param camera_id
-- @return Projection matrix
Expand Down
7 changes: 6 additions & 1 deletion orthographic/camera.script
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
go.property("near_z", -1)
go.property("far_z", 1)
go.property("zoom", 1)
go.property("projection", hash("DEFAULT"))
go.property("enabled", true)

Expand All @@ -15,13 +16,14 @@ local STOP_SHAKING = hash("stop_shaking")
local DEADZONE = hash("deadzone")
local BOUNDS = hash("bounds")
local UPDATE_CAMERA = hash("update_camera")

local ZOOM_TO = hash("zoom_to")

function init(self)
camera.init(go.get_id(), {
near_z = self.near_z,
far_z = self.far_z,
projector_id = self.projection,
zoom = self.zoom,
})
end

Expand Down Expand Up @@ -63,5 +65,8 @@ function on_message(self, message_id, message, sender)
end)
elseif message_id == STOP_SHAKING then
camera.stop_shaking(go.get_id())
elseif message_id == ZOOM_TO then
self.zoom = message.zoom
camera.set_zoom(go.get_id(), message.zoom)
end
end

0 comments on commit 2d9feff

Please sign in to comment.