Skip to content

Commit

Permalink
Updated how script properties are handled
Browse files Browse the repository at this point in the history
  • Loading branch information
britzl committed Feb 17, 2018
1 parent 58f8421 commit e4bb217
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 71 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Select the script component attached to the ```camera.go``` to modify the proper
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")```.
This is the zoom level of the camera. Modify it by calling ```camera.zoom_to()```, ```go.set(camera, "zoom")``` or ```go.animate(camera, "zoom", ...)```. Read it using ```camera.get_zoom()``` or ```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:
Expand All @@ -37,6 +37,22 @@ Additional custom projections can be added, see ```camera.add_projector()``` bel
#### enabled (boolean)
This controls if the camera is enabled by default or not. Send ```enable``` and ```disable``` messages to the script or use ```go.set(id, "enable", true|false)``` to toggle this value.

#### follow (boolean)
This controls if the camera should follow a target or not. See ```camera.follow()``` for details.

#### follow_target (hash)
Id of the game object to follow. See ```camera.follow()``` for details.

#### follow_lerp (number)
Amount of lerp when following a target. See ```camera.follow()``` for details.

#### bounds_left (number), bounds_right (number), bounds_top (number), bounds_bottom (number)
The camera bounds. See ```camera.bounds()``` for details.

#### deadzone_left (number), deadzone_right (number), deadzone_top (number), deadzone_bottom (number)
The camera deadzone. See ```camera.deadzone()``` for details.


## Render script integration
While the camera is enabled it will send ```set_view_projection``` messages once per frame to the render script. The message is the same as that of the camera component, meaning that it contains ```id```, ```view``` and ```projection``` values. Make sure that these values are handled and used properly in the render script:

Expand Down
4 changes: 2 additions & 2 deletions example/camera_controls.gui_script
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ function on_input(self, action_id, action)
msg.post("camera", "unfollow")
return true
elseif gui.pick_node(gui.get_node("follow_basic/button"), action.x, action.y) then
msg.post("camera", "follow", { target = "player" })
msg.post("camera", "follow", { target = "/player" })
return true
elseif gui.pick_node(gui.get_node("follow_lerp/button"), action.x, action.y) then
msg.post("camera", "follow", { target = "player", lerp = 0.1 })
msg.post("camera", "follow", { target = "/player", lerp = 0.1 })
return true
elseif gui.pick_node(gui.get_node("shake_both/button"), action.x, action.y) then
msg.post("camera", "shake", { intensity = 0.05, duration = 0.5, direction = hash("both") })
Expand Down
104 changes: 48 additions & 56 deletions orthographic/camera.lua
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,7 @@ function M.use_projector(camera_id, projector_id)
assert(camera_id, "You must provide a camera id")
assert(projector_id, "You must provide a projector id")
local camera = cameras[camera_id]
if camera then
camera.projector_id = projector_id
end
msg.post(camera.url, "use_projection", { projection = projector_id })
end


Expand Down Expand Up @@ -114,8 +112,13 @@ 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, camera.zoom)
local projector_id = go.get(camera.url, "projection")
local near_z = go.get(camera.url, "near_z")
local far_z = go.get(camera.url, "far_z")
local zoom = go.get(camera.url, "zoom")
camera.zoom = zoom
local projector_fn = projectors[projector_id] or projectors[hash("DEFAULT")]
return projector_fn(camera_id, near_z, far_z, zoom)
end

local function calculate_view(camera_id, camera_world_pos, offset)
Expand All @@ -135,17 +138,12 @@ end
--- Initialize a camera
-- Note: This is called automatically from the init() function of the camera.script
-- @param camera_id
-- @param settings Camera settings. Accepted values:
-- * near_z (number)
-- * far_z (number)
-- * projector_id (hash)
function M.init(camera_id, settings)
-- @param camera_script_url
function M.init(camera_id, camera_script_url, settings)
assert(camera_id, "You must provide a camera id")
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
assert(camera_script_url, "You must provide a camera script url")
cameras[camera_id] = settings
cameras[camera_id].url = camera_script_url
cameras[camera_id].view = calculate_view(camera_id, go.get_world_position(camera_id))
cameras[camera_id].projection = calculate_projection(camera_id)
end
Expand Down Expand Up @@ -179,16 +177,22 @@ function M.update(camera_id, dt)

local camera_world_pos = go.get_world_position(camera_id)
local camera_world_to_local_diff = camera_world_pos - go.get_position(camera_id)
if camera.follow then
local target_pos = go.get_position(camera.follow.target)
local target_world_pos = go.get_world_position(camera.follow.target)
local follow_enabled = go.get(camera.url, "follow")
if follow_enabled then
local follow = go.get(camera.url, "follow_target")
local target_pos = go.get_position(follow)
local target_world_pos = go.get_world_position(follow)
local new_pos
if camera.deadzone then
local deadzone_top = go.get(camera.url, "deadzone_top")
local deadzone_left = go.get(camera.url, "deadzone_left")
local deadzone_right = go.get(camera.url, "deadzone_right")
local deadzone_bottom = go.get(camera.url, "deadzone_bottom")
if deadzone_top ~= 0 or deadzone_left ~= 0 or deadzone_right ~= 0 or deadzone_bottom ~= 0 then
new_pos = vmath.vector3(camera_world_pos)
local left_edge = camera_world_pos.x - camera.deadzone.left
local right_edge = camera_world_pos.x + camera.deadzone.right
local top_edge = camera_world_pos.y + camera.deadzone.top
local bottom_edge = camera_world_pos.y - camera.deadzone.bottom
local left_edge = camera_world_pos.x - deadzone_left
local right_edge = camera_world_pos.x + deadzone_right
local top_edge = camera_world_pos.y + deadzone_top
local bottom_edge = camera_world_pos.y - deadzone_bottom
if target_world_pos.x < left_edge then
new_pos.x = new_pos.x - (left_edge - target_world_pos.x)
elseif target_world_pos.x > right_edge then
Expand All @@ -203,19 +207,19 @@ function M.update(camera_id, dt)
new_pos = target_world_pos
end
new_pos.z = camera_world_pos.z
if camera.follow.lerp then
camera_world_pos = vmath.lerp(camera.follow.lerp or 0.1, camera_world_pos, new_pos)
camera_world_pos.z = new_pos.z
else
camera_world_pos = new_pos
end
local follow_lerp = go.get(camera.url, "follow_lerp")
camera_world_pos = vmath.lerp(follow_lerp, camera_world_pos, new_pos)
camera_world_pos.z = new_pos.z
end

if camera.bounds then
local bounds = camera.bounds
local bounds_top = go.get(camera.url, "bounds_top")
local bounds_left = go.get(camera.url, "bounds_left")
local bounds_bottom = go.get(camera.url, "bounds_bottom")
local bounds_right = go.get(camera.url, "bounds_right")
if bounds_top ~= 0 or bounds_left ~= 0 or bounds_bottom ~= 0 or bounds_right ~= 0 then
local cp = M.world_to_screen(camera_id, vmath.vector3(camera_world_pos))
local tr = M.world_to_screen(camera_id, bounds.top_right) - OFFSET
local bl = M.world_to_screen(camera_id, bounds.bottom_left) + OFFSET
local tr = M.world_to_screen(camera_id, vmath.vector3(bounds_right, bounds_top, 0)) - OFFSET
local bl = M.world_to_screen(camera_id, vmath.vector3(bounds_left, bounds_bottom, 0)) + OFFSET

cp.x = math.max(cp.x, bl.x)
cp.x = math.min(cp.x, tr.x)
Expand All @@ -226,6 +230,7 @@ function M.update(camera_id, dt)
end

go.set_position(camera_world_pos + camera_world_to_local_diff, camera_id)


if camera.shake then
camera.shake.duration = camera.shake.duration - dt
Expand Down Expand Up @@ -275,14 +280,15 @@ end
function M.follow(camera_id, target, lerp)
assert(camera_id, "You must provide a camera id")
assert(target, "You must provide a target")
cameras[camera_id].follow = { target = target, lerp = lerp }
msg.post(cameras[camera_id].url, "follow", { target = target, lerp = lerp })
end


--- Unfollow a game object
-- @param camera_id
function M.unfollow(camera_id)
cameras[camera_id].follow = nil
assert(camera_id, "You must provide a camera id")
msg.post(cameras[camera_id].url, "unfollow")
end

--- Set the camera deadzone
Expand All @@ -293,15 +299,11 @@ end
-- @param bottom
function M.deadzone(camera_id, left, top, right, bottom)
assert(camera_id, "You must provide a camera id")
local camera = cameras[camera_id]
if left and right and top and bottom then
cameras[camera_id].deadzone = {
left = left,
right = right,
bottom = bottom,
top = top,
}
msg.post(camera.url, "deadzone", { left = left, top = top, right = right, bottom = bottom })
else
cameras[camera_id].deadzone = nil
msg.post(camera.url, "deadzone")
end
end

Expand All @@ -314,17 +316,11 @@ end
-- @param bottom
function M.bounds(camera_id, left, top, right, bottom)
assert(camera_id, "You must provide a camera id")
local camera = cameras[camera_id]
if left and top and right and bottom then
cameras[camera_id].bounds = {
left = left,
right = right,
bottom = bottom,
top = top,
bottom_left = vmath.vector3(left, bottom, 0),
top_right = vmath.vector3(right, top, 0),
}
msg.post(camera.url, "bounds", { left = left, top = top, right = right, bottom = bottom })
else
cameras[camera_id].bounds = nil
msg.post(camera.url, "bounds")
end
end

Expand All @@ -347,6 +343,7 @@ function M.shake(camera_id, intensity, duration, direction, cb)
}
end


--- Stop shaking a camera
-- @param camera_id
function M.stop_shaking(camera_id)
Expand All @@ -355,14 +352,12 @@ function M.stop_shaking(camera_id)
end



--- Simulate a recoil effect
-- @param camera_id
-- @param offset Amount to offset the camera with
-- @param duration Duration of the recoil. Optional, default: 0.5s.
function M.recoil(camera_id, offset, duration)
assert(camera_id, "You must provide a strength id")
print("recoil", offset, duration)
cameras[camera_id].recoil = {
offset = offset,
duration = duration or 0.5,
Expand All @@ -377,9 +372,7 @@ end
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)
msg.post(cameras[camera_id], "zoom_to", { zoom = zoom })
end


Expand All @@ -392,7 +385,6 @@ function M.get_zoom(camera_id)
end



--- Get the projection matrix for a camera
-- @param camera_id
-- @return Projection matrix
Expand Down
44 changes: 32 additions & 12 deletions orthographic/camera.script
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ go.property("zoom", 1)
go.property("projection", hash("DEFAULT"))
go.property("enabled", true)

go.property("follow", false)
go.property("follow_target", hash(""))
go.property("follow_lerp", 0.5)

go.property("bounds_left", 0)
go.property("bounds_bottom", 0)
go.property("bounds_right", 0)
go.property("bounds_top", 0)
go.property("deadzone_left", 0)
go.property("deadzone_bottom", 0)
go.property("deadzone_right", 0)
go.property("deadzone_top", 0)


local camera = require "orthographic.camera"


Expand All @@ -20,12 +34,7 @@ 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,
})
camera.init(go.get_id(), msg.url(), { zoom = self.zoom })
end


Expand All @@ -36,7 +45,6 @@ end

function update(self, dt)
if self.enabled then

-- update camera and view projection after all game objects have been updated
-- will jitter otherwise
msg.post("#", UPDATE_CAMERA, { dt = dt })
Expand All @@ -53,13 +61,25 @@ function on_message(self, message_id, message, sender)
elseif message_id == DISABLE then
self.enabled = false
elseif message_id == UNFOLLOW then
camera.unfollow(go.get_id())
self.follow = false
elseif message_id == USE_PROJECTION then
assert(message.projection, "You must provide a projection")
self.projection = message.projection
elseif message_id == FOLLOW then
camera.follow(go.get_id(), message.target, message.lerp)
assert(message.target, "You must provide a target")
self.follow = true
self.follow_target = type(message.target) == "string" and hash(message.target) or message.target
self.follow_lerp = message.lerp or 1
elseif message_id == DEADZONE then
camera.deadzone(go.get_id(), message.left, message.top, message.right, message.bottom)
self.deadzone_right = message.right or 0
self.deadzone_top = message.top or 0
self.deadzone_left = message.left or 0
self.deadzone_bottom = message.bottom or 0
elseif message_id == BOUNDS then
camera.bounds(go.get_id(), message.left, message.top, message.right, message.bottom)
self.bounds_right = message.right or 0
self.bounds_top = message.top or 0
self.bounds_left = message.left or 0
self.bounds_bottom = message.bottom or 0
elseif message_id == SHAKE then
camera.shake(go.get_id(), message.intensity, message.duration, message.direction, function()
msg.post(sender, "shake_completed")
Expand All @@ -69,7 +89,7 @@ function on_message(self, message_id, message, sender)
elseif message_id == STOP_SHAKING then
camera.stop_shaking(go.get_id())
elseif message_id == ZOOM_TO then
assert(message.zoom, "You must provide a zoom level")
self.zoom = message.zoom
camera.set_zoom(go.get_id(), message.zoom)
end
end

0 comments on commit e4bb217

Please sign in to comment.