jellyfish.lua 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. local Jellyfish = class()
  2. Jellyfish.color = { 100, 0, 200 }
  3. Jellyfish.thrust = 150
  4. Jellyfish.gravityStrength = 20
  5. Jellyfish.turnFactor = 2
  6. Jellyfish.pushThreshold = 200
  7. Jellyfish.controllerDeadZone = .25
  8. Jellyfish.lineWidth = 4
  9. function Jellyfish:init(input)
  10. self.input = input
  11. self.lastPressed = nil
  12. self.lastTriggerValues = {left = 0, right = 0}
  13. self.controlScheme = 1
  14. self.x = 400
  15. self.y = 450
  16. self.speed = 0
  17. self.direction = -math.pi / 2
  18. self.gravity = 0
  19. self.tentacleDistance = 1
  20. self.curves = {}
  21. self.curves.top = love.math.newBezierCurve(
  22. 40, 30,
  23. 50, 0,
  24. 40, -20,
  25. 20, -30,
  26. 1, -30
  27. )
  28. self.curves.topMirror = love.math.newBezierCurve(
  29. 40, 30,
  30. 50, 0,
  31. 40, -20,
  32. 20, -30,
  33. 1, -30
  34. )
  35. self.curves.bottom = love.math.newBezierCurve(
  36. -40, 30,
  37. -30, 26,
  38. -20, 22,
  39. -10, 20,
  40. -1, 20
  41. )
  42. self.curves.bottomMirror = love.math.newBezierCurve(
  43. -40, 30,
  44. -30, 26,
  45. -20, 22,
  46. -10, 20,
  47. -1, 20
  48. )
  49. self.tentacles = {}
  50. for i = 1, 4 do
  51. local points = { self.x, self.y }
  52. for j = 1, 9 do
  53. table.insert(points, self.x)
  54. table.insert(points, self.y)
  55. end
  56. local curve = love.math.newBezierCurve(points)
  57. local tentacle = { points = points, curve = curve }
  58. self.tentacles[i] = tentacle
  59. end
  60. self.outerLipStasis = { self.curves.top:getControlPoint(1) }
  61. self.outerLipOpenX = self.outerLipStasis[1] + 10
  62. self.outerLipClosedX = self.outerLipStasis[1] - 10
  63. self.outerLipX = self.outerLipStasis[1]
  64. self.innerWaterLevel = 0
  65. self.currentState = 'none'
  66. self.sounds = {
  67. open = love.audio.newSource('sound/in.ogg'),
  68. close = love.audio.newSource('sound/out.ogg')
  69. }
  70. end
  71. function Jellyfish:update(dt)
  72. if hud.tutorial then
  73. self.curves.top:setControlPoint(1, self.outerLipX, self.outerLipStasis[2])
  74. self.curves.bottom:setControlPoint(1, -self.outerLipX + 1, self.outerLipStasis[2])
  75. return
  76. end
  77. local state = self:getState()
  78. if state == 'open' then
  79. if self.currentState ~= 'open' then
  80. self.currentState = 'open'
  81. if self.sounds.open:isPlaying() then
  82. self.sounds.open:rewind()
  83. else
  84. self.sounds.open:play()
  85. end
  86. local volumeFactor = -math.min(0, self.outerLipX - self.outerLipStasis[1]) / (self.outerLipOpenX - self.outerLipStasis[1])
  87. self.sounds.open:setVolume(.5 + volumeFactor * .5)
  88. end
  89. self.outerLipX = math.lerp(self.outerLipX, self.outerLipOpenX, math.min(6 * dt, 1))
  90. -- Figure out how open we are and fill ourselves with water
  91. local openFactor = (math.max(self.outerLipX - self.outerLipStasis[1], 0) / (self.outerLipOpenX - self.outerLipStasis[1]))
  92. self.innerWaterLevel = openFactor
  93. elseif state == 'close' then
  94. if self.currentState ~= 'close' then
  95. self.currentState = 'close'
  96. if self.sounds.close:isPlaying() then
  97. self.sounds.close:rewind()
  98. else
  99. self.sounds.close:play()
  100. end
  101. self.sounds.close:setVolume(self.innerWaterLevel)
  102. -- Push bubbles
  103. if next(bubbles.list) then
  104. if self.innerWaterLevel > .25 then
  105. table.each(bubbles.list, function(bubble)
  106. local dis, dir = math.vector(self.x, self.y, bubble.x, bubble.y)
  107. local angleDiff = math.abs(math.anglediff(dir, self.direction))
  108. local angleThreshold = 3 * math.pi / 4
  109. if dis < 35 and angleDiff < angleThreshold then
  110. dir = self.direction + math.pi
  111. angleDiff = 2 * math.pi
  112. end
  113. if dis < self.pushThreshold and angleDiff > angleThreshold then
  114. local magnitude = (1 - (dis / self.pushThreshold))
  115. bubble.speed = math.max(bubble.speed, self.thrust * magnitude * self.innerWaterLevel)
  116. bubble.direction = dir
  117. end
  118. end)
  119. end
  120. end
  121. end
  122. self.outerLipX = math.lerp(self.outerLipX, self.outerLipClosedX, math.min(6 * dt, 1))
  123. if self.innerWaterLevel > 0 then
  124. self.speed = math.max(self.speed, self.thrust * self.innerWaterLevel)
  125. self.innerWaterLevel = self.innerWaterLevel - math.min(self.innerWaterLevel, dt)
  126. end
  127. else
  128. self.outerLipX = math.lerp(self.outerLipX, self.outerLipStasis[1], math.min(2 * dt, 1))
  129. self.innerWaterLevel = self.innerWaterLevel - math.min(self.innerWaterLevel, dt)
  130. self.currentState = 'none'
  131. end
  132. self.curves.top:setControlPoint(1, self.outerLipX, self.outerLipStasis[2])
  133. self.curves.bottom:setControlPoint(1, -self.outerLipX + 1, self.outerLipStasis[2])
  134. self:setDirection(dt)
  135. self.speed = math.max(self.speed - math.min(self.speed * dt, self.thrust * dt), 0)
  136. if self.speed > 0 then
  137. local dx, dy = math.dx(self.speed, self.direction), math.dy(self.speed, self.direction)
  138. self.x = self.x + dx * dt
  139. self.y = self.y + dy * dt
  140. end
  141. if self.speed > self.thrust / 2 then
  142. self.gravity = math.max(self.gravity - self.gravityStrength * dt, self.gravityStrength)
  143. else
  144. self.gravity = math.min(self.gravity + self.gravityStrength * dt, self.gravityStrength)
  145. end
  146. if self.gravity > 0 then
  147. self.y = self.y + self.gravity * dt
  148. end
  149. table.each(self.tentacles, function(tentacle, i)
  150. local points = tentacle.points
  151. local openFactor = (self.outerLipX - self.outerLipStasis[1]) / (self.outerLipOpenX - self.outerLipStasis[1])
  152. local x, y
  153. if i > 2 then
  154. local factor
  155. if i == 4 then
  156. factor = .75 + (.1 * openFactor)
  157. else
  158. factor = .25 + (.08 * openFactor)
  159. end
  160. x, y = self.curves.bottomMirror:evaluate(factor)
  161. else
  162. local curve = self.curves.bottom
  163. curve:translate(self.x, self.y)
  164. curve:rotate(self.direction + math.pi / 2, self.x, self.y)
  165. local factor
  166. if i == 1 then
  167. factor = .25 - (.1 * openFactor)
  168. else
  169. factor = .75 - (.08 * openFactor)
  170. end
  171. x, y = self.curves.bottom:evaluate(factor)
  172. curve:rotate(-self.direction - math.pi / 2, self.x, self.y)
  173. curve:translate(-self.x, -self.y)
  174. end
  175. points[1] = x
  176. points[2] = y
  177. for j = 3, #points, 2 do
  178. local px, py = points[j - 2], points[j - 1]
  179. local x, y = points[j], points[j + 1]
  180. local dis, dir = math.vector(px, py, x, y)
  181. local maxDis = self.tentacleDistance
  182. if dis > maxDis then
  183. points[j] = px + math.dx(maxDis, dir)
  184. points[j + 1] = py + math.dy(maxDis, dir)
  185. end
  186. if self.gravity > 0 then
  187. points[j + 1] = points[j + 1] + self.gravity * 2 * dt
  188. end
  189. end
  190. for j = 1, #points / 2 do
  191. tentacle.curve:setControlPoint(j, points[j * 2 - 1], points[j * 2])
  192. end
  193. table.each(bubbles.list, function(bubble)
  194. if math.distance(bubble.x, bubble.y, points[#points - 1], points[#points]) < bubble.size then
  195. bubbles:remove(bubble, true)
  196. bubbles:playSound()
  197. self.tentacleDistance = self.tentacleDistance + .25
  198. end
  199. end)
  200. end)
  201. local clamp = 35
  202. self.x = math.clamp(self.x, clamp, g.getWidth() - clamp)
  203. self.y = math.clamp(self.y, clamp, g.getHeight() - clamp)
  204. if self.input ~= 'mouse' then
  205. local left, right = self.input:getGamepadAxis('triggerleft'), self.input:getGamepadAxis('triggerright')
  206. if self.lastTriggerValues.left < .5 and left > .5 then
  207. self.lastPressed = 'left'
  208. elseif self.lastTriggerValues.right < .5 and right > .5 then
  209. self.lastPressed = 'right'
  210. end
  211. self.lastTriggerValues.left = self.input:getGamepadAxis('triggerleft')
  212. self.lastTriggerValues.right = self.input:getGamepadAxis('triggerright')
  213. end
  214. end
  215. local function reflect(px, py, x1, y1, x2, y2)
  216. local dx = x2 - x1
  217. local dy = y2 - y1
  218. local a = (dx ^ 2 - dy ^ 2) / (dx ^ 2 + dy ^ 2)
  219. local b = 2 * dx * dy / (dx ^ 2 + dy ^ 2)
  220. local xx = a * (px - x1) + b * (py - y1) + x1
  221. local yy = b * (px - x1) - a * (py - y1) + y1
  222. return xx, yy
  223. end
  224. function Jellyfish:draw(onlyBody)
  225. local points = {}
  226. local controlPoints = {}
  227. local alpha = (not onlyBody and hud.dead) and (1 - hud.deadFactor) or 1
  228. local function drawCurve(curve, mirror)
  229. curve:translate(self.x, self.y)
  230. curve:rotate(self.direction + math.pi / 2, self.x, self.y)
  231. local curvePoints = curve:render()
  232. for i = 1, #curvePoints do
  233. table.insert(points, curvePoints[i])
  234. end
  235. local dx, dy = math.dx(10, self.direction), math.dy(10, self.direction)
  236. local x1, y1, x2, y2 = self.x - dx, self.y - dy, self.x + dx, self.y + dy
  237. local ct = curve:getControlPointCount()
  238. for i = 1, ct do
  239. local x, y = curve:getControlPoint(ct - i + 1)
  240. local rx, ry = reflect(x, y, x1, y1, x2, y2)
  241. mirror:setControlPoint(i, rx, ry)
  242. end
  243. curvePoints = mirror:render()
  244. for i = 1, #curvePoints do
  245. table.insert(points, curvePoints[i])
  246. end
  247. local roughPoints = curve:render(1)
  248. for i = 1, #roughPoints, 2 do
  249. table.insert(controlPoints, roughPoints[i])
  250. table.insert(controlPoints, roughPoints[i + 1])
  251. end
  252. local roughPoints = mirror:render(1)
  253. for i = 1, #roughPoints, 2 do
  254. table.insert(controlPoints, roughPoints[i])
  255. table.insert(controlPoints, roughPoints[i + 1])
  256. end
  257. curve:rotate(-self.direction - math.pi / 2, self.x, self.y)
  258. curve:translate(-self.x, -self.y)
  259. end
  260. drawCurve(self.curves.top, self.curves.topMirror)
  261. drawCurve(self.curves.bottom, self.curves.bottomMirror)
  262. g.setColor(self.color[1], self.color[2], self.color[3], 80 * alpha)
  263. local triangles = love.math.triangulate(controlPoints)
  264. for i = 1, #triangles do
  265. g.polygon('fill', triangles[i])
  266. end
  267. g.setColor(self.color[1], self.color[2], self.color[3], 255 * alpha)
  268. g.setLineWidth(self.lineWidth)
  269. table.insert(points, points[1])
  270. table.insert(points, points[2])
  271. g.line(points)
  272. if onlyBody then return end
  273. g.setLineWidth(4)
  274. g.setLineJoin('none')
  275. for i = 1, #self.tentacles do
  276. local points = self.tentacles[i].curve:render(3)
  277. g.setColor(self.color[1], self.color[2], self.color[3], 200 * alpha ^ 3)
  278. g.line(points)
  279. g.setColor(200, 200, 0, 100 * alpha)
  280. g.setPointSize(4)
  281. g.point(points[#points - 1], points[#points])
  282. end
  283. g.setLineJoin('miter')
  284. end
  285. function Jellyfish:keypressed(key)
  286. if key == 'z' then self.lastPressed = 'left'
  287. elseif key == 'x' then self.lastPressed = 'right' end
  288. end
  289. function Jellyfish:mousepressed(x, y, b)
  290. if self.input == 'mouse' then
  291. self.lastPressed = b == 'l' and 'left' or (b == 'r' and 'right' or self.lastPressed)
  292. end
  293. end
  294. function Jellyfish:gamepadpressed(joystick, button)
  295. if joystick == self.input then
  296. if button == 'leftshoulder' then
  297. self.lastPressed = 'left'
  298. elseif button == 'rightshoulder' then
  299. self.lastPressed = 'right'
  300. elseif button == 'select' then
  301. self.controlScheme = 1 - self.controlScheme
  302. end
  303. end
  304. end
  305. function Jellyfish:getState()
  306. if self.input == 'mouse' then
  307. if (love.mouse.isDown('l') or love.keyboard.isDown('z')) and self.lastPressed == 'left' then
  308. return 'open'
  309. elseif (love.mouse.isDown('r') or love.keyboard.isDown('x')) and self.lastPressed == 'right' then
  310. return 'close'
  311. else
  312. return 'none'
  313. end
  314. else
  315. if (self.input:isGamepadDown('leftshoulder') or self.input:getGamepadAxis('triggerleft') > .5) and self.lastPressed == 'left' then
  316. return 'open'
  317. elseif (self.input:isGamepadDown('rightshoulder') or self.input:getGamepadAxis('triggerright') > .5) and self.lastPressed == 'right' then
  318. return 'close'
  319. else
  320. return 'none'
  321. end
  322. end
  323. end
  324. function Jellyfish:setDirection(dt)
  325. if self.input == 'mouse' then
  326. self.direction = math.anglerp(self.direction, math.direction(self.x, self.y, love.mouse.getPosition()), math.min(self.turnFactor * dt, 1))
  327. else
  328. local function getAxisDirection(axis)
  329. local x = self.input:getGamepadAxis(axis .. 'x')
  330. local y = self.input:getGamepadAxis(axis .. 'y')
  331. local dis, dir = math.vector(0, 0, x, y)
  332. if self.controlScheme == 1 then
  333. return dis > self.controllerDeadZone and dir or nil
  334. else
  335. if x > self.controllerDeadZone then return self.direction + math.pi / 2
  336. elseif x < -self.controllerDeadZone then return self.direction - math.pi / 2 end
  337. end
  338. end
  339. local targetDirection = getAxisDirection('left') or getAxisDirection('right')
  340. if targetDirection then
  341. self.direction = math.anglerp(self.direction, targetDirection, math.min(self.turnFactor * dt, 1))
  342. end
  343. end
  344. end
  345. return Jellyfish