def tick args
defaults args
calc args
render args
end
def defaults args
tile_size = 100
tiles_per_row = 32
number_of_rows = 32
number_of_tiles = tiles_per_row * number_of_rows
# generate map tiles
args.state.tiles ||= number_of_tiles.map_with_index do |i|
row = i.idiv(tiles_per_row)
col = i.mod(tiles_per_row)
{
x: row * tile_size,
y: col * tile_size,
w: tile_size,
h: tile_size,
path: 'sprites/square/blue.png'
}
end
center_map = {
x: tiles_per_row.idiv(2) * tile_size,
y: number_of_rows.idiv(2) * tile_size,
w: 1,
h: 1
}
args.state.center_tile ||= args.state.tiles.find { |o| o.intersect_rect? center_map }
args.state.selected_tile ||= args.state.center_tile
# camera must have the following properties (x, y, and scale)
if !args.state.camera
args.state.camera = {
x: 0,
y: 0,
scale: 1,
target_x: 0,
target_y: 0,
target_scale: 1
}
args.state.camera.target_x = args.state.selected_tile.x + args.state.selected_tile.w.half
args.state.camera.target_y = args.state.selected_tile.y + args.state.selected_tile.h.half
args.state.camera.x = args.state.camera.target_x
args.state.camera.y = args.state.camera.target_y
end
end
def calc args
calc_inputs args
calc_camera args
end
def calc_inputs args
# "i" to zoom in, "o" to zoom out
if args.inputs.keyboard.key_down.i || args.inputs.keyboard.key_down.equal_sign || args.inputs.keyboard.key_down.plus
args.state.camera.target_scale += 0.1 * args.state.camera.scale
elsif args.inputs.keyboard.key_down.o || args.inputs.keyboard.key_down.minus
args.state.camera.target_scale -= 0.1 * args.state.camera.scale
args.state.camera.target_scale = 0.1 if args.state.camera.scale < 0.1
end
# "zero" to reset zoom and camera
if args.inputs.keyboard.key_down.zero
args.state.camera.target_scale = 1
args.state.selected_tile = args.state.center_tile
end
# if mouse is clicked
if args.inputs.mouse.click
# convert the mouse to world space and delete any tiles that intersect with the mouse
rect = Camera.to_world_space args.state.camera, args.inputs.mouse
selected_tile = args.state.tiles.find { |o| rect.intersect_rect? o }
if selected_tile
args.state.selected_tile = selected_tile
args.state.camera.target_scale = 1
end
end
# "r" to reset
if args.inputs.keyboard.key_down.r
$gtk.reset_next_tick
end
end
def calc_camera args
args.state.camera.target_x = args.state.selected_tile.x + args.state.selected_tile.w.half
args.state.camera.target_y = args.state.selected_tile.y + args.state.selected_tile.h.half
dx = args.state.camera.target_x - args.state.camera.x
dy = args.state.camera.target_y - args.state.camera.y
ds = args.state.camera.target_scale - args.state.camera.scale
args.state.camera.x += dx * 0.1 * args.state.camera.scale
args.state.camera.y += dy * 0.1 * args.state.camera.scale
args.state.camera.scale += ds * 0.1
end
def render args
args.outputs.background_color = [0, 0, 0]
# define scene
args.outputs[:scene].transient!
args.outputs[:scene].w = Camera::WORLD_SIZE
args.outputs[:scene].h = Camera::WORLD_SIZE
args.outputs[:scene].background_color = [0, 0, 0, 0]
# render diagonals and background of scene
args.outputs[:scene].lines << { x: 0, y: 0, x2: 1500, y2: 1500, r: 0, g: 0, b: 0, a: 255 }
args.outputs[:scene].lines << { x: 0, y: 1500, x2: 1500, y2: 0, r: 0, g: 0, b: 0, a: 255 }
args.outputs[:scene].solids << { x: 0, y: 0, w: 1500, h: 1500, a: 128 }
# find all tiles to render
objects_to_render = Camera.find_all_intersect_viewport args.state.camera, args.state.tiles
# convert mouse to world space to see if it intersects with any tiles (hover color)
mouse_in_world = Camera.to_world_space args.state.camera, args.inputs.mouse
# for tiles that were found, convert the rect to screen coordinates and place them in scene
args.outputs[:scene].sprites << objects_to_render.map do |o|
if o == args.state.selected_tile
tile_to_render = o.merge path: 'sprites/square/green.png'
elsif o.intersect_rect? mouse_in_world
tile_to_render = o.merge path: 'sprites/square/orange.png'
else
tile_to_render = o.merge path: 'sprites/square/blue.png'
end
Camera.to_screen_space args.state.camera, tile_to_render
end
# render scene to screen
args.outputs.sprites << { **Camera.viewport, path: :scene }
# render instructions
args.outputs.sprites << { x: 0, y: 110.from_top, w: 1280, h: 110, path: :pixel, r: 0, g: 0, b: 0, a: 200 }
label_style = { r: 255, g: 255, b: 255, anchor_y: 0.5 }
args.outputs.labels << { x: 30, y: 30.from_top, text: "I/O or +/- keys to zoom in and zoom out (0 to reset camera, R to reset everything).", **label_style }
args.outputs.labels << { x: 30, y: 60.from_top, text: "Click to center on square.", **label_style }
args.outputs.labels << { x: 30, y: 90.from_top, text: "Mouse location in world: #{(Camera.to_world_space args.state.camera, args.inputs.mouse).to_sf}", **label_style }
end
# helper methods to create a camera and go to and from screen space and world space
class Camera
SCREEN_WIDTH = 1280
SCREEN_HEIGHT = 720
WORLD_SIZE = 1500
WORLD_SIZE_HALF = WORLD_SIZE / 2
OFFSET_X = (SCREEN_WIDTH - WORLD_SIZE) / 2
OFFSET_Y = (SCREEN_HEIGHT - WORLD_SIZE) / 2
class << self
# given a rect in screen space, converts the rect to world space
def to_world_space camera, rect
rect_x = rect.x
rect_y = rect.y
rect_w = rect.w || 0
rect_h = rect.h || 0
x = (rect_x - WORLD_SIZE_HALF + camera.x * camera.scale - OFFSET_X) / camera.scale
y = (rect_y - WORLD_SIZE_HALF + camera.y * camera.scale - OFFSET_Y) / camera.scale
w = rect_w / camera.scale
h = rect_h / camera.scale
rect.merge x: x, y: y, w: w, h: h
end
# given a rect in world space, converts the rect to screen space
def to_screen_space camera, rect
rect_x = rect.x
rect_y = rect.y
rect_w = rect.w || 0
rect_h = rect.h || 0
x = rect_x * camera.scale - camera.x * camera.scale + WORLD_SIZE_HALF
y = rect_y * camera.scale - camera.y * camera.scale + WORLD_SIZE_HALF
w = rect_w * camera.scale
h = rect_h * camera.scale
rect.merge x: x, y: y, w: w, h: h
end
# viewport of the scene
def viewport
{
x: OFFSET_X,
y: OFFSET_Y,
w: WORLD_SIZE,
h: WORLD_SIZE
}
end
# viewport in the context of the world
def viewport_world camera
to_world_space camera, viewport
end
# helper method to find objects within viewport
def find_all_intersect_viewport camera, os
Geometry.find_all_intersect_rect viewport_world(camera), os
end
end
end
$gtk.reset