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.

353 lines
10 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
3 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
3 years ago
4 years ago
4 years ago
3 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
3 years ago
4 years ago
4 years ago
4 years ago
3 years ago
3 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. -- @TODO @TODO @TODO @TODO @TODO
  2. -- settings menu
  3. -- -- music volume
  4. -- -- sfx volume
  5. -- -- resolution settings & fix grid size being based on resolution
  6. --
  7. -- serialization
  8. -- -- allow saving by name
  9. -- -- allow loading by name
  10. -- -- investigate saving as lua instead, and having as a consequence a less janky map serialization
  11. --
  12. -- map editor
  13. -- -- paint terrain elevation levels
  14. -- -- place tiles of set elevation
  15. -- -- place towers
  16. -- -- move home?
  17. --
  18. -- game
  19. -- -- HEX_GRID_CENTER =/= HOME
  20. -- -- allow selecting of tiles, if tower is selected then allow sell/upgrade
  21. -- -- button/ui to open pause menu - make it obvious that 'esc' is the button
  22. -- -- play the game and tweak numbers
  23. -- -- new game menu allowing set seed
  24. -- -- gattling gun tower (fast fire rate)
  25. -- -- spooder mob
  26. -- -- make art, birds-eye-ify the redeye tower and lighthouse maybe?
  27. -- aspect ratios seem like a huge mess
  28. -- for now, i think we should enforce 4:3
  29. local RESOLUTION_OPTIONS = {
  30. -- 16:9
  31. -- { width = 1776, height = 1000 },
  32. -- { width = 1920, height = 1080 },
  33. -- { width = 1600, height = 900 },
  34. -- 4:3
  35. { width = 1440, height = 1080 },
  36. { width = 1400, height = 1050 }, -- seems like a good one
  37. { width = 1280, height = 960 },
  38. { width = 1152, height = 864 },
  39. { width = 1024, height = 768 },
  40. { width = 960, height = 720 },
  41. { width = 832, height = 624 },
  42. { width = 800, height = 600 },
  43. }
  44. settings = am.load_state("settings", "json") or {
  45. fullscreen = false,
  46. window_width = 1400,
  47. window_height = 1050,
  48. music_volume = 0.2,
  49. sfx_volume = 0.1,
  50. sound_on = true
  51. }
  52. math.randomseed(os.time())
  53. math.random()
  54. math.random()
  55. math.random()
  56. math.random()
  57. do
  58. win = am.window{
  59. width = settings.window_width,
  60. height = settings.window_height,
  61. title = "hexyz",
  62. mode = settings.fullscreen and "fullscreen" or "windowed",
  63. resizable = true,
  64. highdpi = true,
  65. --resizable = true, -- user should probably set their resolution instead of resizing the window, but hey.
  66. letterbox = true,
  67. show_cursor = true,
  68. }
  69. end
  70. -- asset interfaces and/or trivial code
  71. require "conf"
  72. require "color"
  73. require "sound"
  74. require "texture"
  75. require "src/entity"
  76. require "src/extra"
  77. require "src/geometry"
  78. require "src/hexyz"
  79. require "src/game"
  80. require "src/gui"
  81. require "src/grid"
  82. require "src/mob"
  83. require "src/projectile"
  84. require "src/tower"
  85. local sound_toggle_node_tag = "sound-on-off-icon"
  86. local function make_sound_toggle_node(on)
  87. local sprite
  88. if on then
  89. sprite = pack_texture_into_sprite(TEXTURES.SOUND_ON1, 40, 30)
  90. else
  91. sprite = pack_texture_into_sprite(TEXTURES.SOUND_OFF, 40, 30)
  92. end
  93. return (am.translate(win.right - 30, win.top - 60) ^ sprite)
  94. :tag(sound_toggle_node_tag)
  95. :action(function()
  96. end)
  97. end
  98. local cached_music_volume = 0.2
  99. local cached_sfx_volume = 0.1
  100. local function toggle_mute()
  101. settings.sound_on = not settings.sound_on
  102. if settings.sound_on then
  103. settings.music_volume = cached_music_volume
  104. settings.sfx_volume = cached_sfx_volume
  105. else
  106. cached_music_volume = settings.music_volume
  107. cached_sfx_volume = settings.sfx_volume
  108. settings.music_volume = 0
  109. settings.sfx_volume = 0
  110. end
  111. update_music_volume(settings.music_volume)
  112. win.scene:replace(sound_toggle_node_tag, make_sound_toggle_node(settings.sound_on))
  113. end
  114. -- text popup in the middle of the screen that dissapates, call from anywhere
  115. function alert(message, color)
  116. win.scene:append(
  117. am.scale(3) ^ am.text(message, color or COLORS.WHITE)
  118. :action(coroutine.create(function(self)
  119. am.wait(am.tween(self, 1, { color = vec4(0) }, am.ease_out))
  120. win.scene:remove(self)
  121. end))
  122. )
  123. end
  124. function unpause(root_node)
  125. win.scene("game").paused = false
  126. win.scene:remove(root_node)
  127. end
  128. function main_action(self)
  129. if win:key_pressed("escape") then
  130. if game then
  131. unpause(self)
  132. else
  133. --win:close()
  134. end
  135. elseif win:key_pressed("f4") then
  136. win:close()
  137. elseif win:key_pressed("m") then
  138. toggle_mute()
  139. end
  140. if self"hex_backdrop" then
  141. self"hex_backdrop""rotate".angle = math.wrapf(self"hex_backdrop""rotate".angle - 0.005 * am.delta_time, math.pi*2)
  142. end
  143. end
  144. function make_main_scene_toolbelt()
  145. local include_save_option = game
  146. local include_unpause_option = game
  147. local options = {
  148. false,
  149. {
  150. texture = TEXTURES.NEW_GAME_HEX,
  151. action = function()
  152. win.scene:remove"menu"
  153. game_init()
  154. end
  155. },
  156. false,
  157. include_save_option and {
  158. texture = TEXTURES.SAVE_GAME_HEX,
  159. action = function()
  160. game_save()
  161. alert("succesfully saved!")
  162. end
  163. } or false,
  164. false,
  165. {
  166. texture = TEXTURES.LOAD_GAME_HEX,
  167. action = function()
  168. local save = am.load_state("save", "json")
  169. if save then
  170. win.scene:remove("menu")
  171. game_init(save)
  172. else
  173. alert("no saved games")
  174. end
  175. end
  176. },
  177. false and {
  178. texture = TEXTURES.MAP_EDITOR_HEX,
  179. action = function() alert("not yet :)") end
  180. },
  181. include_unpause_option and {
  182. texture = TEXTURES.UNPAUSE_HEX,
  183. action = function() unpause(win.scene("menu")) end
  184. } or false,
  185. false and {
  186. texture = TEXTURES.SETTINGS_HEX,
  187. action = function() alert("not yet :)") end
  188. },
  189. {
  190. texture = TEXTURES.QUIT_HEX,
  191. action = function() win:close() end
  192. },
  193. false
  194. }
  195. -- calculate the dimensions of the whole grid
  196. local spacing = 150
  197. local grid_width = 6
  198. local grid_height = 2
  199. local hhs = hex_horizontal_spacing(spacing)
  200. local hvs = hex_vertical_spacing(spacing)
  201. local grid_pixel_width = grid_width * hhs
  202. local grid_pixel_height = grid_height * hvs
  203. local pixel_offset = vec2(-grid_pixel_width/2, win.bottom + hex_height(spacing)/2 + 20)
  204. local map = hex_rectangular_map(grid_width, grid_height, HEX_ORIENTATION.POINTY)
  205. local group = am.group()
  206. local option_index = 1
  207. for i,_ in pairs(map) do
  208. for j,_ in pairs(map[i]) do
  209. local hex = vec2(i, j)
  210. local position = hex_to_pixel(hex, vec2(spacing), HEX_ORIENTATION.POINTY)
  211. local option = options[option_index]
  212. local texture = option and option.texture or TEXTURES.SHADED_HEX
  213. local color = option and COLORS.TRANSPARENT or vec4(0.3)
  214. local node = am.translate(position)
  215. ^ pack_texture_into_sprite(texture, texture.width, texture.height, color)
  216. hex_map_set(map, i, j, {
  217. node = node,
  218. option = option
  219. })
  220. local tile = hex_map_get(map, i, j)
  221. local selected = false
  222. node:action(function(self)
  223. local mouse = win:mouse_position()
  224. local hex_ = pixel_to_hex(mouse - pixel_offset, vec2(spacing), HEX_ORIENTATION.POINTY)
  225. if tile.option then
  226. if hex == hex_ then
  227. if not selected then
  228. play_sfx(SOUNDS.SELECT1)
  229. end
  230. selected = true
  231. tile.node"sprite".color = vec4(1)
  232. if win:mouse_pressed("left") then
  233. tile.option.action()
  234. end
  235. else
  236. selected = false
  237. tile.node"sprite".color = COLORS.TRANSPARENT
  238. end
  239. end
  240. end)
  241. group:append(node)
  242. option_index = option_index + 1
  243. end
  244. end
  245. return am.translate(pixel_offset) ^ group
  246. end
  247. function main_scene(do_backdrop, do_logo)
  248. local group = am.group()
  249. if do_backdrop then
  250. local map = hex_hexagonal_map(30)
  251. local hex_backdrop = (am.rotate(0) ^ am.group()):tag"hex_backdrop"
  252. for i,_ in pairs(map) do
  253. for j,n in pairs(map[i]) do
  254. local color = map_elevation_color(n)
  255. color = color{a=color.a - 0.1}
  256. local node = am.translate(hex_to_pixel(vec2(i, j), vec2(HEX_SIZE)))
  257. ^ am.circle(vec2(0), HEX_SIZE, vec4(0), 6)
  258. node"circle":action(am.tween(0.6, { color = color }))
  259. hex_backdrop:append(node)
  260. end
  261. end
  262. group:append(hex_backdrop)
  263. else
  264. group:append(
  265. pack_texture_into_sprite(TEXTURES.CURTAIN, win.width, win.height)
  266. )
  267. end
  268. -- version/author info
  269. group:append(
  270. am.translate(win.right - 10, win.bottom + 10)
  271. ^ am.text(string.format("v%s, by %s", version, author), COLORS.WHITE, "right", "bottom")
  272. )
  273. group:append(
  274. make_sound_toggle_node(settings.sound_on)
  275. )
  276. if do_logo then
  277. local position = vec2(0, win.top - 20 - TEXTURES.LOGO.height/2)
  278. local logo =
  279. am.translate(position)
  280. ^ pack_texture_into_sprite(TEXTURES.LOGO, TEXTURES.LOGO.width, TEXTURES.LOGO.height)
  281. local selected = false
  282. logo:action(function(self)
  283. local mouse = win:mouse_position()
  284. if math.distance(mouse, position) < TEXTURES.LOGO.height/2 then
  285. selected = true
  286. self"sprite".color = vec4(1)
  287. if win:mouse_pressed("left") then
  288. vplay_sfx(math.random(1000000000))
  289. end
  290. else
  291. selected = false
  292. self"sprite".color = vec4(0.95)
  293. end
  294. end)
  295. group:append(logo)
  296. end
  297. group:append(make_main_scene_toolbelt())
  298. group:action(main_action)
  299. return group:tag"menu"
  300. end
  301. win.scene = am.group(
  302. main_scene(true, true)
  303. )
  304. play_track(SOUNDS.MAIN_THEME)
  305. noglobals()