Commits

benbeltran committed 52358e4

Add some NPCs and Moving platforms

Comments (0)

Files changed (17)

 
   --horizontal physics variables
   self.max_x_vel = 2.5      -- Maximum Velocity
-  self.x_acc = 3.0          -- Acceleration
-  self.x_decc = 2.0         -- Decceleration
+  self.x_acc = 48.0         -- Acceleration
+  self.x_decc = 48.0        -- Decceleration
   self.x_vel = 0.0          -- Current Velocity
   self.walk_time = 0.0      -- Total Walk time
   self.stop_time = 0.0      -- Total Stop time
-  self.dir = 'right'      -- Current direction.
+  self.dir = 'right'        -- Current direction.
+  self.accelerating = false -- accelerating/deccelerating flags.
+  self.deccelerating = false
 
   --vertical physics variables
   self.max_y_vel = 12.0       -- Maximum Air Velocity
         if (ev.dx > 0 and self:can_pass("right")) or (ev.dx < 0 and self:can_pass("left")) then
           self.x = self.x + ev.dx - sign(ev.dx)
         end
-        self.x_vel = 0
+        self.x_vel = 0.0
 
         if ev.dx > 0 then
           self.collider.left = true
         end
         self.y_vel = sign(ev.dy)
 
-        if ev.dx > 0 then
+        if ev.dy > 0 then
           self.collider.bottom = false
           self.collider.top = true
+          self.platform = nil
         else
           self.collider.bottom = true
           self.collider.top = false
+          self.platform = ev.other
         end
       else
         self.collider.bottom = false
         self.collider.top = false
+        self.platform = nil
       end
     end
   end)
     self.collider.top = false
     self.collider.left = false
     self.collider.right = false
+    self.platform = nil
   end)
 end
 
 
 function Actor:physics(dt)
   --move in x
-  self.x = math.floor(self.x + self.x_vel)
+  self:accelerate(dt)
+  self:deccelerate(dt)
+
+  -- My own movements
+  if self.dir == "right" then
+    self.x = math.ceil(self.x + self.x_vel)
+  else
+    self.x = math.floor(self.x + self.x_vel)
+  end
+
+  -- External movements
+  if self.platform then
+    if self.platform.dir == "right" then
+      self.x = math.ceil(self.x + self.platform.x_vel)
+    else
+      self.x = math.floor(self.x + self.platform.x_vel)
+    end
+  end
+
+  self:accelerate(dt)
+  self:deccelerate(dt)
   self:unstick("x")
 
   --move in y
 end
 
 function Actor:accelerate(dt)
+  if not self.accelerating then
+    return
+  end
+
   self.walk_time = self.walk_time + dt
   self.stop_time = 0
 
   if self.dir == "right" then
     if self:can_pass("right") then
-      self.x_vel = self.x_vel + (self.x_acc * self.walk_time * self:get_friction())
+      self.x_vel = self.x_vel + self.x_acc * dt / 2
       if self.x_vel > self.max_x_vel then
         self.x_vel = self.max_x_vel
       end
     else
-      self.x_vel = 0
+      self.x_vel = 0.0
       self.walk_time = 0
     end
   else
     if self:can_pass("left") then
-      self.x_vel = self.x_vel - (self.x_acc * self.walk_time * self:get_friction())
+      self.x_vel = self.x_vel - self.x_acc * dt / 2
       if self.x_vel < -self.max_x_vel then
         self.x_vel = -self.max_x_vel
       end
     else
-      self.x_vel = 0
+      self.x_vel = 0.0
       self.walk_time = 0
     end
   end
 end
 
 function Actor:deccelerate(dt)
+  if not self.deccelerating then
+    return
+  end
+
   self.stop_time = self.stop_time + dt
   self.walk_time = 0
 
-
   if self.x_vel ~= 0 then
     if self.x_vel > 0 then
       if self.x_vel > self.max_x_vel then
         self.x_vel = self.max_x_vel
       end
 
-      self.x_vel = self.x_vel - (self.x_decc * self.stop_time * self:get_friction())
+      self.x_vel = self.x_vel - self.x_decc * dt / 2
 
       if self.x_vel < 0 then
-        self.x_vel = 0
+        self.x_vel = 0.0
       end
     elseif self.x_vel < 0 then
       if self.x_vel < -self.max_x_vel then
         self.x_vel = -self.max_x_vel
       end
 
-      self.x_vel = self.x_vel + (self.x_decc * self.stop_time * self:get_friction())
+      self.x_vel = self.x_vel + self.x_decc * dt / 2
 
       if self.x_vel > 0 then
-        self.x_vel = 0
+        self.x_vel = 0.0
       end
     else
-      self.x_vel = 0
+      self.x_vel = 0.0
     end
   end
 end

actors/cat-test.lua

---NOTE: this cat is hella buggy. No clue why and I haven't checked
---but first: y is not centered (I think, maybe it's just a giant gap)
---second: the whole acceleration is weird.
---I haven't touched this since the 90s, but it's crazy.
-
-Cat = class('Cat', Actor)
-
-function Cat:initialize(x,y,image)
-  Actor.initialize(self, x, y, image)
-
-  self.name = "Cat"
-
-  --define the individual sprite width and height
-  self.sprite_width = 64
-  self.sprite_height = 64
-
-  self:create_animation()
-
-  --Redefine bounding box.
-  self.box.top = -self.sprite_height / 2
-  self.box.bottom = self.sprite_height
-  self.box.left = 24
-  self.box.right = 24
-
-  --horizontal_physics
-  self.max_x_vel = 3
-end
-
-function Cat:create_animation()
-  local g = anim8.newGrid(self.sprite_width,self.sprite_height, self.image:getWidth(), self.image:getHeight())
-  self.animation = anim8.newAnimation('loop', g(1,1, 2,1, 1,1, 3,1), 0.1)
-  self.idle = anim8.newAnimation('once', g('1,1'), 0.1)
-end
-
-function Cat:draw()
-  if self.dir == "right" then
-    self.animation:draw(self.image, self:real_x(), self:real_y(), 0, self.scale, self.scale, self.sprite_width/2, self.sprite_height/2)
-  else
-    self.animation:draw(self.image, self:real_x(), self:real_y(), 0, -self.scale, self.scale, self.sprite_width/2, self.sprite_height/2)
-  end
-  self:draw_bounding_box()
-end
-
-function Cat:update(dt)
-  if not self:can_pass("right", self.x+1, self.y) and self.dir == "right" then
-    self:flip('left')
-    self.dir = "left"
-    self.walk_time = dt
-  end
-
-  if not self:can_pass("left", self.x-1, self.y) and self.dir == "left" then
-    self:flip("right")
-    self.dir = "right"
-    self.walk_time = dt
-  end
-
-  self:accelerate(dt)
-  self:physics(dt)
-  self.animation:update(dt)
-end
+--NOTE: this cat is hella buggy. No clue why and I haven't checked
+--but first: y is not centered (I think, maybe it's just a giant gap)
+--second: the whole acceleration is weird.
+--I haven't touched this since the 90s, but it's crazy.
+
+local CatActor = class('CatActor', Actor)
+
+function CatActor:initialize(x,y,image)
+
+  -- Override default
+  local sent_image = sent_image or "assets/images/sprites/anim/cat_64x64.png"
+
+  Actor.initialize(self, x, y, sent_image)
+
+  self.name = "Cat"
+
+  --define the individual sprite width and height
+  self.sprite_width = 64
+  self.sprite_height = 64
+
+  self:create_animation()
+
+  --Redefine bounding box.
+  self.box.top = -22
+  self.box.bottom = 32
+  self.box.left = 8
+  self.box.right = 8
+
+  --horizontal_physics
+  self.max_x_vel = 3
+  self.accelerating = true
+end
+
+function CatActor:create_animation()
+  local g = anim8.newGrid(self.sprite_width,self.sprite_height, self.image:getWidth(), self.image:getHeight())
+  self.animation = anim8.newAnimation('loop', g(1,1, 2,1, 1,1, 3,1), 0.1)
+  self.idle = anim8.newAnimation('once', g('1,1'), 0.1)
+end
+
+function CatActor:draw()
+  if self.dir == "right" then
+    self.animation:draw(self.image, self.x, self.y, 0, 1, 1, self.sprite_width/2, self.sprite_height/2)
+  else
+    self.animation:draw(self.image, self.x, self.y, 0, -1, 1, self.sprite_width/2, self.sprite_height/2)
+  end
+  self:draw_bounding_box()
+end
+
+function CatActor:update(dt)
+  if not self:can_pass("right", self.x+1, self.y) and self.dir == "right" then
+    self:flip('left')
+    self.dir = "left"
+    self.walk_time = dt
+  end
+
+  if not self:can_pass("left", self.x-1, self.y) and self.dir == "left" then
+    self:flip("right")
+    self.dir = "right"
+    self.walk_time = dt
+  end
+
+  Actor.update(self, dt)
+
+  self.animation:update(dt)
+end
+
+return CatActor

actors/moving_platform.lua

+local MovingPlatformActor = class('MovingPlatformActor', Actor)
+
+function MovingPlatformActor:initialize(x,y,image)
+
+  -- Override default
+  local sent_image = sent_image or "assets/images/sprites/static/platform_32x16.png"
+
+  Actor.initialize(self, x, y, sent_image)
+
+  self.name = "platform"
+
+  --platform stuff
+  self.x_origin = x
+  self.range = 64
+  self.immutable = true
+  self.gravity = 0.0
+  self.y_acc = 0.0
+
+  --define the individual sprite width and height
+  self.sprite_width = 64
+  self.sprite_height = 64
+
+  self.x_acc = 6
+  self.x_decc = 6
+
+  --horizontal_physics
+  self.max_x_vel = 2
+  self.accelerating = true
+end
+
+function MovingPlatformActor:update(dt)
+  if self.x > self.x_origin + self.range and self.dir == "right" then
+    self.dir = "left"
+    self:flip('left')
+    self.walk_time = dt
+  end
+
+  if self.x < self.x_origin - self.range and self.dir == "left" then
+    self.dir = "right"
+    self:flip("right")
+    self.walk_time = dt
+  end
+
+  Actor.update(self, dt)
+end
+
+return MovingPlatformActor

actors/player.lua

 
   self.camera_speed = 2
 
-  self.x_acc = 2.5
-  self.x_decc = 3
+  self.x_acc = 16.0
+  self.x_decc = 16.0
 
   self.max_x_vel = 5      -- Maximum Velocity
   self.max_jump_time = 0.05
      and not (love.joystick.getAxis(1,1) > 0.5) and not (love.joystick.getAxis(1,1) < -0.5)
      and not love.joystick.isDown(1,3) and not love.joystick.isDown(1,4) then
     if self.x_vel ~= 0 then
-      self:deccelerate(dt)
+      self.accelerating = false
+      self.deccelerating = true
     end
   else
     self.stop_time = 0
-    self:accelerate(dt)
+    self.accelerating = true
+    self.deccelerating = false
   end
 
   self:physics(dt)

assets/images/sprites/anim/cat-64-64.png

Removed
Old image

assets/images/sprites/anim/cat_64x64.png

Added
New image

assets/images/sprites/static/grass_blu_16x16.png

Old
Old image
New
New image

assets/images/sprites/static/grass_grn_16x16.png

Old
Old image
New
New image

assets/images/sprites/static/grass_red_16x16.png

Old
Old image
New
New image

assets/images/sprites/static/platform_32x16.png

Added
New image

assets/maps/cowtest.tmx

  </layer>
  <layer name="MG" width="100" height="50">
   <data encoding="base64" compression="zlib">
-   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
+   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
   </data>
  </layer>
  <layer name="FG" width="100" height="50">
  </layer>
  <layer name="Collision" width="100" height="50">
   <data encoding="base64" compression="zlib">
-   eJzt10EOgzAMRFEuC9z/Bt1mU1LHiWdI/5NmiWo8ihqOw8vVZBdXIG6cZxsR6cLxnZ1nG0EfXqJ9uPVFH/Sx2i59ROc4m7hb2cfM7jJz3E3cvem8/HMfVb9bgT50zk7cze5D3efdiTP1/8HTTCN6Z6PqnGTuPm59fDPzrr7qnuIyR3TezLNunTjMoKDswyFu1PtQZ9b+ZlHvQx036n2o46b3XZHJE3UPrp3Rx299VPVDH5rzon6HnZKhnn3HjFDPTAghhBBSFdRh9wAAAAAAAAAAAADg6wONrnBT
+   eJzt10EOgzAQQ1EuC9z/Bt1mU9LJJGOT/id5WdXEikqPw8vVZBdXIG6cu42IbOH4zM7dRrCHl+gebnuxB3ustsse0R5nE3cr95i5XabH3cTdm+7LP+9R9b0V2EPn7MTd7D3Ue96dOFP/Hjx1GtG7G1X3JPPu47bHNzPf1Ve9p7j0iPbNfNZtE4cOCso9HOJGfR7quFGfhzpu1Oehjpve/4pMnqh3cN2MPX7bo2of9tDcF/Uz7JQMdfcdM0LdmRBCCCGkKqjD2QMAAAAAAAAAAACArw+pH2+H
   </data>
  </layer>
  <objectgroup name="Objects" width="100" height="50">
-  <object name="Walker" type="SPWN" x="304" y="384" width="16" height="16">
+  <object name="Cat" type="SPAWN" x="304" y="368" width="16" height="16">
    <properties>
-    <property name="spawn" value="walker"/>
+    <property name="spawn" value="cat"/>
    </properties>
   </object>
   <object name="Player" type="SPAWN" x="64" y="256" width="16" height="16">
     <property name="spawn" value="grass"/>
    </properties>
   </object>
+  <object name="Moving Platform" type="SPAWN" x="272" y="272" width="16" height="16">
+   <properties>
+    <property name="spawn" value="moving_platform"/>
+   </properties>
+  </object>
  </objectgroup>
 </map>
   end
 
   function self.collider.shouldCollide(actor1, actor2)
+    if actor1.dying or actor2.dying then
+      return false
+    end
+
     if actor1.skip_same_collision and actor2.skip_same_collision then
       return false
     end
+
     return true
   end
 end

lib/vendor/anim8.lua

+-- anim8 v1.0.0 - 2012-02
+-- Copyright (c) 2011 Enrique García Cota
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+local Grid = {}
+
+local _frames = {}
+
+local function assertPositiveInteger(value, name)
+  if type(value) ~= 'number' then error(("%s should be a number, was %q"):format(name, tostring(value))) end
+  if value < 1 then error(("%s should be a positive number, was %d"):format(name, value)) end
+  if value ~= math.floor(value) then error(("%s should be an integer, was %d"):format(name, value)) end
+end
+
+local function createFrame(self, x, y)
+  local fw, fh = self.frameWidth, self.frameHeight
+  return love.graphics.newQuad(
+    self.left + (x-1) * fw + x * self.border,
+    self.top  + (y-1) * fh + y * self.border,
+    fw,
+    fh,
+    self.imageWidth,
+    self.imageHeight
+  )
+end
+
+local function getGridKey(...)
+  return table.concat( {...} ,'-' )
+end
+
+
+local function getOrCreateFrame(self, x, y)
+  if x < 1 or x > self.width or y < 1 or y > self.height then
+    error(("There is no frame for x=%d, y=%d"):format(x, y))
+  end
+  local key = self._key
+  _frames[key]       = _frames[key]       or {}
+  _frames[key][x]    = _frames[key][x]    or {}
+  _frames[key][x][y] = _frames[key][x][y] or createFrame(self, x, y)
+  return _frames[key][x][y]
+end
+
+local function parseInterval(str)
+  str = str:gsub(' ', '')
+  local min, max = str:match("^(%d+)-(%d+)$")
+  if not min then
+    min = str:match("^%d+$")
+    max = min
+  end
+  assert(min and max, ("Could not parse interval from %q"):format(str))
+  min, max = tonumber(min), tonumber(max)
+  local step = min <= max and 1 or -1
+  return min, max, step
+end
+
+local function parseIntervals(str)
+  local left, right = str:match("(.+),(.+)")
+  assert(left and right, ("Could not parse intervals from %q"):format(str))
+  local minx, maxx, stepx = parseInterval(left)
+  local miny, maxy, stepy = parseInterval(right)
+  return minx, maxx, stepx, miny, maxy, stepy
+end
+
+local function parseFrames(self, args, result, position)
+  local current = args[position]
+  local kind = type(current)
+
+  if kind == 'number' then
+
+    result[#result + 1] = getOrCreateFrame(self, current, args[position + 1])
+    return position + 2
+
+  elseif kind == 'string' then
+
+    local minx, maxx, stepx, miny, maxy, stepy  = parseIntervals(current)
+    for x = minx, maxx, stepx do
+      for y = miny, maxy, stepy do
+        result[#result+1] = getOrCreateFrame(self,x,y)
+      end
+    end
+
+    return position + 1
+
+  else
+
+    error(("Invalid type: %q (%s)"):format(kind, tostring(args[position])))
+
+  end
+end
+
+function Grid:getFrames(...)
+  local args = {...}
+  local length = #args
+  local result = {}
+  local position = 1
+
+  while position <= length do
+    position = parseFrames(self, args, result, position)
+  end
+
+  return result
+end
+
+local Gridmt = {
+  __index = Grid,
+  __call  = Grid.getFrames
+}
+
+local function newGrid(frameWidth, frameHeight, imageWidth, imageHeight, left, top, border)
+  assertPositiveInteger(frameWidth,  "frameWidth")
+  assertPositiveInteger(frameHeight, "frameHeight")
+  assertPositiveInteger(imageWidth,  "imageWidth")
+  assertPositiveInteger(imageHeight, "imageHeight")
+
+  left   = left   or 0
+  top    = top    or 0
+  border = border or 0
+
+  local key  = getGridKey(frameWidth, frameHeight, imageWidth, imageHeight, left, top, border)
+
+  local grid = setmetatable(
+    { frameWidth  = frameWidth,
+      frameHeight = frameHeight,
+      imageWidth  = imageWidth,
+      imageHeight = imageHeight,
+      left        = left,
+      top         = top,
+      border      = border,
+      width       = math.floor(imageWidth/frameWidth),
+      height      = math.floor(imageHeight/frameHeight),
+      _key        = key
+    },
+    Gridmt
+  )
+  return grid
+end
+
+-----------------------------------------------------------
+
+local Animation = {}
+
+local function cloneArray(arr)
+  local result = {}
+  for i=1,#arr do result[i] = arr[i] end
+  return result
+end
+
+local function parseDelays(delays)
+  local parsedDelays = {}
+  local tk,min,max,step
+  for k,v in pairs(delays) do
+    tk = type(k)
+    if     tk == "number"
+      then parsedDelays[k] = v
+    elseif tk == "string" then
+      min, max, step = parseInterval(k)
+      for i = min,max,step do parsedDelays[i] = v end
+    else
+      error(("Unexpected delay key: [%s]. Expected a number or a string"):format(tostring(k)))
+    end
+  end
+  return parsedDelays
+end
+
+local function repeatValue(value, times)
+  local result = {}
+  for i=1,times do result[i] = value end
+  return result
+end
+
+local function createDelays(frames, defaultDelay, delays)
+  local maxFrames = #frames
+  local result = repeatValue(defaultDelay, maxFrames)
+  for i,v in pairs(parseDelays(delays)) do
+    if i > maxFrames then
+      error(("The delay value %d is too high; there are only %d frames"):format(i, maxFrames))
+    end
+    result[i] = v
+  end
+  return result
+end
+
+local animationModes = {
+  loop   = function(self) self.position = 1 end,
+  once   = function(self) self.position = #self.frames end,
+  bounce = function(self)
+    self.direction = self.direction * -1
+    self.position = self.position + self.direction + self.direction
+  end
+}
+
+local Animationmt = { __index = Animation }
+
+local function newAnimation(mode, frames, defaultDelay, delays)
+  delays = delays or {}
+  assert(animationModes[mode], ("%q is not a valid mode"):format(tostring(mode)))
+  assert(type(defaultDelay) == 'number' and defaultDelay > 0, "defaultDelay must be a positive number" )
+  assert(type(delays) == 'table', "delays must be a table or nil")
+  return setmetatable({
+      mode        = mode,
+      frames      = cloneArray(frames),
+      padPosition = animationModes[mode],
+      delays      = createDelays(frames, defaultDelay, delays),
+      timer       = 0,
+      position    = 1,
+      direction   = 1,
+      status      = "playing"
+    },
+    Animationmt
+  )
+end
+
+function Animation:update(dt)
+  if self.status ~= "playing" then return end
+
+  self.timer = self.timer + dt
+
+  while self.timer > self.delays[self.position] do
+    self.timer = self.timer - self.delays[self.position]
+    self.position = self.position + self.direction
+    if self.position < 1 or self.position > #self.frames then
+      self:padPosition()
+    end
+  end
+end
+
+function Animation:pause()
+  self.status = "paused"
+end
+
+function Animation:resume()
+  self.status = "playing"
+end
+
+function Animation:gotoFrame(position)
+  self.position = position
+end
+
+function Animation:draw(image, x, y, r, sx, sy, ox, oy)
+  local frame = self.frames[self.position]
+  love.graphics.drawq(image, frame, x, y, r, sx, sy, ox, oy)
+end
+
+-----------------------------------------------------------
+
+local anim8 = {
+  newGrid      = newGrid,
+  newAnimation = newAnimation
+}
+return anim8
 require('lib/vendor/Beetle')
 require('lib/vendor/middleclass')
 gamera = require('lib/vendor/gamera')
-Stateful = require('lib/vendor/stateful')
-AdvTileLoader = require('lib/vendor/AdvancedTiledLoader/Loader')
 table.inspect = require('lib/vendor/inspect')
 bump = require('lib/vendor/bump')
+anim8 = require('lib/vendor/anim8')
+Stateful = require('lib/vendor/stateful')
+AdvTileLoader = require('lib/vendor/AdvancedTiledLoader/Loader')
 CustomEvent = require('lib/vendor/custom_event')
 CustomEventSupport = require('lib/vendor/custom_event_support')
 
 Actor = require('actors/actor')
 Player = require('actors/player')
 GrassActor = require('actors/grass')
+CatActor = require('actors/cat')
+MovingPlatformActor = require('actors/moving_platform')
 
 entities = {}
 
 function MapManager:initialize_objects()
   for i, v in ipairs(self.map("Objects").objects) do
     if v.type == "SPAWN" then
+      -- TODO: Do this in a smarter manner. Parse and check existence.
+      -- Actually, abstract this to a factory. ActorFactory.spawn(v)
+      -- I don't know how to handle stuff like v.properties. I think
+      -- I'll have actors receive a properties table and just roll with that
+      -- send (v.x, v.y, v.properties)
       if v.properties.spawn == "player" then
         local player = Player:new(v.x, v.y)
         camera:setPosition(v.x, v.y)
       if v.properties.spawn == "grass" then
         local grass = GrassActor:new(v.x, v.y, v.properties.color)
       end
+      if v.properties.spawn == "cat" then
+        local cat = CatActor(v.x, v.y)
+      end
+      if v.properties.spawn == "moving_platform" then
+        local moving_platform = MovingPlatformActor(v.x, v.y)
+      end
     end
   end
 end
 function GameScreen:update(dt)
   beetle.update("Actors", table.getn(self.actor_manager.actors))
   self.map_manager:update(dt)
-  self.collider.collide()
   self.actor_manager:update(dt)
+  self.collider.collide()
 end
 
 function GameScreen:exitedState()