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.

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