ID:1573076
 
BYOND gets a bad rap for being a jittery, jumpy mess in tile mode. This isn't the case. Just a few lines of code can fix this.

world
icon_size = 32
fps = 25

#define MOVE_SLIDE 1
#define MOVE_JUMP 2
#define MOVE_TELEPORT 4

#define TILE_WIDTH 32
#define TILE_HEIGHT 32
#define TICK_LAG 0.4 //set to (10 / world.fps) a define is faster, though

atom/movable
appearance_flags = LONG_GLIDE //make diagonal and horizontal moves take the same amount of time
var
move_delay = 0 //how long between self movements this movable should be forced to wait.
tmp
next_move = 0 //the world.time value of the next allowed self movement
last_move = 0 //the world.time value of the last self movement
move_dir = 0 //the direction of the current/last movement
move_flags = 0 //the type of the current/last movement
Move(atom/NewLoc,Dir=0)
var/time = world.time
if(next_move>time)
return 0
if(!NewLoc) //if the new location is null, treat this as a failed slide and an edge bump.
move_dir = Dir
move_flags = MOVE_SLIDE
else if(isturf(loc)&&isturf(NewLoc)) //if this is a movement between two turfs
var/dx = NewLoc.x - x //get the distance delta
var/dy = NewLoc.y - y
if(z==NewLoc.z&&abs(dx)<=1&&abs(dy)<=1) //if only moving one tile on the same layer, mark the current move as a slide and figure out the move_dir
move_dir = 0
move_flags = MOVE_SLIDE
if(dx>0) move_dir |= EAST
else if(dx<0) move_dir |= WEST
if(dy>0) move_dir |= NORTH
else if(dy<0) move_dir |= SOUTH
else //jumping between z levels or more than one tile is a jump with no move_dir
move_dir = 0
move_flags = MOVE_JUMP
else //moving into or out of a null location or another atom other than a turf is a teleport with no move_dir
move_dir = 0
move_flags = MOVE_TELEPORT
glide_size = TILE_WIDTH / max(move_delay,TICK_LAG) * TICK_LAG //set the glide size
. = ..() //perform the movement
last_move = time //set the last movement time
if(.)
next_move = time+move_delay


For best results, avoid moving the player where possible while they are gliding, otherwise their glide will jump instantly to the finished position.

This should, in almost all cases, fix the jumpiness experienced in tile-based BYOND games. Higher FPS values will of course result in smoother movement. Faster movement will be a bit more chunky than slower movement, but overall, it will be better than simply not accounting for it at all.

If you use walk()/step(), make sure to set the move_delay of the movable just before you call the walk()/step(), otherwise, you can wind up with some ugly jumps.

And here's a poorly recorded image of it in action: The top character is using the default glide_size, and the bottom is using a delay of 8 * TICK_LAG.

Huh. I had played around with glide_size a bit once before to fix up movement, but I never realized it made that huge of a difference and was such a common issue.
In response to Toddab503
Toddab503 wrote:
Huh. I had played around with glide_size a bit once before to fix up movement, but I never realized it made that huge of a difference and was such a common issue.

90% of the complaints about BYOND's "lag" are due to this. Read the reviews of NEStalgia. Even there, people complain about this issue. Using glide_size properly, you can pretty much eliminate the jumpy movement. The real trick, is to use an even divisible of the icon_size per-tick, meaning you optimally want your FPS to be a factor of 2, and your movement delays to be a factor of 2. (1 is a factor of 2, remember!).

Put simply, 20FPS or 40FPS are the most ideal. Anything in lower, higher, or in between is going to be harder to sync up.

Also, the number of frames in your movement states are absolutely critical!

In my case, I use an 8-frame walking state in the above animation. They are synced to 0.8 delay per frame, meaning a single cycle takes 16 frames at 25 fps. The animation also was made to account for the foot positions, so that there is no sliding. Every step is intended to glide 2 pixels per frame, and as such, every frame of the animation has the weighted foot moving backward by 2 pixels per frame of the animation.

If I wanted to have a running animation, where the player moved one tile every 4 frames, for instance, I would probably want to sync the animation to 0.4 delay per frame at 8 frames, or 0.8 delay per frame at 4 frames.

Again, it's not just a matter of making a good animation, it's syncing it to your fps and getting the motion of each frame matching the glide size. Otherwise, things start looking really bad.

On the other hand, simply not making the client jitter around every time you move is a very big improvement, as my gif shows. (It looks way, way better in a demo. Licecap didn't record it very well.)
In response to Ter13
That makes a lot of sense, and is actually really good to know. I'm not someone who runs in fear from pixel movement, but there are some games that rightfully use tile based movement and this seems to be pretty crucial for them.

Personally, I usually use 4 frame movement for my icons, sometimes 6; the size kind of influences it. I initially started doing this because of some confusion about the cause of the jittering. I also stick to 40 fps for several reasons, some of which being related to jitter confusion.

I'm still going over this, and haven't gotten the chance to test anything, but if I'm understanding correctly to achieve a similar effect on a 4 frame movement to what you did with yours would mean syncing it to 1.2 per frame?
10 / 40 = 0.25.

That means that your minimum frame delay would be 0.025 seconds per frame. Your animations must be even multiples of 0.25 since you are using 40fps. Therefore, 0.25 per frame for 4 frames would be a delay of 1 (4 frames). 0.5 per frame for 4 frames would be a delay of 2 (8 frames). 0.75 per frame for 4 frames would be a delay of 3 (12 frames), and 1 per frame for 4 frames would be a delay of 4 (16 frames).
Thank you sir
Since some people were confused that this snippet is not plug & play, I added the movement delay in manually.
Sorry for necroing, but this has helped a ton with smoothing out some of my animations. Thanks.
In response to Crazah
Crazah wrote:
Sorry for necroing, but this has helped a ton with smoothing out some of my animations. Thanks.

In response to Crazah
Crazah wrote:
Sorry for necroing, but this has helped a ton with smoothing out some of my animations. Thanks.

In this subforum, there's no such thing as a necro. Thanks.
Would someone mind explaining the differences between this snippet and FIREking's similar solution? Why should I prefer this approach?
His approach will break with a non-square glide size, mine won't. His approach also has very slight additional overhead than mine due to it calling a global function. His is also slightly less configurable than mine.

Though, mine relies on a sqrt, rather than a bit shift for diagonal glides. I'm not sure what the effect of that would be. I'm honestly not sure how accurate that is.
I just had to update this because it was brought to my attention that this was not working well on certain FPS values. This is a call to anybody using this, please try the updated code and see if it's working better for you.
What is TILE_DIAG supposed to be? (My guess is 48; I get some good performance with that value.)
C^2 = A^2 + B^2.

So roughly 45.255
In response to Ter13
Appreciated. The gliding is flawless.
It'll get better when Lummox implements partial pixel accuracy for glide_size.
What is Ceil?
ceil() is a rounding-up proc, the opposite of floor().


ceil(1.5) = 2
ceil(-1.5) = -1

floor(1.5) = 1
floor(-1.5) = -2


edit: These functions don't exist in BYOND's standard engine, although round(x) is identical to floor(). to accomplish ceil() you'd need to use -round(-x).
In response to Mr_Goober
Thanks, that cleared things up for me, What is Ceil part of?
Page: 1 2