hexyz is tower defense game, and a lua library for dealing with hexagonal grids
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

256 lines
7.4 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
5 years ago
4 years ago
5 years ago
  1. math.randomseed(os.time())
  2. math.random()
  3. math.random()
  4. math.random()
  5. -- Globals
  6. WIN = am.window{
  7. width = 1920,
  8. height = 1080,
  9. title = "hexyz",
  10. highdpi = true,
  11. letterbox = true
  12. }
  13. STATE = {} -- @TODO move state in here
  14. -- assets and/or trivial code
  15. require "color"
  16. require "sound"
  17. require "texture"
  18. require "src/entity"
  19. require "src/extra"
  20. require "src/geometry"
  21. require "src/hexyz"
  22. require "src/grid"
  23. require "src/gui"
  24. require "src/mob"
  25. require "src/projectile"
  26. require "src/tower"
  27. OFF_SCREEN = vec2(WIN.width * 2) -- arbitrary pixel position that is garunteed to be off screen
  28. PERF_STATS = false -- result of am.perf_stats() -- should be called every frame
  29. WORLD = false -- root scene node of everything considered to be in the game world
  30. -- aka non gui stuff
  31. HEX_CURSOR = false
  32. TIME = 0 -- runtime of the current game in seconds (not whole program runtime)
  33. SCORE = 0 -- score of the player
  34. STARTING_MONEY = 50
  35. MONEY = STARTING_MONEY -- available resources
  36. MOUSE = false -- position of the mouse at the start of every frame, if an action is tracking it
  37. -- global audio settings
  38. MUSIC_VOLUME = 0.1
  39. SFX_VOLUME = 0.1
  40. -- game stuff
  41. SELECTED_TOWER_TYPE = 1
  42. -- top right display types
  43. local TRDTS = {
  44. NOTHING = -1,
  45. CENTERED_EVENQ = 0,
  46. EVENQ = 1,
  47. HEX = 2,
  48. PLATFORM = 3,
  49. PERF = 4,
  50. SEED = 5,
  51. TILE = 6
  52. }
  53. local TRDT = TRDTS.SEED
  54. local function select_hex(hex)
  55. local tower = tower_on_hex(hex)
  56. local tile = HEX_MAP.get(hex.x, hex.y)
  57. end
  58. local function can_do_build(hex, tile, tower_type)
  59. return can_afford_tower(MONEY, tower_type) and tower_is_buildable_on(hex, tile, tower_type)
  60. end
  61. local function game_action(scene)
  62. --if SCORE < 0 then game_end() end
  63. TIME = TIME + am.delta_time
  64. SCORE = SCORE + am.delta_time
  65. MOUSE = WIN:mouse_position()
  66. PERF_STATS = am.perf_stats()
  67. local hex = pixel_to_hex(MOUSE - WORLDSPACE_COORDINATE_OFFSET)
  68. local rounded_mouse = hex_to_pixel(hex) + WORLDSPACE_COORDINATE_OFFSET
  69. local evenq = hex_to_evenq(hex)
  70. local centered_evenq = evenq{ y = -evenq.y } - vec2(math.floor(HEX_GRID_WIDTH/2)
  71. , math.floor(HEX_GRID_HEIGHT/2))
  72. local tile = HEX_MAP.get(hex.x, hex.y)
  73. local hot = evenq_is_interactable(evenq{ y = -evenq.y })
  74. do_entity_updates()
  75. do_mob_spawning()
  76. do_gui_updates()
  77. if WIN:mouse_pressed"left" then
  78. if hot and can_do_build(hex, tile, SELECTED_TOWER_TYPE) then
  79. build_tower(hex, SELECTED_TOWER_TYPE)
  80. end
  81. end
  82. --[[
  83. if WIN:mouse_pressed"middle" then
  84. WORLD"scale".scale2d = vec2(1)
  85. else
  86. local mwd = vec2(WIN:mouse_wheel_delta().y / 500)
  87. WORLD"scale".scale2d = WORLD"scale".scale2d + mwd
  88. WORLD"scale".scale2d = WORLD"scale".scale2d + mwd
  89. end
  90. ]]
  91. if WIN:key_pressed"escape" then
  92. WIN.scene"game".paused = true
  93. WIN.scene:append(am.group{
  94. am.rect(WIN.left, WIN.bottom, WIN.right, WIN.top, COLORS.TRANSPARENT),
  95. am.scale(3) ^ am.text("Paused.\nEscape to Resume", COLORS.BLACK)
  96. }:tag"pause_menu")
  97. WIN.scene:action(function()
  98. if WIN:key_pressed"escape" then
  99. WIN.scene:remove"pause_menu"
  100. WIN.scene"game".paused = false
  101. return true
  102. end
  103. end)
  104. elseif WIN:key_pressed"f1" then
  105. TRDT = (TRDT + 1) % #table.keys(TRDTS)
  106. elseif WIN:key_pressed"f2" then
  107. WORLD"flow_field".hidden = not WORLD"flow_field".hidden
  108. elseif WIN:key_pressed"tab" then
  109. local num_of_types = #table.keys(TOWER_TYPE)
  110. if WIN:key_down"lshift" then
  111. select_tower_type((SELECTED_TOWER_TYPE + num_of_types - 2) % num_of_types + 1)
  112. else
  113. select_tower_type((SELECTED_TOWER_TYPE) % num_of_types + 1)
  114. end
  115. elseif WIN:key_pressed"1" then select_tower_type(TOWER_TYPE.REDEYE)
  116. elseif WIN:key_pressed"2" then select_tower_type(2)
  117. elseif WIN:key_pressed"3" then select_tower_type(3)
  118. elseif WIN:key_pressed"4" then select_tower_type(4)
  119. elseif WIN:key_pressed"5" then select_tower_type(5)
  120. elseif WIN:key_pressed"6" then select_tower_type(6)
  121. elseif WIN:key_pressed"7" then select_tower_type(7)
  122. elseif WIN:key_pressed"8" then select_tower_type(8)
  123. elseif WIN:key_pressed"9" then select_tower_type(9)
  124. elseif WIN:key_pressed"0" then select_tower_type(10)
  125. elseif WIN:key_pressed"minus" then select_tower_type(11)
  126. elseif WIN:key_pressed"equals" then select_tower_type(12)
  127. end
  128. if tile and hot then
  129. HEX_CURSOR.center = rounded_mouse
  130. else
  131. HEX_CURSOR.center = OFF_SCREEN
  132. end
  133. WIN.scene"score".text = string.format("SCORE: %.2f", SCORE)
  134. WIN.scene"money".text = string.format("MONEY: %d", MONEY)
  135. do
  136. local str = ""
  137. if TRDT == TRDTS.CENTERED_EVENQ then
  138. str = centered_evenq.x .. "," .. centered_evenq.y .. " (cevenq)"
  139. elseif TRDT == TRDTS.EVENQ then
  140. str = evenq.x .. "," .. evenq.y .. " (evenq)"
  141. elseif TRDT == TRDTS.HEX then
  142. str = hex.x .. "," .. hex.y .. " (hex)"
  143. elseif TRDT == TRDTS.PLATFORM then
  144. str = string.format("%s %s lang %s", am.platform, am.version, am.language())
  145. elseif TRDT == TRDTS.PERF then
  146. str = table.tostring(PERF_STATS)
  147. elseif TRDT == TRDTS.SEED then
  148. str = "SEED: " .. HEX_MAP.seed
  149. elseif TRDT == TRDTS.TILE then
  150. str = table.tostring(HEX_MAP.get(hex.x, hex.y))
  151. end
  152. WIN.scene"coords".text = str
  153. end
  154. do_day_night_cycle()
  155. end
  156. function do_day_night_cycle()
  157. local slow = 100
  158. local tstep = (math.sin(TIME / 100) + 1) / PERF_STATS.avg_fps
  159. WORLD"negative_mask".color = vec4(tstep){a=1}
  160. end
  161. function game_end()
  162. -- de-initialize stuff
  163. delete_all_entities()
  164. TIME = 0
  165. SCORE = 0
  166. MONEY = STARTING_MONEY
  167. WORLD = false
  168. WIN.scene = am.group(am.scale(1) ^ game_scene())
  169. end
  170. function update_score(diff) SCORE = SCORE + diff end
  171. function update_money(diff) MONEY = MONEY + diff end
  172. function draw_hex_cursor(map, color)
  173. local group = am.group()
  174. for _,h in pairs(map) do
  175. group:append(am.circle(hex_to_pixel(h), HEX_SIZE, color, 6))
  176. end
  177. return group
  178. end
  179. function game_scene()
  180. local score = am.translate(WIN.left + 10, WIN.top - 20) ^ am.text("", "left"):tag"score"
  181. local money = am.translate(WIN.left + 10, WIN.top - 40) ^ am.text("", "left"):tag"money"
  182. local coords = am.translate(WIN.right - 10, WIN.top - 20) ^ am.text("", "right", "top"):tag"coords"
  183. HEX_CURSOR = am.circle(OFF_SCREEN, HEX_SIZE, COLORS.TRANSPARENT, 6):tag"hex_cursor"
  184. local curtain = am.rect(WIN.left, WIN.bottom, WIN.right, WIN.top, COLORS.TRUE_BLACK)
  185. curtain:action(coroutine.create(function()
  186. am.wait(am.tween(curtain, 3, { color = vec4(0) }, am.ease.out(am.ease.hyperbola)))
  187. WIN.scene:remove(curtain)
  188. end))
  189. -- 2227
  190. HEX_MAP, WORLD = random_map()
  191. local scene = am.group{
  192. WORLD,
  193. curtain,
  194. HEX_CURSOR,
  195. toolbelt(),
  196. score,
  197. money,
  198. coords,
  199. }:tag"game"
  200. scene:action(game_action)
  201. --scene:action(am.play(SOUNDS.TRACK1))
  202. return scene
  203. end
  204. WIN.scene = am.group(game_scene())
  205. noglobals()