| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408 |
- local Jellyfish = class()
- Jellyfish.color = { 100, 0, 200 }
- Jellyfish.thrust = 150
- Jellyfish.gravityStrength = 20
- Jellyfish.turnFactor = 2
- Jellyfish.pushThreshold = 200
- Jellyfish.controllerDeadZone = .25
- Jellyfish.lineWidth = 4
- function Jellyfish:init(input)
- self.input = input
- self.lastPressed = nil
- self.lastTriggerValues = {left = 0, right = 0}
- self.controlScheme = 1
- self.x = 400
- self.y = 450
- self.speed = 0
- self.direction = -math.pi / 2
- self.gravity = 0
- self.tentacleDistance = 1
- self.curves = {}
- self.curves.top = love.math.newBezierCurve(
- 40, 30,
- 50, 0,
- 40, -20,
- 20, -30,
- 1, -30
- )
- self.curves.topMirror = love.math.newBezierCurve(
- 40, 30,
- 50, 0,
- 40, -20,
- 20, -30,
- 1, -30
- )
- self.curves.bottom = love.math.newBezierCurve(
- -40, 30,
- -30, 26,
- -20, 22,
- -10, 20,
- -1, 20
- )
- self.curves.bottomMirror = love.math.newBezierCurve(
- -40, 30,
- -30, 26,
- -20, 22,
- -10, 20,
- -1, 20
- )
- self.tentacles = {}
- for i = 1, 4 do
- local points = { self.x, self.y }
- for j = 1, 9 do
- table.insert(points, self.x)
- table.insert(points, self.y)
- end
- local curve = love.math.newBezierCurve(points)
- local tentacle = { points = points, curve = curve }
- self.tentacles[i] = tentacle
- end
- self.outerLipStasis = { self.curves.top:getControlPoint(1) }
- self.outerLipOpenX = self.outerLipStasis[1] + 10
- self.outerLipClosedX = self.outerLipStasis[1] - 10
- self.outerLipX = self.outerLipStasis[1]
- self.innerWaterLevel = 0
- self.currentState = 'none'
- self.sounds = {
- open = love.audio.newSource('sound/in.ogg'),
- close = love.audio.newSource('sound/out.ogg')
- }
- end
- function Jellyfish:update(dt)
- if hud.tutorial then
- self.curves.top:setControlPoint(1, self.outerLipX, self.outerLipStasis[2])
- self.curves.bottom:setControlPoint(1, -self.outerLipX + 1, self.outerLipStasis[2])
- return
- end
- local state = self:getState()
- if state == 'open' then
- if self.currentState ~= 'open' then
- self.currentState = 'open'
- if self.sounds.open:isPlaying() then
- self.sounds.open:rewind()
- else
- self.sounds.open:play()
- end
- local volumeFactor = -math.min(0, self.outerLipX - self.outerLipStasis[1]) / (self.outerLipOpenX - self.outerLipStasis[1])
- self.sounds.open:setVolume(.5 + volumeFactor * .5)
- end
- self.outerLipX = math.lerp(self.outerLipX, self.outerLipOpenX, math.min(6 * dt, 1))
- -- Figure out how open we are and fill ourselves with water
- local openFactor = (math.max(self.outerLipX - self.outerLipStasis[1], 0) / (self.outerLipOpenX - self.outerLipStasis[1]))
- self.innerWaterLevel = openFactor
- elseif state == 'close' then
- if self.currentState ~= 'close' then
- self.currentState = 'close'
- if self.sounds.close:isPlaying() then
- self.sounds.close:rewind()
- else
- self.sounds.close:play()
- end
- self.sounds.close:setVolume(self.innerWaterLevel)
- -- Push bubbles
- if next(bubbles.list) then
- if self.innerWaterLevel > .25 then
- table.each(bubbles.list, function(bubble)
- local dis, dir = math.vector(self.x, self.y, bubble.x, bubble.y)
- local angleDiff = math.abs(math.anglediff(dir, self.direction))
- local angleThreshold = 3 * math.pi / 4
- if dis < 35 and angleDiff < angleThreshold then
- dir = self.direction + math.pi
- angleDiff = 2 * math.pi
- end
- if dis < self.pushThreshold and angleDiff > angleThreshold then
- local magnitude = (1 - (dis / self.pushThreshold))
- bubble.speed = math.max(bubble.speed, self.thrust * magnitude * self.innerWaterLevel)
- bubble.direction = dir
- end
- end)
- end
- end
- end
- self.outerLipX = math.lerp(self.outerLipX, self.outerLipClosedX, math.min(6 * dt, 1))
- if self.innerWaterLevel > 0 then
- self.speed = math.max(self.speed, self.thrust * self.innerWaterLevel)
- self.innerWaterLevel = self.innerWaterLevel - math.min(self.innerWaterLevel, dt)
- end
- else
- self.outerLipX = math.lerp(self.outerLipX, self.outerLipStasis[1], math.min(2 * dt, 1))
- self.innerWaterLevel = self.innerWaterLevel - math.min(self.innerWaterLevel, dt)
- self.currentState = 'none'
- end
- self.curves.top:setControlPoint(1, self.outerLipX, self.outerLipStasis[2])
- self.curves.bottom:setControlPoint(1, -self.outerLipX + 1, self.outerLipStasis[2])
- self:setDirection(dt)
- self.speed = math.max(self.speed - math.min(self.speed * dt, self.thrust * dt), 0)
- if self.speed > 0 then
- local dx, dy = math.dx(self.speed, self.direction), math.dy(self.speed, self.direction)
- self.x = self.x + dx * dt
- self.y = self.y + dy * dt
- end
- if self.speed > self.thrust / 2 then
- self.gravity = math.max(self.gravity - self.gravityStrength * dt, self.gravityStrength)
- else
- self.gravity = math.min(self.gravity + self.gravityStrength * dt, self.gravityStrength)
- end
- if self.gravity > 0 then
- self.y = self.y + self.gravity * dt
- end
- table.each(self.tentacles, function(tentacle, i)
- local points = tentacle.points
- local openFactor = (self.outerLipX - self.outerLipStasis[1]) / (self.outerLipOpenX - self.outerLipStasis[1])
- local x, y
- if i > 2 then
- local factor
- if i == 4 then
- factor = .75 + (.1 * openFactor)
- else
- factor = .25 + (.08 * openFactor)
- end
- x, y = self.curves.bottomMirror:evaluate(factor)
- else
- local curve = self.curves.bottom
- curve:translate(self.x, self.y)
- curve:rotate(self.direction + math.pi / 2, self.x, self.y)
- local factor
- if i == 1 then
- factor = .25 - (.1 * openFactor)
- else
- factor = .75 - (.08 * openFactor)
- end
- x, y = self.curves.bottom:evaluate(factor)
- curve:rotate(-self.direction - math.pi / 2, self.x, self.y)
- curve:translate(-self.x, -self.y)
- end
- points[1] = x
- points[2] = y
- for j = 3, #points, 2 do
- local px, py = points[j - 2], points[j - 1]
- local x, y = points[j], points[j + 1]
- local dis, dir = math.vector(px, py, x, y)
- local maxDis = self.tentacleDistance
- if dis > maxDis then
- points[j] = px + math.dx(maxDis, dir)
- points[j + 1] = py + math.dy(maxDis, dir)
- end
- if self.gravity > 0 then
- points[j + 1] = points[j + 1] + self.gravity * 2 * dt
- end
- end
- for j = 1, #points / 2 do
- tentacle.curve:setControlPoint(j, points[j * 2 - 1], points[j * 2])
- end
- table.each(bubbles.list, function(bubble)
- if math.distance(bubble.x, bubble.y, points[#points - 1], points[#points]) < bubble.size then
- bubbles:remove(bubble, true)
- bubbles:playSound()
- self.tentacleDistance = self.tentacleDistance + .25
- end
- end)
- end)
- local clamp = 35
- self.x = math.clamp(self.x, clamp, g.getWidth() - clamp)
- self.y = math.clamp(self.y, clamp, g.getHeight() - clamp)
- if self.input ~= 'mouse' then
- local left, right = self.input:getGamepadAxis('triggerleft'), self.input:getGamepadAxis('triggerright')
- if self.lastTriggerValues.left < .5 and left > .5 then
- self.lastPressed = 'left'
- elseif self.lastTriggerValues.right < .5 and right > .5 then
- self.lastPressed = 'right'
- end
- self.lastTriggerValues.left = self.input:getGamepadAxis('triggerleft')
- self.lastTriggerValues.right = self.input:getGamepadAxis('triggerright')
- end
- end
- local function reflect(px, py, x1, y1, x2, y2)
- local dx = x2 - x1
- local dy = y2 - y1
- local a = (dx ^ 2 - dy ^ 2) / (dx ^ 2 + dy ^ 2)
- local b = 2 * dx * dy / (dx ^ 2 + dy ^ 2)
- local xx = a * (px - x1) + b * (py - y1) + x1
- local yy = b * (px - x1) - a * (py - y1) + y1
- return xx, yy
- end
- function Jellyfish:draw(onlyBody)
- local points = {}
- local controlPoints = {}
- local alpha = (not onlyBody and hud.dead) and (1 - hud.deadFactor) or 1
- local function drawCurve(curve, mirror)
- curve:translate(self.x, self.y)
- curve:rotate(self.direction + math.pi / 2, self.x, self.y)
- local curvePoints = curve:render()
- for i = 1, #curvePoints do
- table.insert(points, curvePoints[i])
- end
- local dx, dy = math.dx(10, self.direction), math.dy(10, self.direction)
- local x1, y1, x2, y2 = self.x - dx, self.y - dy, self.x + dx, self.y + dy
- local ct = curve:getControlPointCount()
- for i = 1, ct do
- local x, y = curve:getControlPoint(ct - i + 1)
- local rx, ry = reflect(x, y, x1, y1, x2, y2)
- mirror:setControlPoint(i, rx, ry)
- end
- curvePoints = mirror:render()
- for i = 1, #curvePoints do
- table.insert(points, curvePoints[i])
- end
- local roughPoints = curve:render(1)
- for i = 1, #roughPoints, 2 do
- table.insert(controlPoints, roughPoints[i])
- table.insert(controlPoints, roughPoints[i + 1])
- end
- local roughPoints = mirror:render(1)
- for i = 1, #roughPoints, 2 do
- table.insert(controlPoints, roughPoints[i])
- table.insert(controlPoints, roughPoints[i + 1])
- end
- curve:rotate(-self.direction - math.pi / 2, self.x, self.y)
- curve:translate(-self.x, -self.y)
- end
- drawCurve(self.curves.top, self.curves.topMirror)
- drawCurve(self.curves.bottom, self.curves.bottomMirror)
- g.setColor(self.color[1], self.color[2], self.color[3], 80 * alpha)
- local triangles = love.math.triangulate(controlPoints)
- for i = 1, #triangles do
- g.polygon('fill', triangles[i])
- end
- g.setColor(self.color[1], self.color[2], self.color[3], 255 * alpha)
- g.setLineWidth(self.lineWidth)
- table.insert(points, points[1])
- table.insert(points, points[2])
- g.line(points)
- if onlyBody then return end
- g.setLineWidth(4)
- g.setLineJoin('none')
- for i = 1, #self.tentacles do
- local points = self.tentacles[i].curve:render(3)
- g.setColor(self.color[1], self.color[2], self.color[3], 200 * alpha ^ 3)
- g.line(points)
- g.setColor(200, 200, 0, 100 * alpha)
- g.setPointSize(4)
- g.point(points[#points - 1], points[#points])
- end
- g.setLineJoin('miter')
- end
- function Jellyfish:keypressed(key)
- if key == 'z' then self.lastPressed = 'left'
- elseif key == 'x' then self.lastPressed = 'right' end
- end
- function Jellyfish:mousepressed(x, y, b)
- if self.input == 'mouse' then
- self.lastPressed = b == 'l' and 'left' or (b == 'r' and 'right' or self.lastPressed)
- end
- end
- function Jellyfish:gamepadpressed(joystick, button)
- if joystick == self.input then
- if button == 'leftshoulder' then
- self.lastPressed = 'left'
- elseif button == 'rightshoulder' then
- self.lastPressed = 'right'
- elseif button == 'select' then
- self.controlScheme = 1 - self.controlScheme
- end
- end
- end
- function Jellyfish:getState()
- if self.input == 'mouse' then
- if (love.mouse.isDown('l') or love.keyboard.isDown('z')) and self.lastPressed == 'left' then
- return 'open'
- elseif (love.mouse.isDown('r') or love.keyboard.isDown('x')) and self.lastPressed == 'right' then
- return 'close'
- else
- return 'none'
- end
- else
- if (self.input:isGamepadDown('leftshoulder') or self.input:getGamepadAxis('triggerleft') > .5) and self.lastPressed == 'left' then
- return 'open'
- elseif (self.input:isGamepadDown('rightshoulder') or self.input:getGamepadAxis('triggerright') > .5) and self.lastPressed == 'right' then
- return 'close'
- else
- return 'none'
- end
- end
- end
- function Jellyfish:setDirection(dt)
- if self.input == 'mouse' then
- self.direction = math.anglerp(self.direction, math.direction(self.x, self.y, love.mouse.getPosition()), math.min(self.turnFactor * dt, 1))
- else
- local function getAxisDirection(axis)
- local x = self.input:getGamepadAxis(axis .. 'x')
- local y = self.input:getGamepadAxis(axis .. 'y')
- local dis, dir = math.vector(0, 0, x, y)
- if self.controlScheme == 1 then
- return dis > self.controllerDeadZone and dir or nil
- else
- if x > self.controllerDeadZone then return self.direction + math.pi / 2
- elseif x < -self.controllerDeadZone then return self.direction - math.pi / 2 end
- end
- end
- local targetDirection = getAxisDirection('left') or getAxisDirection('right')
- if targetDirection then
- self.direction = math.anglerp(self.direction, targetDirection, math.min(self.turnFactor * dt, 1))
- end
- end
- end
- return Jellyfish
|