ID:1404261
 
(See the best response by Ter13.)
Code:
gravity_loop()
set background = 1
while(src)

if(jumping) //if Jumping...
on_ground = 0
vel_y -= gravity
if(vel_y > 0)
step(src,NORTH,vel_y)
set_dir(direction)
else if(vel_y <= 0)
if(!step(src,SOUTH,-vel_y))
set_dir(direction)
jumping = 0
else if(step(src,SOUTH,1)) //if falling...
vel_y -= gravity
step(src,SOUTH,-vel_y)
set_dir(direction)
else if(!on_ground) //hit ground
if(jumping) jumping = 0
if(vel_y != 0) vel_y = 0
set_dir(direction)
on_ground = 1

sleep(world.tick_lag)


EDIT: Seems I sorted the glitchy looking behaviour when I edited my code just now after spotting something. Still, any advice on improvements/errors spotted would be welcome.

This is my little gravity loop, it works, although it looks a little glitchy when my mob is falling (from stepping off a platform), as opposed to the smooth looking drop when he jumps off a platform. The only reason I can see why this might be happening is the if(step(src,SOUTH,gravity)), just wondering if there is a better way to go about this?

The current setup is to have this running when the player logs in. Hitting the Jump key will set vel_y to 15, the gravity loop takes care of the rest.

Please point out any other errors or inefficiencies, still getting the hang of platformer physics.
Best response
I handled this a bit differently.

Basically, I rigged up a bounding box to hang out below the object with falling physics attached:

#define iscollider(x) istype((x),/collider)

collider
parent_type = /atom/movable
proc
Collide(var/atom/o)
Uncollide(var/atom/o)

Crossed(var/atom/movable/o)
if(!iscollider(o))
src.Collide(o)

Uncrossed(var/atom/movable/o)
if(!iscollider(o))
src.Uncollide(o)

platform
icon = 'floorcollider.dmi'
layer = 1000
density = 0
var/tmp
atom/movable/owner
list/colliding = list()
proc
OwnerMoved()
if(src.owner.loc)
workingpoint.Set(src.owner)
workingpoint.y -= 1
src.Move(workingpoint.getLoc(),0,workingpoint.getPixelX(),workingpoint.getPixelY())
else
src.Move(null)
New(var/atom/movable/owner)
src.owner = owner
src.bound_width = src.owner.bound_width
src.bound_height = 1
Hook("moved",owner,src,"OwnerMoved")

Collide(var/atom/o)
if(o.density)
var/y1 = (o.y-1)*TILE_HEIGHT
if(istype(o,/atom/movable))
var/atom/movable/m = o
y1 += m.step_y + m.bound_height
var/y2 = (src.y-1)*TILE_HEIGHT + src.step_y
if(y1==y2)
src.colliding += o

Uncollide(var/atom/o)
src.colliding -= o

turf
Entered(var/atom/movable/o)
if(iscollider(o))
var/collider/c = o
c.Collide(src)
Exited(var/atom/movable/o)
if(iscollider(o))
var/collider/c = o
c.Uncollide(src)

atom
movable
Crossed(var/atom/movable/o)
if(iscollider(o))
var/collider/c = o
c.Collide(src)
Uncrossed(var/atom/movable/o)
if(iscollider(o))
var/collider/c = o
c.Uncollide(src)


And then I check if the length of the colliding objects list is zero after each move. If it's zero, I kick in a loop that applies velocity to the object in a southerly direction, accelerating over time. Gravity is applied per-tick to the existing velocity vector, and the object falls until it is grounded again.

But I never have a gravity loop running on an object that isn't actually falling, so it's a bit more efficient than your design.

Note: This uses DatumPoint, and CodeHooks, two libraries I have written.
This definitely looks like a better solution, the only thing that I'm unsure of is how you defined workingpoint.
Ah, workingpoint is just a global variable that I use to keep from initializing new point datums:

var
point/workingpoint = new()


I only use this for actions that aren't going to be persistent. In other words, if I need to keep workingpoint around for more than one tick, I do this instead:

var/point/p = new()
p.Set(src)


The point datum is part of my DatumPoint library. It makes tile calculations a lot easier.