Browse Source

back to Astar

master
Nicholas Hayashi 4 years ago
parent
commit
9c04dbb73d
  1. BIN
      res/077-8332_001.jpg
  2. BIN
      res/arrow.png
  3. BIN
      res/marquis.png
  4. BIN
      res/tower1.png
  5. BIN
      res/tower2.png
  6. 5
      src/color.lua
  7. 2
      src/extra.lua
  8. 41
      src/grid.lua
  9. 6
      src/gui.lua
  10. 114
      src/hexyz.lua
  11. 0
      src/log.txt
  12. 84
      src/main.lua
  13. 82
      src/mob.lua
  14. 9
      src/texture.lua
  15. 38
      src/tower.lua

BIN
res/077-8332_001.jpg

After

Width: 2000  |  Height: 2000  |  Size: 716 KiB

BIN
res/arrow.png

After

Width: 46  |  Height: 58  |  Size: 518 B

BIN
res/marquis.png

After

Width: 306  |  Height: 40  |  Size: 452 B

BIN
res/tower1.png

After

Width: 137  |  Height: 137  |  Size: 2.5 KiB

BIN
res/tower2.png

After

Width: 32  |  Height: 32  |  Size: 487 B

5
src/color.lua

@ -1,7 +1,7 @@
COLORS = { COLORS = {
TRANSPARENT = vec4(0.4),
TRANSPARENT = vec4(0.6),
-- tones -- tones
WHITE = vec4(0.8, 0.8, 0.7, 1), WHITE = vec4(0.8, 0.8, 0.7, 1),
@ -21,6 +21,7 @@ COLORS = {
PALE_SILVER = vec4(193/255, 178/255, 171/255, 1), PALE_SILVER = vec4(193/255, 178/255, 171/255, 1),
CLARET = vec4(139/255, 30/255, 63/255, 1), CLARET = vec4(139/255, 30/255, 63/255, 1),
BISTRO = vec4(73/255, 44/255, 29/255, 1), BISTRO = vec4(73/255, 44/255, 29/255, 1),
DEEP_SPACE_SPARKLE = vec4(61/255, 90/255, 108/255, 1)
DEEP_SPACE_SPARKLE = vec4(61/255, 90/255, 108/255, 1),
WHEAT = vec4(225/255, 202/255, 150/255, 1)
} }

2
src/extra.lua

@ -4,7 +4,7 @@ function math.wrapf(float, range)
return float - range * math.floor(float / range) return float - range * math.floor(float / range)
end end
function math.lerpv2(v1, v2, t)
function math.lerp(v1, v2, t)
return v1 * t + v2 * (1 - t) return v1 * t + v2 * (1 - t)
end end

41
src/grid.lua

@ -1,13 +1,15 @@
require "gui" require "gui"
require "hexyz" require "hexyz"
HEX_SIZE = 20 HEX_SIZE = 20
HEX_GRID_WIDTH = 65 -- 65
HEX_GRID_HEIGHT = 33 -- 33
HEX_GRID_DIMENSIONS = vec2(HEX_GRID_WIDTH, HEX_GRID_HEIGHT)
-- with 1920x1080, this is the minimal dimensions to cover the screen (65x33)
-- odd numbers are important because we want a 'true' center
HEX_GRID_WIDTH = 65
HEX_GRID_HEIGHT = 33
HEX_GRID_DIMENSIONS = vec2(HEX_GRID_WIDTH, HEX_GRID_HEIGHT)
-- leaving y == 0 makes this the center in hex coordinates -- leaving y == 0 makes this the center in hex coordinates
HEX_GRID_CENTER = vec2(math.floor(HEX_GRID_DIMENSIONS.x/2), 0) HEX_GRID_CENTER = vec2(math.floor(HEX_GRID_DIMENSIONS.x/2), 0)
@ -16,20 +18,19 @@ HEX_GRID_CENTER = vec2(math.floor(HEX_GRID_DIMENSIONS.x/2), 0)
-- { { elevation, node, etc. } } -- { { elevation, node, etc. } }
HEX_MAP = {} HEX_MAP = {}
function grid_pixel_dimensions()
local function grid_pixel_dimensions()
local hhs = hex_horizontal_spacing(HEX_SIZE) local hhs = hex_horizontal_spacing(HEX_SIZE)
local hvs = hex_vertical_spacing(HEX_SIZE) local hvs = hex_vertical_spacing(HEX_SIZE)
-- number of 'spacings' on the grid == number of cells - 1 -- number of 'spacings' on the grid == number of cells - 1
return vec2((HEX_GRID_DIMENSIONS.x - 1) * hhs
, (HEX_GRID_DIMENSIONS.y - 1) * hvs)
return vec2((HEX_GRID_WIDTH - 1) * hhs
, (HEX_GRID_HEIGHT - 1) * hvs)
end end
GRID_PIXEL_DIMENSIONS = grid_pixel_dimensions() GRID_PIXEL_DIMENSIONS = grid_pixel_dimensions()
WORLDSPACE_COORDINATE_OFFSET = -GRID_PIXEL_DIMENSIONS/2
HEX_GRID_INTERACTABLE_REGION_PADDING = 2
HEX_GRID_INTERACTABLE_REGION_PADDING = 2
function is_interactable(tile, evenq) function is_interactable(tile, evenq)
return point_in_rect(evenq, { return point_in_rect(evenq, {
x1 = HEX_GRID_INTERACTABLE_REGION_PADDING, x1 = HEX_GRID_INTERACTABLE_REGION_PADDING,
@ -39,28 +40,37 @@ function is_interactable(tile, evenq)
}) })
end end
function is_passable(tile, mob)
return tile.elevation > -0.5 and tile.elevation < 0.5
end
-- map elevation to appropriate tile color. -- map elevation to appropriate tile color.
function color_at(elevation) function color_at(elevation)
if elevation < -0.5 then -- lowest elevation : impassable if elevation < -0.5 then -- lowest elevation : impassable
return COLORS.BLUE_STONE{ a = (elevation + 1.4) / 2 + 0.2 } return COLORS.BLUE_STONE{ a = (elevation + 1.4) / 2 + 0.2 }
elseif elevation < 0 then -- med-low elevation : passable elseif elevation < 0 then -- med-low elevation : passable
return COLORS.MYRTLE{ a = (elevation + 1.8) / 2 + 0.2 }
return math.lerp(COLORS.MYRTLE, COLORS.BROWN_POD, elevation + 0.5){ a = (elevation + 1.8) / 2 + 0.2 }
elseif elevation < 0.5 then -- med-high elevation : passable elseif elevation < 0.5 then -- med-high elevation : passable
return COLORS.BROWN_POD{ a = (elevation + 1.6) / 2 + 0.2 }
return math.lerp(COLORS.MYRTLE, COLORS.BROWN_POD, elevation + 0.5){ a = (elevation + 1.6) / 2 + 0.2 }
elseif elevation < 1 then -- highest elevation : impassable elseif elevation < 1 then -- highest elevation : impassable
return COLORS.BOTTLE_GREEN{ a = (elevation + 1.0) / 2 + 0.2 } return COLORS.BOTTLE_GREEN{ a = (elevation + 1.0) / 2 + 0.2 }
else else
log('bad elevation')
return vec4(0)
log('bad elevation'); return vec4(0)
end end
end end
function grid_neighbours(map, hex)
return table.filter(hex_neighbours(hex), function(_hex)
return map.get(_hex.x, _hex.y)
end)
end
function random_map(seed) function random_map(seed)
local map = rectangular_map(HEX_GRID_DIMENSIONS.x, HEX_GRID_DIMENSIONS.y)
local map = rectangular_map(HEX_GRID_DIMENSIONS.x, HEX_GRID_DIMENSIONS.y, seed)
math.randomseed(map.seed) math.randomseed(map.seed)
local world = am.group():tag"world" local world = am.group():tag"world"
@ -90,9 +100,10 @@ function random_map(seed)
for _,hex in pairs(home) do for _,hex in pairs(home) do
map[hex.x][hex.y].elevation = 0 map[hex.x][hex.y].elevation = 0
map[hex.x][hex.y].node.color = color_at(0) map[hex.x][hex.y].node.color = color_at(0)
world:append(am.circle(hex_to_pixel(vec2(hex.x, hex.y)), HEX_SIZE/2, COLORS.MAGENTA, 4))
end end
world:append(am.circle(hex_to_pixel(HEX_GRID_CENTER), HEX_SIZE/2, COLORS.MAGENTA, 4))
WORLDSPACE_COORDINATE_OFFSET = -GRID_PIXEL_DIMENSIONS/2
return map, am.translate(WORLDSPACE_COORDINATE_OFFSET) return map, am.translate(WORLDSPACE_COORDINATE_OFFSET)
^ world:tag"world" ^ world:tag"world"
end end

6
src/gui.lua

@ -21,15 +21,15 @@ function register_button_widget(id, rect)
local click = false local click = false
if active and active.id == id then if active and active.id == id then
if win:mouse_released"left" then
if WIN:mouse_released"left" then
if hot and hot.id == id then click = true end if hot and hot.id == id then click = true end
active = false active = false
end end
elseif hot and hot.id == id then elseif hot and hot.id == id then
if win:mouse_pressed"left" then active = { id = id } end
if WIN:mouse_pressed"left" then active = { id = id } end
end end
if point_in_rect(win:mouse_position(), rect) then set_hot(id) end
if point_in_rect(WIN:mouse_position(), rect) then set_hot(id) end
return click return click
end) end)

114
src/hexyz.lua

@ -6,8 +6,6 @@ else
log("clobbering a math.round function.") log("clobbering a math.round function.")
end end
--============================================================================
-- HEX CONSTANTS AND UTILITY FUNCTIONS
-- wherever 'orientation' appears as an argument, use one of these two, or set a default just below -- wherever 'orientation' appears as an argument, use one of these two, or set a default just below
ORIENTATION = { ORIENTATION = {
@ -261,6 +259,14 @@ local function map_set(t, x, y, v)
return t return t
end end
local function map_traverse(t, callback)
for i,_ in pairs(t) do
for _,entry in pairs(t[i]) do
callback(entry)
end
end
end
-- @NOTE probably shouldn't use this... -- @NOTE probably shouldn't use this...
local function map_partial_set(t, x, y, k, v) local function map_partial_set(t, x, y, k, v)
local entry = map_get(t, x, y) local entry = map_get(t, x, y)
@ -304,7 +310,8 @@ function parallelogram_map(width, height, seed)
seed = seed, seed = seed,
get = function(x, y) return map_get(map, x, y) end, get = function(x, y) return map_get(map, x, y) end,
set = function(x, y, v) return map_set(map, x, y, v) end, set = function(x, y, v) return map_set(map, x, y, v) end,
partial = function(x, y, k, v) return map_partial_set(map, x, y, k, v) end
partial = function(x, y, k, v) return map_partial_set(map, x, y, k, v) end,
traverse = function(callback) return map_traverse(map, callback) end
}}) }})
end end
@ -336,7 +343,8 @@ function triangular_map(size, seed)
seed = seed, seed = seed,
get = function(x, y) return map_get(map, x, y) end, get = function(x, y) return map_get(map, x, y) end,
set = function(x, y, v) return map_set(map, x, y, v) end, set = function(x, y, v) return map_set(map, x, y, v) end,
partial = function(x, y, k, v) return map_partial_set(map, x, y, k, v) end
partial = function(x, y, k, v) return map_partial_set(map, x, y, k, v) end,
traverse = function(callback) return map_traverse(map, callback) end
}}) }})
end end
@ -373,7 +381,8 @@ function hexagonal_map(radius, seed)
seed = seed, seed = seed,
get = function(x, y) return map_get(map, x, y) end, get = function(x, y) return map_get(map, x, y) end,
set = function(x, y, v) return map_set(map, x, y, v) end, set = function(x, y, v) return map_set(map, x, y, v) end,
partial = function(x, y, k, v) return map_partial_set(map, x, y, k, v) end
partial = function(x, y, k, v) return map_partial_set(map, x, y, k, v) end,
traverse = function(callback) return map_traverse(map, callback) end
}}) }})
end end
@ -408,51 +417,89 @@ function rectangular_map(width, height, seed)
seed = seed, seed = seed,
get = function(x, y) return map_get(map, x, y) end, get = function(x, y) return map_get(map, x, y) end,
set = function(x, y, v) return map_set(map, x, y, v) end, set = function(x, y, v) return map_set(map, x, y, v) end,
partial = function(x, y, k, v) return map_partial_set(map, x, y, k, v) end
partial = function(x, y, k, v) return map_partial_set(map, x, y, k, v) end,
traverse = function(callback) return map_traverse(map, callback) end
}}) }})
end end
--============================================================================ --============================================================================
-- PATHFINDING -- PATHFINDING
-- @TODO @FIXME
function breadth_first(map, target, neighbour_f)
local neighbour_f = neighbour_f or hex_neighbours
local frontier = { target }
-- maps each hex of |map| to a numeric value, which is its distance from |start|
-- returns this new map
function breadth_first_distance(map, start, neighbour_f)
local neighbour_f = neighbour_f or function(map, hex) return hex_neighbours(hex) end
local frontier = { start }
local reached = {}
reached[target.x] = {}
reached[target.x][target.y] = true
local distance = {}
distance[start.x] = {}
distance[start.x][start.y] = 0
while not #frontier == 0 do
while not (#frontier == 0) do
local current = table.remove(frontier, 1) local current = table.remove(frontier, 1)
for _,next_ in pairs(neighbour_f(current)) do
for _,next_ in pairs(neighbour_f(map, current)) do
if not map_get(distance, next_.x, next_.y) then
table.insert(frontier, next_)
map_set(distance, next_.x, next_.y, 1 + map_get(distance, current.x, current.y))
end
end
end
return setmetatable(distance, { __index = {
get = function(x, y) return map_get(distance, x, y) end
}})
end
-- maps each hex of |map| to an adjacent cell that is on the path to |start|, or nil if no path exists
-- returns this new map
function breadth_first_vectorfield(map, start, neighbour_f)
local neighbour_f = neighbour_f or function(map, hex) return hex_neighbours(hex) end
local frontier = { start }
local came_from = {}
came_from[start.x] = {}
came_from[start.x][start.y] = false
while not (#frontier == 0) do
local current = table.remove(frontier, 1)
for _,next_ in pairs(neighbour_f(map, current)) do
if not map_get(came_from, next_.x, next_.y) then
table.insert(frontier, next_)
map_set(came_from, next_.x, next_.y, current)
end
end end
end end
came_from.get = function(x, y) return map_get(came_from, x, y) end
return came_from
end end
-- generic A* pathfinding -- generic A* pathfinding
-- @NOTE is it better to move the functions to be members of the map?
-- --
-- returns a map that has map[hex.x][hex.y] = 'the vec2 which is the hex you came from when trying to get to |goal|'
-- returns a map that has map[hex.x][hex.y] = { hex = vec2, priority = number },
-- where the hex is the spot it thinks you should go to from the indexed hex, and priority is the cost of that decision,
-- as well as 'made_it' a bool that tells you if we were successful in reaching |goal|
function Astar(map, start, goal, neighbour_f, heuristic_f, cost_f) function Astar(map, start, goal, neighbour_f, heuristic_f, cost_f)
local neighbour_f = neighbour_f or hex_neighbours
local neighbour_f = neighbour_f or function(map, hex) return hex_neighbours(hex) end
local heuristic_f = heuristic_f or math.distance local heuristic_f = heuristic_f or math.distance
local cost_f = cost_f or function() return 1 end
local cost_f = cost_f or function(from, to) return 1 end
local came_from = {}
came_from[start.x] = {}
came_from[start.x][start.y] = false
local path = {}
path[start.x] = {}
path[start.x][start.y] = false
local frontier = {} local frontier = {}
frontier[1] = { hex = start, priority = 0 } frontier[1] = { hex = start, priority = 0 }
local cost_so_far = {}
cost_so_far[start.x] = {}
cost_so_far[start.x][start.y] = 0
local path_so_far = {}
path_so_far[start.x] = {}
path_so_far[start.x][start.y] = 0
local made_it = false local made_it = false
while not (#frontier == 0) do while not (#frontier == 0) do
@ -463,24 +510,19 @@ function Astar(map, start, goal, neighbour_f, heuristic_f, cost_f)
break break
end end
for _,next_ in pairs(neighbour_f(current.hex)) do
local new_cost = map_get(cost_so_far, current.hex.x, current.hex.y)
+ cost_f(next_)
local next_cost = map_get(cost_so_far, next_.x, next_.y)
for _,next_ in pairs(neighbour_f(map, current.hex)) do
local new_cost = map_get(path_so_far, current.hex.x, current.hex.y) + cost_f(current, next_)
local next_cost = map_get(path_so_far, next_.x, next_.y)
if not next_cost or new_cost < next_cost then if not next_cost or new_cost < next_cost then
map_set(cost_so_far, next_.x, next_.y, new_cost)
map_set(path_so_far, next_.x, next_.y, new_cost)
local priority = new_cost + heuristic_f(goal, next_) local priority = new_cost + heuristic_f(goal, next_)
table.insert(frontier, { hex = next_, priority = priority }) table.insert(frontier, { hex = next_, priority = priority })
map_set(came_from, next_.x, next_.y, current)
map_set(path, next_.x, next_.y, current)
end end
end end
end end
if not made_it then
log(" we didn't make it!")
end
return came_from, made_it
return path, made_it
end end

0
src/log.txt

84
src/main.lua

@ -1,20 +1,21 @@
math.randomseed(os.time()); math.random(); math.random(); math.random()
--============================================================================
-- Imports
math.randomseed(os.time())
math.random()
math.random()
math.random()
require "color" require "color"
require "grid" require "grid"
require "mob" require "mob"
require "tower" require "tower"
--============================================================================
-- Globals -- Globals
win = am.window{ width = 1920, height = 1080 }
WIN = am.window{ width = 1920, height = 1080 }
TIME = 0 TIME = 0
SCORE = 0 SCORE = 0
OFF_SCREEN = vec2(WIN.width * 2) -- random pixel position that is garunteed to be off screen
local COORDINATE_DISPLAY_TYPES = { local COORDINATE_DISPLAY_TYPES = {
CENTERED_EVENQ = 0, CENTERED_EVENQ = 0,
@ -23,61 +24,85 @@ local COORDINATE_DISPLAY_TYPES = {
} }
local COORDINATE_DISPLAY_TYPE = COORDINATE_DISPLAY_TYPES.CENTERED_EVENQ local COORDINATE_DISPLAY_TYPE = COORDINATE_DISPLAY_TYPES.CENTERED_EVENQ
function game_action(scene)
local function game_action(scene)
TIME = am.current_time() TIME = am.current_time()
SCORE = TIME SCORE = TIME
local mouse = win:mouse_position()
local mouse = WIN:mouse_position()
local hex = pixel_to_hex(mouse - WORLDSPACE_COORDINATE_OFFSET) local hex = pixel_to_hex(mouse - WORLDSPACE_COORDINATE_OFFSET)
local rounded_mouse = hex_to_pixel(hex) + WORLDSPACE_COORDINATE_OFFSET
local evenq = hex_to_evenq(hex) local evenq = hex_to_evenq(hex)
local centered_evenq = evenq{ y = -evenq.y } - vec2(math.floor(HEX_GRID_WIDTH/2) local centered_evenq = evenq{ y = -evenq.y } - vec2(math.floor(HEX_GRID_WIDTH/2)
, math.floor(HEX_GRID_HEIGHT/2)) , math.floor(HEX_GRID_HEIGHT/2))
local tile = HEX_MAP.get(hex.x, hex.y) local tile = HEX_MAP.get(hex.x, hex.y)
local hot = is_interactable(tile, evenq{ y = -evenq.y })
if win:mouse_pressed"left" then
if WIN:mouse_pressed"left" then
if hot and is_buildable(hex, tile, nil) then
make_tower(hex)
end
end end
if win:key_pressed"f3" then
if WIN:key_pressed"f3" then
COORDINATE_DISPLAY_TYPE = (COORDINATE_DISPLAY_TYPE + 1) % #table.keys(COORDINATE_DISPLAY_TYPES) COORDINATE_DISPLAY_TYPE = (COORDINATE_DISPLAY_TYPE + 1) % #table.keys(COORDINATE_DISPLAY_TYPES)
end end
do_tower_updates()
do_mob_updates() do_mob_updates()
do_mob_spawning() do_mob_spawning()
if tile and is_interactable(tile, evenq{ y = -evenq.y }) then
win.scene"hex_cursor".center = hex_to_pixel(hex) + WORLDSPACE_COORDINATE_OFFSET
if tile and hot then
WIN.scene"hex_cursor".center = rounded_mouse
else else
win.scene"hex_cursor".center = vec2(6969)
WIN.scene"hex_cursor".center = OFF_SCREEN
end end
win.scene"score".text = string.format("SCORE: %.2f", SCORE)
WIN.scene"score".text = string.format("SCORE: %.2f", SCORE)
do do
local str, coords local str, coords
if COORDINATE_DISPLAY_TYPE == COORDINATE_DISPLAY_TYPES.CENTERED_EVENQ then if COORDINATE_DISPLAY_TYPE == COORDINATE_DISPLAY_TYPES.CENTERED_EVENQ then
str, coords = "evenqc: ", centered_evenq
str, coords = "evenqc", centered_evenq
elseif COORDINATE_DISPLAY_TYPE == COORDINATE_DISPLAY_TYPES.EVENQ then elseif COORDINATE_DISPLAY_TYPE == COORDINATE_DISPLAY_TYPES.EVENQ then
str, coords = "evenq: ", evenq
str, coords = "evenq", evenq
elseif COORDINATE_DISPLAY_TYPE == COORDINATE_DISPLAY_TYPES.HEX then elseif COORDINATE_DISPLAY_TYPE == COORDINATE_DISPLAY_TYPES.HEX then
str, coords = "hex: ", hex
str, coords = "hex", hex
end end
win.scene"coords".text = string.format("%s%d,%d", str, coords.x, coords.y)
WIN.scene"coords".text = string.format("%d,%d (%s)", coords.x, coords.y, str)
end end
end end
function game_scene()
local score = am.translate(win.left + 10, win.top - 20) ^ am.text("", "left"):tag"score"
local coords = am.translate(win.right - 10, win.top - 20) ^ am.text("", "right"):tag"coords"
local hex_cursor = am.circle(vec2(-6969), HEX_SIZE, COLORS.TRANSPARENT, 6):tag"hex_cursor"
local function toolbelt()
local toolbelt_height = hex_height(HEX_SIZE) * 2
local toolbelt = am.group{
am.rect(WIN.left, WIN.bottom, WIN.right, WIN.bottom + toolbelt_height, COLORS.TRANSPARENT)
}
--[[
local padding = 22
local size = toolbelt_height - padding
for i = 0, 0 do
toolbelt:append(
am.translate(vec2(size + padding, 0) * i + vec2(WIN.left + padding/3, WIN.bottom + padding/3))
^ am.rect(0, 0, size, size, COLORS.BLACK)
)
end
]]
return toolbelt
end
local function game_scene()
local score = am.translate(WIN.left + 10, WIN.top - 20) ^ am.text("", "left"):tag"score"
local coords = am.translate(WIN.right - 10, WIN.top - 20) ^ am.text("", "right"):tag"coords"
local hex_cursor = am.circle(OFF_SCREEN, HEX_SIZE, COLORS.TRANSPARENT, 6):tag"hex_cursor"
local curtain = am.rect(win.left, win.bottom, win.right, win.top, COLORS.TRUEBLACK)
local curtain = am.rect(WIN.left, WIN.bottom, WIN.right, WIN.top, COLORS.TRUE_BLACK)
curtain:action(coroutine.create(function() curtain:action(coroutine.create(function()
am.wait(am.tween(curtain, 3, { color = vec4(0) }, am.ease.out(am.ease.hyperbola))) am.wait(am.tween(curtain, 3, { color = vec4(0) }, am.ease.out(am.ease.hyperbola)))
win.scene:remove(curtain)
WIN.scene:remove(curtain)
end)) end))
local world local world
@ -87,8 +112,9 @@ function game_scene()
world, world,
curtain, curtain,
hex_cursor, hex_cursor,
toolbelt(),
score, score,
coords
coords,
} }
scene:action(game_action) scene:action(game_action)
@ -96,10 +122,10 @@ function game_scene()
return scene return scene
end end
function init()
local function init()
require "texture" require "texture"
load_textures() load_textures()
win.scene = am.scale(1) ^ game_scene()
WIN.scene = am.scale(1) ^ game_scene()
end end
init() init()

82
src/mob.lua

@ -1,36 +1,44 @@
MOBS = {}
local MOBS = {}
--[[ --[[
mob structure: mob structure:
{ {
TOB - float -- time stamp in seconds of when the mob when spawned
TOB - float -- time stamp in seconds of when the tower was spawned
hex - vec2 -- hexagon the mob is on hex - vec2 -- hexagon the mob is on
position - vec2 -- true pixel coordinates position - vec2 -- true pixel coordinates
node - node -- the root graph node for this mob node - node -- the root graph node for this mob
update - function -- function that gets called every frame with itself as an argument update - function -- function that gets called every frame with itself as an argument
path - 2d table -- map of hexes to other hexes, forms a path
speed - number -- multiplier on distance travelled per frame, up to the update function to use correctly
} }
]] ]]
require "extra" require "extra"
require "sound" require "sound"
MOB_UPDATES = {
local MOB_UPDATES = {
BEEPER = function(mob, index) BEEPER = function(mob, index)
mob.hex = pixel_to_hex(mob.position) mob.hex = pixel_to_hex(mob.position)
local frame_target = mob.path[mob.hex.x] and mob.path[mob.hex.x][mob.hex.y] local frame_target = mob.path[mob.hex.x] and mob.path[mob.hex.x][mob.hex.y]
if frame_target then if frame_target then
mob.position = math.lerpv2(mob.position, hex_to_pixel(frame_target.hex), 0.91)
mob.position = mob.position + math.normalize(hex_to_pixel(frame_target.hex) - mob.position) * mob.speed
mob.node.position2d = mob.position mob.node.position2d = mob.position
else -- can't find path, or dead
win.scene:action(am.play(am.sfxr_synth(SOUNDS.EXPLOSION1), false, math.random() + 0.5))
else
if mob.hex == HEX_GRID_CENTER then
WIN.scene"world":action(
am.play(am.sfxr_synth(SOUNDS.EXPLOSION1), false, math.random() + 0.5)
)
local i,v = table.find(MOBS, function(_mob) return _mob == mob end)
table.remove(MOBS, index) table.remove(MOBS, index)
win.scene"world":remove(mob.node)
WIN.scene"world":remove(mob.node)
else
log("stuck")
end
end end
-- passive animation -- passive animation
@ -43,13 +51,32 @@ MOB_UPDATES = {
} }
-- check if a the tile at |hex| is passable by |mob| -- check if a the tile at |hex| is passable by |mob|
function can_pass_through(mob, hex)
local function mob_can_pass_through(mob, hex)
local tile = HEX_MAP.get(hex.x, hex.y) local tile = HEX_MAP.get(hex.x, hex.y)
return tile and tile.elevation < 0.5 and tile.elevation > -0.5 return tile and tile.elevation < 0.5 and tile.elevation > -0.5
end end
local function get_mob_path(map, start, goal, mob)
return Astar(map, goal, start, -- goal and start are switched intentionally
-- neighbour function
function(map, hex)
return table.filter(grid_neighbours(map, hex), function(_hex)
return mob_can_pass_through(mob, _hex)
end)
end,
-- heuristic function
math.distance,
-- cost function
function(from, to)
return math.abs(map.get(to.x, to.y).elevation)
end
)
end
-- @FIXME there's a bug here where the position of the spawn hex is sometimes 1 closer to the center than we want -- @FIXME there's a bug here where the position of the spawn hex is sometimes 1 closer to the center than we want
function get_spawn_hex(mob)
local function get_spawn_hex(mob)
local spawn_hex local spawn_hex
repeat repeat
-- ensure we spawn on an random tile along the map's edges -- ensure we spawn on an random tile along the map's edges
@ -73,51 +100,40 @@ function get_spawn_hex(mob)
spawn_hex = evenq_to_hex(vec2(x, -y)) spawn_hex = evenq_to_hex(vec2(x, -y))
local tile = HEX_MAP[spawn_hex.x][spawn_hex.y] local tile = HEX_MAP[spawn_hex.x][spawn_hex.y]
until can_pass_through(mob, spawn_hex)
until mob_can_pass_through(mob, spawn_hex)
return spawn_hex return spawn_hex
end end
--
function make_mob()
local function make_mob()
local mob = {} local mob = {}
mob.TOB = TIME mob.TOB = TIME
mob.update = MOB_UPDATES.BEEPER mob.update = MOB_UPDATES.BEEPER
mob.hex = get_spawn_hex(mob) mob.hex = get_spawn_hex(mob)
mob.position = hex_to_pixel(mob.hex) mob.position = hex_to_pixel(mob.hex)
mob.path = Astar(HEX_MAP, HEX_GRID_CENTER, mob.hex,
-- neighbour function
function(hex)
return table.filter(hex_neighbours(hex), function(_hex)
return can_pass_through(mob, _hex)
end)
end,
-- heuristic function
function(source, target)
return math.distance(source, target)
end,
-- cost function
function(hex)
return math.abs(HEX_MAP.get(hex.x, hex.y).elevation)
end
)
mob.path = get_mob_path(HEX_MAP, mob.hex, HEX_GRID_CENTER, mob)
mob.speed = 10
mob.node = am.translate(mob.position) mob.node = am.translate(mob.position)
^ am.scale(2) ^ am.scale(2)
^ am.rotate(mob.TOB) ^ am.rotate(mob.TOB)
^ pack_texture_into_sprite(TEX_MOB1_1, 20, 20) ^ pack_texture_into_sprite(TEX_MOB1_1, 20, 20)
win.scene"world":append(mob.node)
WIN.scene"world":append(mob.node)
return mob return mob
end end
function mob_on_hex(hex)
return table.find(MOBS, function(mob)
return mob.hex == hex
end)
end
local SPAWN_CHANCE = 50 local SPAWN_CHANCE = 50
function do_mob_spawning() function do_mob_spawning()
--if WIN:key_pressed"space" then
if math.random(SPAWN_CHANCE) == 1 then if math.random(SPAWN_CHANCE) == 1 then
table.insert(MOBS, make_mob()) table.insert(MOBS, make_mob())
end end

9
src/texture.lua

@ -1,5 +1,12 @@
function load_textures() function load_textures()
TEX_MARQUIS = am.texture2d("../res/marquis.png")
TEX_ARROW = am.texture2d("../res/arrow.png")
TEX_WALL_CLOSED = am.texture2d("../res/wall_closed.png")
TEX_TOWER1 = am.texture2d("../res/tower1.png")
TEX_MOB1_1 = am.texture2d("../res/mob1_1.png") TEX_MOB1_1 = am.texture2d("../res/mob1_1.png")
end end
@ -12,5 +19,3 @@ function pack_texture_into_sprite(texture, width, height)
} }
end end

38
src/tower.lua

@ -1,15 +1,43 @@
TOWERS = {}
local TOWERS = {}
--[[
tower structure:
{
TOB - float -- time stamp in seconds of when the tower was spawned
hex - vec2 -- hexagon the tower is on
position - vec2 -- true pixel coordinates
node - node -- the root graph node for this tower
update - function -- function that gets called every frame with itself as an argument
}
]]
function is_buildable(hex, tile, tower)
local blocked = mob_on_hex(hex)
return not blocked and is_passable(tile)
end
function is_buildable(tile, tower)
function make_tower(hex)
local tower = {}
end
tower.TOB = TIME
tower.hex = hex
tower.position = hex_to_pixel(tower.hex)
tower.node = am.translate(tower.position)
^ pack_texture_into_sprite(TEX_TOWER1, 55, 55)
tower.update = function(_tower) end
function make_tower()
-- make this cell impassable
--HEX_MAP.get(hex.x, hex.y).elevation = 2
WIN.scene"world":append(tower.node)
return tower
end end
function do_tower_updates() function do_tower_updates()
for i,tower in pairs(TOWERS) do
tower.update(tower, i)
end
end end
Loading…
Cancel
Save