I've got it working, so far - you can run and jump around, and in one (long gone) iteration, you could hit stuff with a sword and be hit back.
My problem is that my current code is a rather painful-to-modify mess, both in lua and c++ - I'm afraid to implement anything new, because I don't know how much of the existing behaviour code is going to have to change just to add, say, attacking. I've thought about it, and read a lot about programming in general, but I've yet to understand how I can better organize the logic.
For reference, here's the code for my main character - it allows Pip to run animations, walk, jump, fall in four directions, and collide with objects, but not much else
(I've also copied it fairly directly into c++, aside from having implemented almost identical behaviour about eight times in the past, with minor variances, already)
pip.lua
Code: Select all
require "physo"
--phys[ics]o[bject], common behaviour for gravity- and inertia-influenced objects
require "animation"
--too lazy to learn preexisting animation libraries, don't know if they support the features I want
Pip = {}
Pip.po = po.new_physo()
Pip.po.siz_x = 6-1
Pip.po.siz_y = 8-1
-- subtract 1 from sizes to get expected behaviour from pos+siz collision detection
Pip.off_x = 13
Pip.off_y = 24
-- off[set]; I don't align my sprites to the top-left of their grids in my spritesheets, so they need to be drawn at an offset
Pip.flip = false
Pip.po.terminal_vel = 3.75
Pip.po.gravity = 0.125
Pip.po.gravity_dir = 'd'
--gravity_dir[ection]; so things can fall u[p], r[ight], d[own] or l[eft]
Pip.po.a_friction = 0.125
Pip.po.g_friction = 0.25
Pip.def_physics = po.new_physo(Pip.po)
--def[ault], essentially a set of const values for Pip's physics
Pip.jump_gravity = 1/12
Pip.wall_gravity = 0.25
--I think I was going to implement wall-grabbing, not sure what this is for
Pip.jump_strength = 2.25
Pip.a_accel = 0.15625
Pip.g_accel = 0.25
Pip.run_max = 1.875
Pip.state = {}
Pip.state.jumping = false
Pip.state.moving = false
--used to make sure that if you're holding both direction keys, he'll only move in the direction of the last one hit
Pip.state.grounded = false
Pip.anim_frame = 1
Pip.draw_frame = 1
Pip.anim = {}
Pip.anim.idle = a.new_animation({1})
Pip.anim.run = a.new_animation({1,1,1,2,2,2,2,2,2,2,1,1,1,3,3,3,3,3,3,3})
Pip.anim.jump = a.new_animation({17})
Pip.cur_anim = Pip.anim.idle
Pip.update = function()
--po:is_blocked(dir): moves physo one pixel in the specified direction, tests for collisions, unmoves, returns test result
-- state end tests
if Pip.po:is_blocked('u') then
Pip.state.jumping = false
elseif Pip.po:is_blocked('d') then
Pip.state.jumping = false
end
--engine.key_pressed/engine.key_released: keeps track of which keys were only pressed/released on that specific frame
-- key presses
if engine.key_pressed['z'] then
if Pip.po:is_blocked('d') then
Pip.po.vel_y = -Pip.jump_strength
Pip.state.jumping = true
end
end
if engine.key_pressed['left'] then
Pip.state.moving = true
Pip.flip = true
end
if engine.key_pressed['right'] then
Pip.state.moving = true
Pip.flip = false
end
-- key releases
if engine.key_released['left'] then
if love.keyboard.isDown('right') then
Pip.flip = false
else
Pip.state.moving = false
end
end
if engine.key_released['right'] then
if love.keyboard.isDown('left') then
Pip.flip = true
else
Pip.state.moving = false
end
end
-- key helds
if love.keyboard.isDown('z') then
else
Pip.state.jumping = false
end
-- state effects
if Pip.state.moving then
if Pip.po:is_blocked('d') then
Pip.po.g_friction = 0
if Pip.flip then
if Pip.po.vel_x > -Pip.run_max then
Pip.po.vel_x = Pip.po.vel_x-Pip.g_accel
if Pip.po.vel_x < -Pip.run_max then
Pip.po.vel_x = -Pip.run_max
end
end
else
if Pip.po.vel_x < Pip.run_max then
Pip.po.vel_x = Pip.po.vel_x+Pip.g_accel
if Pip.po.vel_x > Pip.run_max then
Pip.po.vel_x = Pip.run_max
end
end
end
else
Pip.po.a_friction = 0
if Pip.flip then
if Pip.po.vel_x > -Pip.run_max then
Pip.po.vel_x = Pip.po.vel_x-Pip.a_accel
if Pip.po.vel_x < -Pip.run_max then
Pip.po.vel_x = -Pip.run_max
end
end
else
if Pip.po.vel_x < Pip.run_max then
Pip.po.vel_x = Pip.po.vel_x+Pip.a_accel
if Pip.po.vel_x > Pip.run_max then
Pip.po.vel_x = Pip.run_max
end
end
end
end
else
Pip.po.g_friction = Pip.def_physics.g_friction
Pip.po.a_friction = Pip.def_physics.a_friction
end
if Pip.state.jumping then
Pip.po.gravity = Pip.jump_gravity
if Pip.po.siz_y==8-1 then
Pip.po:resize('d',-1)
end
else
Pip.po.gravity = Pip.def_physics.gravity
if Pip.po.siz_y==8-1-1 then
Pip.po:resize('u',1)
end
end
-- animation setting
if Pip.state.moving then
if Pip.po:is_blocked('d') then
if Pip.cur_anim ~= Pip.anim.run then
Pip.cur_anim = Pip.anim.run
Pip.anim_frame = 1
end
else
if Pip.cur_anim == Pip.anim.run then
Pip.cur_anim = Pip.anim.idle
Pip.anim_frame = 1
end
end
else
Pip.cur_anim = Pip.anim.idle
end
if Pip.state.jumping then
if Pip.cur_anim ~= Pip.anim.jump then
Pip.cur_anim = Pip.anim.jump
Pip.anim_frame = 1
end
else
if Pip.cur_anim == Pip.anim.jump then
Pip.cur_anim = Pip.anim.idle
end
end
-- non-ai
Pip.anim_frame = Pip.anim_frame+1
if Pip.anim_frame > Pip.cur_anim.length then
Pip.cur_anim = Pip.cur_anim.next_anim
Pip.anim_frame = 1
end
Pip.draw_frame = Pip.cur_anim.frames[Pip.anim_frame].i
Pip.po:update()
end
(features remnants of an unusual commenting scheme that I dropped during later changes; it made code easier to understand, I think, but was itself very tedious to maintain, not to mention very inconsistent in how I handled indentation, branching, etc.)
Code: Select all
po = {}
PhysO = {}
PhysO.__index = PhysO
function po.new_physo(ref)
local physo = {}
setmetatable(physo,PhysO)
if ref then
physo.pos_x = ref.pos_x
physo.pos_y = ref.pos_y
physo.vel_x = ref.vel_x
physo.vel_y = ref.vel_y
physo.siz_x = ref.siz_x
physo.siz_y = ref.siz_y
physo.terminal_vel = ref.terminal_vel
physo.gravity = ref.gravity
physo.gravity_dir = ref.gravity_dir
physo.a_friction = ref.a_friction
physo.g_friction = ref.g_friction
else
physo.pos_x = 0
physo.pos_y = 0
physo.vel_x = 0
physo.vel_y = 0
physo.siz_x = 0
physo.siz_y = 0
physo.terminal_vel = 0
physo.gravity = 0
physo.gravity_dir = 'd'
physo.a_friction = 0
physo.g_friction = 0
end
return physo
end
function PhysO:update()
if engine.is_colliding(self) then
return
end
self.vel_x = self.vel_x+(self.gravity*po.block_ref[self.gravity_dir].x)
self.vel_y = self.vel_y+(self.gravity*po.block_ref[self.gravity_dir].y)
--block_ref can be found further down
-- it's a shortcut so that I can refer to directions (left,leftup,up,upright,...), and turn them into numerical values (left is -1x,0y, leftup is -1x,-1y, up is 0x,-1y, ...)
if self.gravity_dir=='l' then
if self.vel_x < -self.terminal_vel then
self.vel_x = -self.terminal_vel
end
elseif self.gravity_dir=='u' then
if self.vel_y < -self.terminal_vel then
self.vel_y = -self.terminal_vel
end
elseif self.gravity_dir=='r' then
if self.vel_x > self.terminal_vel then
self.vel_x = self.terminal_vel
end
elseif self.gravity_dir=='d' then
if self.vel_y > self.terminal_vel then
self.vel_y = self.terminal_vel
end
end
--TODO
-- test for gravity_dir; do not apply friction unless vel > terminal_vel
--apply friction (horizontal)
-- test if touching wall (0), determines ground_friction vs air_friction (g/a)
-- test if vel_y is negative/positive (1,n/p)
-- apply g_friction (2)
-- ensure velocity does not pass 0 (3)
-- else apply a_friction (2)
-- ensure velocity does not pass 0 (3)
--if self:is_blocked(self.vel_x,0) then -- 0g
-- if self.vel_y < 0 then -- 1gn
-- self.vel_y = self.vel_y + self.g_friction -- 2gn
-- if self.vel_y > 0 then -- 3gn
-- self.vel_y = 0 -- 3gn
-- end
-- elseif self.vel_y > 0 then -- 1gp
-- self.vel_y = self.vel_y - self.g_friction -- 2gp
-- if self.vel_y < 0 then -- 3gp
-- self.vel_y = 0 -- 3gp
-- end
-- end
--elseif self.vel_y < 0 then -- 1an
-- self.vel_y = self.vel_y + self.a_friction -- 2an
-- if self.vel_y > 0 then -- 3an
-- self.vel_y = 0 -- 3an
-- end
--elseif self.vel_y > 0 then -- 1ap
-- self.vel_y = self.vel_y - self.a_friction -- 2ap
-- if self.vel_y < 0 then -- 3ap
-- self.vel_y = 0 -- 3ap
-- end
--end
--apply friction (vertical)
-- see above
if self:is_blocked(0,self.vel_y) then -- 0g
if self.vel_x < 0 then -- 1gn
self.vel_x = self.vel_x + self.g_friction -- 2gn
if self.vel_x > 0 then -- 3gn
self.vel_x = 0 -- 3gn
end
elseif self.vel_x > 0 then -- 1gp
self.vel_x = self.vel_x - self.g_friction -- 2gp
if self.vel_x < 0 then -- 3gp
self.vel_x = 0 -- 3gp
end
end
elseif self.vel_x < 0 then -- 1an
self.vel_x = self.vel_x + self.a_friction -- 2an
if self.vel_x > 0 then -- 3an
self.vel_x = 0 -- 3an
end
elseif self.vel_x > 0 then -- 1ap
self.vel_x = self.vel_x - self.a_friction -- 2ap
if self.vel_x < 0 then -- 3ap
self.vel_x = 0 -- 3ap
end
end
--apply velocity (horizontal)
-- moves object according to x velocity, checks for collisions, and, if necessary, moves it backwards 1 pixel at a time until it is no longer colliding
self.pos_x = self.pos_x + self.vel_x
if engine.is_colliding(self) then
if self.vel_x < 0 then
while engine.is_colliding(self) do
self.pos_x = self.pos_x + 1
end
elseif self.vel_x > 0 then
while engine.is_colliding(self) do
self.pos_x = self.pos_x - 1
end
end
self.vel_x = 0
end
--apply velocity (vertical)
-- see above
self.pos_y = self.pos_y + self.vel_y
if engine.is_colliding(self) then
if self.vel_y < 0 then
while engine.is_colliding(self) do
self.pos_y = self.pos_y + 1
end
elseif self.vel_y > 0 then
while engine.is_colliding(self) do
self.pos_y = self.pos_y - 1
end
end
self.vel_y = 0
end
end
po.block_ref = {l ={x=-1,y= 0},
ul={x=-1,y=-1},
u ={x= 0,y=-1},
ur={x= 1,y=-1},
r ={x= 1,y= 0},
dr={x= 1,y= 1},
d ={x= 0,y= 1},
dl={x=-1,y= 1}}
function PhysO:is_blocked(x,y)
if type(x)=='number' then
if x < 0 then
if y < 0 then
dir = 'ul'
elseif y > 0 then
dir = 'dl'
else
dir = 'l'
end
elseif x > 0 then
if y < 0 then
dir = 'ur'
elseif y > 0 then
dir = 'dr'
else
dir = 'r'
end
else
if y < 0 then
dur = 'u'
elseif y > 0 then
dir = 'd'
else
return false
end
end
else
dir = x
end
self.pos_x = self.pos_x + po.block_ref[dir].x
self.pos_y = self.pos_y + po.block_ref[dir].y
if engine.is_colliding(self) then
self.pos_x = self.pos_x - po.block_ref[dir].x
self.pos_y = self.pos_y - po.block_ref[dir].y
return true
else
self.pos_x = self.pos_x - po.block_ref[dir].x
self.pos_y = self.pos_y - po.block_ref[dir].y
return false
end
end
function PhysO:resize(dir,change)
if dir=='l' then
self.pos_x = self.pos_x-change
self.siz_x = self.siz_x+change
if change > 0 then
while engine.is_colliding(self) do
self.pos_x = self.pos_x+1
end
end
elseif dir=='u' then
self.pos_y = self.pos_y-change
self.siz_y = self.siz_y+change
if change > 0 then
while engine.is_colliding(self) do
self.pos_y = self.pos_y+1
end
end
elseif dir=='r' then
self.pos_x = self.pos_x+change
self.siz_x = self.siz_x+change
if change > 0 then
while engine.is_colliding(self) do
self.pos_x = self.pos_x-1
end
end
else
self.pos_y = self.pos_y+change
self.siz_y = self.siz_y+change
if change > 0 then
while engine.is_colliding(self) do
self.pos_y = self.pos_y-1
end
end
end
end