Nicholas Hayashi
4 years ago
14 changed files with 492 additions and 248 deletions
-
35NOTES.md
-
14color.lua
-
27main.lua
-
BINres/moat1.png
-
BINres/wall_closed.png
-
114src/entity.lua
-
3src/extra.lua
-
13src/geometry.lua
-
37src/grid.lua
-
107src/hexyz.lua
-
143src/mob.lua
-
88src/projectile.lua
-
102src/tower.lua
-
1texture.lua
@ -0,0 +1,35 @@ |
|||||
|
|
||||
|
|
||||
|
todoooos & notes |
||||
|
|
||||
|
@TODO test optimizing pathfinding via breadth first search/djikstra |
||||
|
|
||||
|
i think i want more or less no such thing as 'impassable' terrain |
||||
|
|
||||
|
all mobs always can always traverse everything, though they initially will give the impression that certain tiles are impassable by prefering certain tiles. |
||||
|
|
||||
|
the illusion is likely to be broken when you attempt to fully wall-off an area, and mobs begin deciding to climb over mountains or swim through lakes. this will come at great cost to them, but they will be capable of it |
||||
|
|
||||
|
MAP RESOURCES |
||||
|
- spawn diamonds or special floating resources that give you bonuses for building on, whether it's score, money, or boosting the effectiveness of the tower you place on top, etc. |
||||
|
- killing certain mobs may cause these resources to spawn on the hex they died on |
||||
|
|
||||
|
|
||||
|
towers: |
||||
|
0 - wall |
||||
|
some fraction of the height of the tallest mountain |
||||
|
makes mob pathing more difficult |
||||
|
|
||||
|
upgrades: |
||||
|
- +height - making the tower taller makes it more difficult/costly for mobs to climb over it |
||||
|
- spikes - mobs take damage when climbing |
||||
|
|
||||
|
1 - moat |
||||
|
some fraction of the depth of the deepest lake |
||||
|
makes mob pathing more difficult |
||||
|
|
||||
|
upgrades: |
||||
|
- +depth - making the moat deeper makes it more difficult/costly for mobs to swim through it |
||||
|
- alligators - mobs take damage while swimming |
||||
|
|
||||
|
|
After Width: 137 | Height: 137 | Size: 1.8 KiB |
Before Width: 138 | Height: 138 | Size: 2.0 KiB After Width: 100 | Height: 88 | Size: 1.9 KiB |
@ -1,77 +1,81 @@ |
|||||
|
|
||||
|
|
||||
ENTITY_TYPE = { |
|
||||
ENTITY = 0, |
|
||||
MOB = 1, |
|
||||
TOWER = 2, |
|
||||
PROJECTILE = 3 |
|
||||
} |
|
||||
|
MOBS = {} |
||||
|
TOWERS = {} |
||||
|
PROJECTILES = {} |
||||
|
|
||||
ENTITIES = {} |
|
||||
-- entity structure: |
|
||||
-- { |
|
||||
-- TOB - number - time of birth, const |
|
||||
-- hex - vec2 - current occupied hex, if any |
|
||||
-- position - vec2 - current pixel position of it's translate (forced parent) node |
|
||||
-- update - function - runs every frame with itself and its index as an argument |
|
||||
-- node - node - scene graph node |
|
||||
-- } |
|
||||
-- |
|
||||
-- mob(entity) structure: |
|
||||
-- { |
|
||||
-- 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 |
|
||||
-- bounty - number - score bonus you get when this mob is killed |
|
||||
-- hurtbox_radius - number - |
|
||||
-- } |
|
||||
-- |
|
||||
-- tower(entity) structure: |
|
||||
-- { |
|
||||
-- -- @NOTE these should probably be wrapped in a 'weapon' struct or something, so towers can have multiple weapons |
|
||||
-- range - number - distance it can shoot |
|
||||
-- last_shot_time - number - timestamp (seconds) of last time it shot |
|
||||
-- target_index - number - index of entity it is currently shooting |
|
||||
-- } |
|
||||
-- |
|
||||
-- bullet/projectile structure |
|
||||
-- { |
|
||||
-- vector - vec2 - normalized vector of the current direction of this projectile |
|
||||
-- velocity - number - multplier on distance travelled per frame |
|
||||
-- damage - number - guess |
|
||||
-- hitbox_radius - number - hitboxes are circles |
|
||||
-- } |
|
||||
-- |
|
||||
function make_and_register_entity(type_, hex, node, update) |
|
||||
|
--[[ |
||||
|
entity structure: |
||||
|
{ |
||||
|
TOB - number - time of birth, const |
||||
|
hex - vec2 - current occupied hex, if any |
||||
|
position - vec2 - current pixel position of it's translate (forced parent) node |
||||
|
update - function - runs every frame with itself and its index as an argument |
||||
|
node - node - scene graph node |
||||
|
} |
||||
|
--]] |
||||
|
function make_basic_entity(hex, node, update, position) |
||||
local entity = {} |
local entity = {} |
||||
|
|
||||
entity.type = type_ |
|
||||
entity.TOB = TIME |
entity.TOB = TIME |
||||
|
|
||||
|
-- usually you'll provide a hex and not a position, and the entity will spawn in the center |
||||
|
-- of the hex. if you want an entity to exist not at the center of a hex, you can provide a |
||||
|
-- pixel position instead |
||||
|
if position then |
||||
|
entity.position = position |
||||
|
entity.hex = pixel_to_hex(entity.position) |
||||
|
else |
||||
entity.hex = hex |
entity.hex = hex |
||||
entity.position = hex_to_pixel(hex) |
entity.position = hex_to_pixel(hex) |
||||
entity.update = update or function() log("unimplemented update function!") end |
|
||||
|
end |
||||
|
|
||||
|
entity.update = update |
||||
entity.node = am.translate(entity.position) ^ node |
entity.node = am.translate(entity.position) ^ node |
||||
|
|
||||
table.insert(ENTITIES, entity) |
|
||||
WORLD:append(entity.node) |
|
||||
return entity |
return entity |
||||
end |
end |
||||
|
|
||||
function delete_all_entities() |
|
||||
for index,entity in pairs(ENTITIES) do |
|
||||
delete_entity(index) |
|
||||
end |
|
||||
|
function register_entity(t, entity) |
||||
|
table.insert(t, entity) |
||||
|
WORLD:append(entity.node) |
||||
|
end |
||||
|
|
||||
ENTITIES = {} |
|
||||
|
-- |t| is the source table, probably MOBS, TOWERS, or PROJECTILES |
||||
|
function delete_entity(t, index) |
||||
|
if not t then log("splat!") end |
||||
|
|
||||
|
WORLD:remove(t[index].node) |
||||
|
t[index] = false -- leave empty indexes so other entities can learn that this entity was deleted |
||||
end |
end |
||||
|
|
||||
function delete_entity(index) |
|
||||
WORLD:remove(ENTITIES[index].node) |
|
||||
ENTITIES[index] = nil -- leave empty indexes so other entities can learn that this entity was deleted |
|
||||
|
function delete_all_entities() |
||||
|
for mob_index,mob in pairs(MOBS) do |
||||
|
delete_entity(MOBS, mob_index) |
||||
|
end |
||||
|
for tower_index,tower in pairs(TOWERS) do |
||||
|
delete_entity(TOWERS, tower_index) |
||||
|
end |
||||
|
for projectile_index,projectile in pairs(PROJECTILES) do |
||||
|
delete_entity(PROJECTILES, projectile_index) |
||||
|
end |
||||
end |
end |
||||
|
|
||||
function do_entity_updates() |
function do_entity_updates() |
||||
for index,entity in pairs(ENTITIES) do |
|
||||
entity.update(entity, index) |
|
||||
|
for mob_index,mob in pairs(MOBS) do |
||||
|
if mob and mob.update then |
||||
|
mob.update(mob, mob_index) |
||||
|
end |
||||
|
end |
||||
|
for tower_index,tower in pairs(TOWERS) do |
||||
|
if tower and tower.update then |
||||
|
tower.update(tower, tower_index) |
||||
|
end |
||||
|
end |
||||
|
for projectile_index,projectile in pairs(PROJECTILES) do |
||||
|
if projectile and projectile.update then |
||||
|
projectile.update(projectile, projectile_index) |
||||
|
end |
||||
end |
end |
||||
end |
end |
||||
|
|
@ -1,45 +1,81 @@ |
|||||
|
|
||||
|
|
||||
function make_and_register_projectile(hex, vector, velocity, damage, hitbox_radius) |
|
||||
local projectile = make_and_register_entity( |
|
||||
-- type |
|
||||
ENTITY_TYPE.PROJECTILE, |
|
||||
|
--[[ |
||||
|
bullet/projectile(entity) structure |
||||
|
{ |
||||
|
vector - vec2 - normalized vector of the current direction of this projectile |
||||
|
velocity - number - multplier on distance travelled per frame |
||||
|
damage - number - guess |
||||
|
hitbox_radius - number - hitboxes are circles |
||||
|
} |
||||
|
--]] |
||||
|
|
||||
hex, |
|
||||
|
function projectile_update(projectile, projectile_index) |
||||
|
projectile.position = projectile.position + projectile.vector * projectile.velocity |
||||
|
projectile.node.position2d = projectile.position |
||||
|
projectile.hex = pixel_to_hex(projectile.position) |
||||
|
|
||||
-- node |
|
||||
am.circle(vec2(0), hitbox_radius - 1, COLORS.CLARET), |
|
||||
|
-- check if we're out of bounds |
||||
|
if not point_in_rect(projectile.position + WORLDSPACE_COORDINATE_OFFSET, { |
||||
|
x1 = WIN.left, |
||||
|
y1 = WIN.bottom, |
||||
|
x2 = WIN.right, |
||||
|
y2 = WIN.top |
||||
|
}) then |
||||
|
delete_entity(PROJECTILES, projectile_index) |
||||
|
return true |
||||
|
end |
||||
|
|
||||
-- update function |
|
||||
function(_projectile, _projectile_index) |
|
||||
_projectile.position = _projectile.position + vector * velocity |
|
||||
_projectile.node.position2d = _projectile.position |
|
||||
_projectile.hex = pixel_to_hex(_projectile.position) |
|
||||
|
-- check if we hit something |
||||
|
-- get a list of hexes that could have something we could hit on them |
||||
|
local search_hexes = spiral_map(projectile.hex, 1) |
||||
|
local hit_mob_count = 0 |
||||
|
local hit_mobs = {} |
||||
|
for _,hex in pairs(search_hexes) do |
||||
|
|
||||
local mob_index,mob = mob_on_hex(_projectile.hex) |
|
||||
|
-- check if there's a mob on the hex |
||||
|
for mob_index,mob in pairs(mobs_on_hex(hex)) do |
||||
if mob and circles_intersect(mob.position |
if mob and circles_intersect(mob.position |
||||
, _projectile.position |
|
||||
|
, projectile.position |
||||
, mob.hurtbox_radius |
, mob.hurtbox_radius |
||||
, _projectile.hitbox_radius) then |
|
||||
|
, projectile.hitbox_radius) then |
||||
|
table.insert(hit_mobs, mob_index, mob) |
||||
|
hit_mob_count = hit_mob_count + 1 |
||||
|
end |
||||
|
end |
||||
|
end |
||||
|
|
||||
do_hit_mob(mob, _projectile.damage, mob_index) |
|
||||
delete_entity(_projectile_index) |
|
||||
WORLD:action(vplay_sound(SOUNDS.HIT1)) |
|
||||
|
-- we didn't hit anyone |
||||
|
if hit_mob_count == 0 then return end |
||||
|
|
||||
elseif not point_in_rect(_projectile.position + WORLDSPACE_COORDINATE_OFFSET, { |
|
||||
x1 = WIN.left, |
|
||||
y1 = WIN.bottom, |
|
||||
x2 = WIN.right, |
|
||||
y2 = WIN.top |
|
||||
}) then |
|
||||
delete_entity(_projectile_index) |
|
||||
|
-- we could have hit multiple, (optionally) find the closest |
||||
|
local closest_mob_index, closest_mob = next(hit_mobs, nil) |
||||
|
local closest_d = math.distance(closest_mob.position, projectile.position) |
||||
|
for _mob_index,mob in pairs(hit_mobs) do |
||||
|
local d = math.distance(mob.position, projectile.position) |
||||
|
if d < closest_d then |
||||
|
closest_mob_index = _mob_index |
||||
|
closest_mob = mob |
||||
|
closest_d = d |
||||
end |
end |
||||
end |
end |
||||
) |
|
||||
|
|
||||
|
-- hit the mob, delete ourselves, affect the world |
||||
|
do_hit_mob(closest_mob, projectile.damage, closest_mob_index) |
||||
|
delete_entity(PROJECTILES, projectile_index) |
||||
|
WORLD:action(vplay_sound(SOUNDS.HIT1)) |
||||
|
end |
||||
|
|
||||
|
function make_and_register_projectile(hex, vector, velocity, damage, hitbox_radius) |
||||
|
local projectile = make_basic_entity(hex |
||||
|
, am.line(vector, vector*hitbox_radius, 3, COLORS.CLARET) |
||||
|
, projectile_update) |
||||
|
|
||||
projectile.vector = vector |
projectile.vector = vector |
||||
projectile.velocity = velocity |
projectile.velocity = velocity |
||||
projectile.damage = damage |
projectile.damage = damage |
||||
projectile.hitbox_radius = hitbox_radius |
projectile.hitbox_radius = hitbox_radius |
||||
|
|
||||
|
register_entity(PROJECTILES, projectile) |
||||
end |
end |
||||
|
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue