Browse Source

fix spawn

master
Nicholas Hayashi 4 years ago
parent
commit
97b1983119
  1. 2
      README.md
  2. BIN
      res/wall_closed.png
  3. BIN
      res/wall_open.png
  4. 6
      src/colors.lua
  5. 99
      src/grid.lua
  6. 54
      src/gui.lua
  7. 51
      src/hexyz.lua
  8. 155
      src/main.lua
  9. 131
      src/mob.lua
  10. 14
      src/util.lua

2
README.md

@ -1,6 +1,8 @@
## INTRODUCTION ## INTRODUCTION
AN: Basically everything in here it out of date until I remove this line.
This is a small and simple library for using hexagonal grids in amulet + lua. I wrote it for a tower defense game I'm making. This is a small and simple library for using hexagonal grids in amulet + lua. I wrote it for a tower defense game I'm making.
It's not really well documented. If you want an actual good resource, go to [amit's guide to hexagonal grids](https://redblobgames.com/grids/hexagons). So much of what is here I derived from amit's work. It's not really well documented. If you want an actual good resource, go to [amit's guide to hexagonal grids](https://redblobgames.com/grids/hexagons). So much of what is here I derived from amit's work.

BIN
res/wall_closed.png

After

Width: 138  |  Height: 138  |  Size: 2.0 KiB

BIN
res/wall_open.png

After

Width: 137  |  Height: 137  |  Size: 1.8 KiB

6
src/colors.lua

@ -1,15 +1,17 @@
COLORS = { COLORS = {
TRANSPARENT = vec4(0.4), TRANSPARENT = vec4(0.4),
--TRANSPARENT = vec4(0.6),
-- tones
WHITE = vec4(0.8, 0.8, 0.7, 1), WHITE = vec4(0.8, 0.8, 0.7, 1),
BLACK = vec4(0, 0, 0, 1), BLACK = vec4(0, 0, 0, 1),
TRUEBLACK = vec4(0, 0, 0, 1),
-- hues -- hues
BLUE_STONE = vec4(0.12, 0.3, 0.3, 1), BLUE_STONE = vec4(0.12, 0.3, 0.3, 1),
MYRTLE = vec4(0.10, 0.25, 0.10, 1), MYRTLE = vec4(0.10, 0.25, 0.10, 1),
BROWN_POD = vec4(0.25, 0.20, 0.10, 1), BROWN_POD = vec4(0.25, 0.20, 0.10, 1),
BOTTLE_GREEN = vec4(0.15, 0.30, 0.20, 1)
BOTTLE_GREEN = vec4(0.15, 0.30, 0.20, 1),
MAGENTA = vec4(1, 0, 1, 1)
} }

99
src/grid.lua

@ -1,38 +1,34 @@
require "colors" require "colors"
require "gui"
WORLD_GRID_DIMENSIONS = vec2(46, 32)
CELL_SIZE = 20
local world_grid_map
HEX_SIZE = 20
HEX_GRID_WIDTH = 65
HEX_GRID_HEIGHT = 33
HEX_GRID_DIMENSIONS = vec2(HEX_GRID_WIDTH, HEX_GRID_HEIGHT)
-- ensure home-base is somewhat of an open area.
function find_home(preferred_radius)
home = spiral_map(vec2(23, 4), preferred_radius or 2)
local home_node = am.group()
-- @NOTE no idea why the y coordinate doesn't need to be transformed here
HEX_GRID_CENTER = vec2(math.floor(HEX_GRID_DIMENSIONS.x/2), 0)
repeat
local happy = true
-- index is hex coordinates [x][y]
-- { { elevation, sprite, tile } }
HEX_MAP = {}
for i,h in pairs(home) do
local elevation = map[h.x][h.y]
function grid_pixel_dimensions()
local hhs = hex_horizontal_spacing(HEX_SIZE)
local hvs = hex_vertical_spacing(HEX_SIZE)
if not elevation then -- hex not in map
elseif elevation > 0.5 or elevation < -0.5 then
happy = false
-- number of 'spacings' on the grid == number of cells - 1
return vec2((HEX_GRID_DIMENSIONS.x - 1) * hhs
, (HEX_GRID_DIMENSIONS.y - 1) * hvs)
end
elseif not happy then
home = spiral_map(h, preferred_radius or 1)
home_node = am.group()
GRID_PIXEL_DIMENSIONS = grid_pixel_dimensions()
WORLDSPACE_COORDINATE_OFFSET = -GRID_PIXEL_DIMENSIONS/2
else
local center = hex_to_pixel(h)
local color = vec4(1, 0, 0.5, 1)
local node = am.circle(center, 4, color, 4)
home_node:append(node)
end
end
until happy
return home_node
-- convience function for when getting a tile at x,y could fail
function get_tile(x, y)
return HEX_MAP[x] and HEX_MAP[x][y]
end end
-- map elevation to appropriate tile color. -- map elevation to appropriate tile color.
@ -48,36 +44,51 @@ function color_at(elevation)
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
log('bad elevation')
return vec4(0)
end end
end end
function worldspace_coordinate_offset()
return vec2(-hex_height(CELL_SIZE))
end
function random_map(seed, do_seed_rng)
local elevation_map = rectangular_map(HEX_GRID_DIMENSIONS.x, HEX_GRID_DIMENSIONS.y, seed)
function random_map(seed)
world_grid_map = rectangular_map(WORLD_GRID_DIMENSIONS.x, WORLD_GRID_DIMENSIONS.y, seed);
math.randomseed(world_grid_map.seed)
local world = am.translate(worldspace_coordinate_offset()) ^ am.group(am.circle(vec2(0), 32, COLORS.WHITE)):tag"world"
if do_seed_rng then math.randomseed(elevation_map.seed) end
for i,_ in pairs(world_grid_map) do
for j,elevation in pairs(world_grid_map[i]) do
HEX_MAP = {}
local world = am.group():tag"world"
for i,_ in pairs(elevation_map) do
HEX_MAP[i] = {}
for j,elevation in pairs(elevation_map[i]) do
-- subtly shade map edges
local off = hex_to_offset(vec2(i, j))
local mask = vec4(0, 0, 0, math.max(((off.x - WORLD_GRID_DIMENSIONS.x/2) / WORLD_GRID_DIMENSIONS.x) ^ 2,
((-off.y - WORLD_GRID_DIMENSIONS.y/2) / WORLD_GRID_DIMENSIONS.y) ^ 2))
local off = hex_to_evenq(vec2(i, j))
local mask = vec4(0, 0, 0, math.max(((off.x - HEX_GRID_DIMENSIONS.x/2) / HEX_GRID_DIMENSIONS.x) ^ 2
, ((-off.y - HEX_GRID_DIMENSIONS.y/2) / HEX_GRID_DIMENSIONS.y) ^ 2))
local color = color_at(elevation) - mask local color = color_at(elevation) - mask
local node = am.circle(hex_to_pixel(vec2(i, j)), CELL_SIZE, vec4(0), 6)
:action(am.tween(2, { color=color }, am.ease.out(am.ease.hyperbola)))
local node = am.circle(hex_to_pixel(vec2(i, j)), HEX_SIZE, color, 6)
HEX_MAP[i][j] = {
elevation = elevation,
sprite = node,
tile = {}
}
world:append(node) world:append(node)
end end
end end
--world:append(find_home(2))
--world:action(spawner)
return world:tag"world"
-- the center of the map in some radius is always considered 'passable' terrain and is home base
-- terraform this area to ensure it's passable
-- @NOTE no idea why the y-coord doesn't need to be transformed
local home = spiral_map(HEX_GRID_CENTER, 3)
for _,hex in pairs(home) do
HEX_MAP[hex.x][hex.y].elevation = 0
HEX_MAP[hex.x][hex.y].sprite.color = color_at(0)
world:append(am.circle(hex_to_pixel(vec2(hex.x, hex.y)), HEX_SIZE/2, COLORS.MAGENTA, 4))
end end
return am.translate(WORLDSPACE_COORDINATE_OFFSET)
^ world:tag"world"
end

54
src/gui.lua

@ -1,9 +1,53 @@
local hot
local active
local hot, active = false, false
local widgets = {}
function button(x, y)
local color = (x + y) % 2 == 0 and vec4(0.4, 0.4, 0.5, 1) or vec4(0.5, 0.4, 0.4, 1)
return am.translate(x * 80, y * 80) ^ am.rect(-40, 40, 40, -40, color)
function get_widgets() return widgets end
function register_widget(id, poll)
widgets[id] = { id = id, poll = poll }
end
function point_in_rect(point, rect)
return point.x > rect.x1 and point.x < rect.x2 and point.y > rect.y1 and point.y < rect.y2
end
function set_hot(id)
if not active then hot = { id = id } end
end
function register_button_widget(id, rect)
register_widget(id, function()
local click = false
if active and active.id == id then
if win:mouse_released"left" then
if hot and hot.id == id then click = true end
active = false
end
elseif hot and hot.id == id then
if win:mouse_pressed"left" then active = { id = id } end
end
if point_in_rect(win:mouse_position(), rect) then set_hot(id) end
return click
end)
end
function make_button_widget(id, position, dimensions, text)
local rect = am.rect(
-dimensions.x/2,
dimensions.y/2,
dimensions.x/2,
-dimensions.y/2,
vec4(1, 0, 0, 1)
)
register_button_widget(id, rect)
return am.group{
rect,
am.text(text)
}:tag(id)
end end

51
src/hexyz.lua

@ -1,4 +1,5 @@
math.round = function(n) return math.floor(n + 0.5) end
--============================================================================ --============================================================================
-- HEX CONSTANTS AND UTILITY FUNCTIONS -- HEX CONSTANTS AND UTILITY FUNCTIONS
@ -11,7 +12,6 @@ ORIENTATION = {
W = mat2(2.0/3.0, 0.0, -1.0/3.0 , 3.0^0.5/3.0), W = mat2(2.0/3.0, 0.0, -1.0/3.0 , 3.0^0.5/3.0),
angle = 0.0 angle = 0.0
}, },
-- Forward & Inverse Matrices used for the Pointy Orientation -- Forward & Inverse Matrices used for the Pointy Orientation
POINTY = { POINTY = {
M = mat2(3.0^0.5, 3.0^0.5/2.0, 0.0, 3.0/2.0), M = mat2(3.0^0.5, 3.0^0.5/2.0, 0.0, 3.0/2.0),
@ -23,8 +23,9 @@ ORIENTATION = {
-- whenver |orientation| appears as an argument, if it isn't provided, this is used instead. -- whenver |orientation| appears as an argument, if it isn't provided, this is used instead.
local DEFAULT_ORIENTATION = ORIENTATION.FLAT local DEFAULT_ORIENTATION = ORIENTATION.FLAT
-- whenever |size| for a hexagon appears as an argument, if it isn't provided, use this
-- 'size' here is distance from the centerpoint to any vertex in pixel -- 'size' here is distance from the centerpoint to any vertex in pixel
local DEFAULT_HEX_SIZE = 20
local DEFAULT_HEX_SIZE = vec2(20)
-- actual width (longest contained horizontal line) of the hexagon -- actual width (longest contained horizontal line) of the hexagon
function hex_width(size, orientation) function hex_width(size, orientation)
@ -42,8 +43,12 @@ end
function hex_height(size, orientation) function hex_height(size, orientation)
local orientation = orientation or DEFAULT_ORIENTATION local orientation = orientation or DEFAULT_ORIENTATION
-- hex_width in one orientation == the height in the opposite orientation
return hex_width(size, orientation == ORIENTATION.FLAT and ORIENTATION.POINTY or ORIENTATION.FLAT)
if orientation == ORIENTATION.FLAT then
return math.sqrt(3) * size
elseif orientation == ORIENTATION.POINTY then
return size * 2
end
end end
-- returns actual width and height of a hexagon given it's |size| which is the distance from the centerpoint to any vertex in pixels -- returns actual width and height of a hexagon given it's |size| which is the distance from the centerpoint to any vertex in pixels
@ -59,7 +64,7 @@ function hex_horizontal_spacing(size, orientation)
if orientation == ORIENTATION.FLAT then if orientation == ORIENTATION.FLAT then
return hex_width(size, orientation) * 3/4 return hex_width(size, orientation) * 3/4
elseif orietnation == ORIENTATION.POINTY then
elseif orientation == ORIENTATION.POINTY then
return hex_height(size, orientation) return hex_height(size, orientation)
end end
end end
@ -68,8 +73,12 @@ end
function hex_vertical_spacing(size, orientation) function hex_vertical_spacing(size, orientation)
local orientation = orientation or DEFAULT_ORIENTATION local orientation = orientation or DEFAULT_ORIENTATION
-- hex_horizontal_spacing in one orientation == the vertical spacing in the opposite orientation
return hex_horizontal_spacing(size, orientation == ORIENTATION.FLAT and ORIENTATION.POINTY or ORIENTATION.FLAT)
if orientation == ORIENTATION.FLAT then
return hex_height(size, orientation)
elseif orientation == ORIENTATION.POINTY then
return hex_width(size, orientation) * 3/4
end
end end
-- returns the distance between adjacent hexagon centers in a grid -- returns the distance between adjacent hexagon centers in a grid
@ -101,15 +110,13 @@ end
-- Returns a vec2 Which is the Nearest |x, y| to Float Trio |x, y, z| -- Returns a vec2 Which is the Nearest |x, y| to Float Trio |x, y, z|
local function hex_round(x, y, z) local function hex_round(x, y, z)
local function round(n) return math.floor(n + 0.5) end
local rx = round(x)
local ry = round(y)
local rz = round(z) or round(-x - y)
local rx = math.round(x)
local ry = math.round(y)
local rz = math.round(z) or math.round(-x - y)
local xdelta = math.abs(rx - x) local xdelta = math.abs(rx - x)
local ydelta = math.abs(ry - y) local ydelta = math.abs(ry - y)
local zdelta = math.abs(rz - z or round(-x - y))
local zdelta = math.abs(rz - z or math.round(-x - y))
if xdelta > ydelta and xdelta > zdelta then if xdelta > ydelta and xdelta > zdelta then
rx = -ry - rz rx = -ry - rz
@ -126,8 +133,8 @@ end
function hex_to_pixel(hex, size, orientation) function hex_to_pixel(hex, size, orientation)
local M = orientation and orientation.M or DEFAULT_ORIENTATION.M local M = orientation and orientation.M or DEFAULT_ORIENTATION.M
local x = (M[1][1] * hex[1] + M[1][2] * hex[2]) * (size and size[1] or DEFAULT_HEX_SIZE)
local y = (M[2][1] * hex[1] + M[2][2] * hex[2]) * (size and size[2] or DEFAULT_HEX_SIZE)
local x = (M[1][1] * hex[1] + M[1][2] * hex[2]) * (size and size[1] or DEFAULT_HEX_SIZE[1])
local y = (M[2][1] * hex[1] + M[2][2] * hex[2]) * (size and size[2] or DEFAULT_HEX_SIZE[2])
return vec2(x, y) return vec2(x, y)
end end
@ -163,11 +170,13 @@ function hex_corners(hex, size, orientation)
return corners return corners
end end
function hex_to_offset(hex)
return vec2(hex[1], -hex[1] - hex[2] + (hex[1] + (hex[1] % 2)) / 2) end
function hex_to_evenq(hex)
return vec2(hex.x, (-hex.x - hex.y) + (hex.x + (hex.x % 2)) / 2)
end
function offset_to_hex(off)
return vec2(off[1], off[2] - math.floor((off[1] - 1 * (off[1] % 2))) / 2) end
function evenq_to_hex(off)
return vec2(off.x, -off.x - (off.y - (off.x + (off.x % 2)) / 2))
end
--============================================================================ --============================================================================
-- MAPS & STORAGE -- MAPS & STORAGE
@ -285,9 +294,9 @@ function rectangular_map(width, height, seed)
local seed = seed or math.random(width * height) local seed = seed or math.random(width * height)
local map = {} local map = {}
for i = 0, width do
for i = 0, width - 1 do
map[i] = {} map[i] = {}
for j = 0, height do
for j = 0, height - 1 do
-- Begin to Calculate Noise -- Begin to Calculate Noise
local idelta = i / width local idelta = i / width

155
src/main.lua

@ -1,125 +1,96 @@
math.randomseed(os.time()); math.random(); math.random(); math.random() math.randomseed(os.time()); math.random(); math.random(); math.random()
--[[=I==========================================================================]]
--============================================================================
-- Imports -- Imports
require "hexyz" require "hexyz"
require "grid" require "grid"
require "mob"
require "util"
--[[============================================================================]]
--============================================================================
-- Globals -- Globals
win = am.window{
width = 1920,
height = 1080,
resizable = false
}
--[[============================================================================]]
-- Local 'Globals'
local home
win = am.window{ width = 1920, height = 1080 }
function poll_mouse()
if win:mouse_position().x > -268 then -- mouse is inside game map
function mask()
return am.rect(win.left, win.bottom, win.right, win.top, COLORS.TRANSPARENT):tag"mask"
end
local hex = pixel_to_hex(win:mouse_position() - vec2(-278, -318))
local off = hex_to_offset(hex)
function get_menu_for_tile(x, y, tile)
local pos = hex_to_pixel(vec2(x, y)) + WORLDSPACE_COORDINATE_OFFSET
return am.translate(pos) ^ am.group{
am.rect(-50, -50, 50, 50, COLORS.TRANSPARENT),
make_button_widget(x .. y, pos, vec2(100, 37), "close")
}
end
-- check if cursor location outside of map bounds
if off.x <= 1 or -off.y <= 1 or off.x >= 46 or -off.y >= 32 then
win.scene"coords".text = ""
function invoke_tile_menu(x, y, tile)
win.scene:append(get_menu_for_tile(x, y, tile))
end
else
if win:mouse_down"left" then
if map[hex.x][hex.y] <= -0.5 or map[hex.x][hex.y] >= 0.5 then
function game_action(scene)
local time = am.current_time()
else
map[hex.x][hex.y] = 2
win.scene"world":append(am.circle(hex_to_pixel(hex), CELL_SIZE, COLORS.BLACK, 6))
end
end
win.scene"coords".text = string.format("%2d,%2d", off.x, -off.y)
win.scene"hex_cursor".center = hex_to_pixel(hex) + vec2(-278, -318)
end
else -- mouse is over background bar, (or outside window!)
if win:key_pressed"escape" then
init()
local mouse = win:mouse_position()
local hex = pixel_to_hex(mouse - WORLDSPACE_COORDINATE_OFFSET)
local _off = hex_to_evenq(hex)
local off = _off{ y = -_off.y } - vec2(math.floor(HEX_GRID_WIDTH/2)
, math.floor(HEX_GRID_HEIGHT/2))
local off2 = evenq_to_hex(_off)
local tile = get_tile(hex.x, hex.y)
if tile and win:mouse_pressed"left" then
log(tile)
--invoke_tile_menu(hex.x, hex.y, tile)
end end
if win:key_pressed"f1" then end
for wid,widget in pairs(get_widgets()) do
if widget.poll() then
log('we clicked button with id %s!', wid)
end end
end end
function update_score()
win.scene"score".text = string.format("SCORE: %.2f", am.current_time())
end
do_mob_updates()
do_mob_spawning()
function main_action(main_scene)
update_score()
poll_mouse()
-- draw stuff
win.scene"hex_cursor".center = hex_to_pixel(hex) + WORLDSPACE_COORDINATE_OFFSET
win.scene"score".text = string.format("SCORE: %.2f", time)
win.scene"coords".text = string.format("%d,%d", off.x, off.y)
win.scene"rev".text = string.format("%d,%d", off2.x, off2.y)
end end
function game_init()
local score = am.translate(win.left, win.top - 50) ^ am.text("", "left"):tag"score"
local coords = am.translate(win.right, win.top - 50) ^ am.text("", "right"):tag"coords"
local hex_cursor = am.circle(vec2(win.left, win.top), CELL_SIZE, vec4(0.4), 6):tag"hex_cursor"
local curtain = am.rect(win.left, win.top, win.right, win.bottom, COLORS.BLUE_STONE):tag"curtain"
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 coords2 = am.translate(win.right - 10, win.top - 40) ^ am.text("", "right"):tag"rev"
local hex_cursor = am.circle(vec2(-6969), HEX_SIZE, COLORS.TRANSPARENT, 6):tag"hex_cursor"
local main_scene = am.group{
local curtain = am.rect(win.left, win.bottom, win.right, win.top, COLORS.TRUEBLACK)
curtain:action(coroutine.create(function()
am.wait(am.tween(curtain, 3, { color = vec4(0) }, am.ease.out(am.ease.hyperbola)))
win.scene:remove(curtain)
end))
local scene = am.group{
random_map(), random_map(),
curtain,
hex_cursor,
score, score,
coords, coords,
curtain,
hex_cursor
coords2
} }
main_scene:action(am.series
{
am.tween(curtain, 0.8, { x2 = win.left }, am.ease.bounce),
main_action
})
win.scene = main_scene
end
function draw_menu()
local map = hexagonal_map(15, 9)
local backdrop = am.group()
scene:action(game_action)
for i,_ in pairs(map) do
for j,e in pairs(map[i]) do
backdrop:append(am.circle(hex_to_pixel(vec2(i, j)), 11, color_at(e), 6))
end
end
local title_text = am.group
{
am.translate(0, 200) ^ am.scale(5) ^ am.text("hexyz", COLORS.WHITE, "right"),
am.translate(0, 130) ^ am.scale(4) ^ am.text("a tower defense", COLORS.WHITE, 1),
am.circle(vec2(0), 100, vec4(0.6), 6):tag"button", am.scale(4) ^ am.text("START", COLORS.BLACK)
}
win.scene = am.group
{
backdrop,
title_text
}
:action(function(self)
local mouse = win:mouse_position()
if math.length(mouse) < 100 then
self"button":action(am.series
{
am.tween(0.1, { color = COLORS.WHITE }),
am.tween(0.1, { color = vec4(0.6) })
})
if win:mouse_pressed"left" then
game_init()
end
end
end)
return scene
end end
function init() function init()
draw_menu()
win.scene = am.scale(1) ^ game_scene()
end end
init() init()

131
src/mob.lua

@ -1,79 +1,108 @@
MOBS = {}
MOB_HURTBOX_RADIUS = 4
-- check if a |tile| is passable by |mob|
function can_pass_through(mob, tile)
return tile and tile.elevation and tile.elevation < 0.5 and tile.elevation > -0.5
end
function get_path(mob, starting_hex, goal_hex)
local moves = {}
local visited = {}
visited[starting_hex.x] = {}
visited[starting_hex.x][starting_hex.y] = true
-- determines when, where, and how often to spawn mobs.
function spawner(world)
local SPAWN_CHANCE = 25
if math.random(SPAWN_CHANCE) == 1 then -- chance to spawn
local spawn_position
repeat repeat
-- ensure we spawn on an random tile along the map's edges
local x,y = math.random(46), math.random(33)
if math.random() < 0.5 then
x = math.random(0, 1) * 46
local neighbours = hex_neighbours(pixel_to_hex(mob.position))
local candidates = {}
-- get list of candidates: hex positions to consider moving to.
for _,neighbour in pairs(neighbours) do
if can_pass_through(mob, get_tile(neighbour.x, neighbour.y)) then
if not (visited[neighbour.h] and visited[neighbour.x][neighbour.y]) then
table.insert(candidates, neighbour)
else else
y = math.random(0, 1) * 33
--table.insert(candidates, neighbour)
end
end
end end
spawn_position = offset_to_hex(vec2(x, y))
-- ensure that we spawn somewhere that is passable: mid-elevation
local e = map[spawn_position.x][spawn_position.y]
until e and e < 0.5 and e > -0.5
local mob = am.translate(-278, -318) ^ am.circle(hex_to_pixel(spawn_position), MOB_HURTBOX_RADIUS)
world:append(mob"circle":action(coroutine.create(live)))
-- choose where to move
local move = candidates[1]
for _,hex in pairs(candidates) do
if math.distance(hex, goal_hex) < math.distance(move, goal_hex) then
move = hex
end end
end end
-- this function is the coroutine that represents the life-cycle of a mob.
function live(mob)
local dead = false
if move then
table.insert(moves, move)
visited[move.x] = {}
visited[move.x][move.y] = true
end
local visited = {}
visited[mob.center.x] = {}; visited[mob.center.x][mob.center.y] = true
--if move == goal then log('made it!') return end
until move == goal_hex
return moves
end
-- begin life
function get_spawn_hex(mob)
local spawn_hex
repeat repeat
local neighbours = hex_neighbours(pixel_to_hex(mob.center))
local candidates = {}
-- ensure we spawn on an random tile along the map's edges
local roll = math.random(HEX_GRID_WIDTH * 2 + HEX_GRID_HEIGHT * 2) - 1
local x, y
-- get list of candidates: hex positions to consider moving to.
for _,h in pairs(neighbours) do
if roll < HEX_GRID_HEIGHT then
x, y = 0, roll
local e
if map[h.x] then
e = map[h.x][h.y]
end
elseif roll < (HEX_GRID_WIDTH + HEX_GRID_HEIGHT) then
x, y = roll - HEX_GRID_HEIGHT, HEX_GRID_HEIGHT - 1
elseif roll < (HEX_GRID_HEIGHT * 2 + HEX_GRID_WIDTH) then
x, y = HEX_GRID_WIDTH - 1, roll - HEX_GRID_WIDTH - HEX_GRID_HEIGHT
if e and e < 0.5 and e > -0.5 then
if visited[h.x] then
if not visited[h.x][h.y] then
table.insert(candidates, h)
end
else else
table.insert(candidates, h)
x, y = roll - (HEX_GRID_HEIGHT * 2) - HEX_GRID_WIDTH, 0
end end
-- @NOTE negate 'y' because hexyz algorithms assume south is positive, in amulet north is positive
spawn_hex = evenq_to_hex(vec2(x, -y))
local tile = HEX_MAP[spawn_hex.x][spawn_hex.y]
until can_pass_through(mob, tile)
return spawn_hex
end end
function make_mob()
local mob = {}
local spawn_hex = get_spawn_hex(mob)
log(spawn_hex)
local spawn_position = hex_to_pixel(spawn_hex) + WORLDSPACE_COORDINATE_OFFSET
mob.position = spawn_position
--mob.path = get_path(mob, spawn_hex, HEX_GRID_CENTER)
mob.sprite = am.circle(spawn_position, 18, COLORS.WHITE, 4)
win.scene:append(mob.sprite)
return mob
end end
-- choose where to move. manhattan distance closest to goal is chosen.
local move = candidates[1]
for _,h in pairs(candidates) do
if math.distance(h, home.center) < math.distance(move, home.center) then
move = h
local SPAWN_CHANCE = 25
function do_mob_spawning()
if win:key_pressed"space" then
--if math.random(SPAWN_CHANCE) == 1 then
table.insert(MOBS, make_mob())
end end
end end
if not move then print("can't find anywhere to move to"); return
end -- bug
function do_mob_updates()
for _,mob in pairs(MOBS) do
local speed = map[move.x][move.y] ^ 2 + 0.5
am.wait(am.tween(mob, speed, {center=hex_to_pixel(move)}))
visited[move.x] = {}; visited[move.x][move.y] = true
if move == home.center then dead = true end
until dead
win.scene:remove(mob)
end
end end

14
src/util.lua

@ -0,0 +1,14 @@
function table.shift(t, count)
local e = t[1]
t[1] = nil
for i,e in pairs(t) do
if e then
t[i - 1] = e
end
end
return e
end
Loading…
Cancel
Save