ID:2295677
 
(See the best response by Ter13.)
I'm trying to figure out what FPS is ideal for the type of game I'm working on. My game is an open world PvP game with lots of combat in it. I'm trying to get smooth movement out of it. I was thinking between 20-40, any thoughts?

There are two separate FPS variables available: world.fps and client.fps.

world.fps basically affects how responsive the game's controls are: higher = more precise control.

Network latency (ping) usually kills the precision of controls, though, so I wouldn't worry about that too much. Choose your world.fps based on the game's performance demands. Lower world.fps allows for more time for things to happen per tick. Higher world.fps allows for more ticks (actions) to happen per second.

Smoothness of animations can be achieved via client.fps. It should suffice to set it to as high as it can go (100 currently), or 60, and maybe even give the client an option to change it (it's a per-client variable, so different clients can have different fps values). client.fps doesn't affect server-side performance at all, so client-side performance is the only concern.
I think 20 or 25 FPS server-side is a really good place to start, and you can go higher on the client. Something like 50 FPS on the client will probably work well if the graphics aren't super-demanding.

Usually you'll want client.fps to be a multiple of world.fps, and your best bet is to make sure both numbers divide evenly into 1000. That's because internally this is changed to an integer number of milliseconds.
So 25 world fps and 50 client fps would be a good place to begin? I'm going to be using the smooth tile movement lib and it doesn't seem to respond well at lower fps values
At world fps at 25 and client fps at 50, there's this weird delay in between movements on the smooth tile movement lib
some third party libraries were created before the FPS libraries were even invented

for example, if you're using this smooth movement lib
http://www.byond.com/developer/FIREking/SmoothTileMovement

that's from 2012 and WAY before anyone even thought seriously about FPS optimization in BYOND. Back in 2012, games were programmed with 10FPS in mind. That's pretty bad.

you're going to have to ditch old libraries if you're thinking seriously about optimization with current standards.

EDIT: Your problem stems from the fact that the "Smooth Movement" libraries scales with world.icon_size and world.tick_lag, it does NOT even consider the client.fps value because client.fps did not even exist when the library was made. So yes, the library is incompatible with client.fps.

If you want to keep the library, something you could try is changing the client.fps value a bit to something higher or lower. Like 60fps (the actual refresh rate of a 60hz monitor) and see if that hides the artifacts.

For example, in a game i'm making, 60fps is smoother than 50fps for whatever reason.
Best response
some third party libraries were created before the FPS libraries were even invented

Smooth Tile movement is newer than world.FPS. It very much was intended to be used with worlds running at 40 FPS. He and I talked about it a fair bit when he was working on it.


Provided you know how to calculate glide_size for steps, you can actually very easily do this without FireKing's lib. FireKing's lib is actually doing the same thing under the hood, he's just not exposing the full equation for movement for you in a universal format. He settled on 8 distinct movement speeds that looked good after a lot of experimentation instead of simply figuring out a function that would make it work at any step delay.

Calculating accurate tick durations:

(ceil(1000 / FPS) / 100)


Smooth tile movement is as simple as:

#define ceil(x) (-round(-(x)))

#define FPS 20
#define VFPS 40
#define TICK_LAG (ceil(1000 / FPS) / 100)
#define FRAME_LAG (ceil(1000 / VFPS) / 100)

world
fps = FPS
client
fps = VFPS

atom/movable
appearance_flags = LONG_GLIDE
var
move_delay = 2.5
proc
Step(Dir,Facing)
return Move(get_step(src,Dir||dir),Facing||Dir||dir)
Move()
glide_size = TILE_WIDTH / (move_delay || TICK_LAG) * TICK_LAG
. = ..()

mob
var/tmp
next_step = 0
last_step = -1#INF

Step(Dir,Facing)
if(next_step>world.time)
return 0
. = ..()
last_step = world.time
next_step = last_step + move_delay

client
Move(NewLoc,Dir)
return mob.Step(dir)


I'd strongly recommend a world FPS of 20 and a client FPS of 40. They are even divisibles of 1000, meaning no rounding of milliseconds in their tick calculations, and better floating point precision.
I keep getting tricked. Movement from those libs is super smooth when running in dream maker, but when publicly hosted it's choppy.
Coding\4.4 Movement.dm:480:error: ceil: undefined proc


Getting this error when I plugged your code in, Ter
client
North()
mob.dir=NORTH
..()
South()
mob.dir=SOUTH
..()
East()
mob.dir=EAST
..()
West()
mob.dir=WEST
..()

#define ceil(x) -round(-x)
#define FPS 20
#define VFPS 40
#define TICK_LAG (ceil(1000 / FPS) / 100)
#define FRAME_LAG (ceil(1000 / VFPS) / 100)

world
fps = FPS
client
fps = VFPS

atom/movable
appearance_flags = LONG_GLIDE
var
move_delay = 2.5
proc
Step(Dir,Facing)
return Move(get_step(src,Dir||dir),Facing||Dir||dir)
Move()
glide_size = 32 / move_delay * TICK_LAG
..()

mob
var
next_step = 0
last_step = -1#INF

Step(Dir,Facing)
if(next_step>world.time)
return 0
. = ..()
last_step = world.time
next_step = last_step + move_delay

client
Move(NewLoc,Dir)
return mob.Step(dir)


It works but doesn't seem to be very responsive. An example: if I'm holding the WEST key down and then try to move NORTH, there's a very noticeable delay before I actually move north.
Essentially what I'm going for, is a responsive movement system that is smooth and has more than just 3 movement speeds
The very noticeable movement delay only happens when world.fps is lower than client.fps. Is there any way to keep the world fps lower without causing this issue?
You'll want to use your own custom movement verbs with instant set to 1. Otherwise you can only trigger one verb per world tick.
Ah! I got it! Sweet stuff. How many movement speed ranks can you get with this setup?
Ah! I got it! Sweet stuff. How many movement speed ranks can you get with this setup?

You can change move_delay to anything. You won't be able to move more than one tile per tick, and move_delay should effectively be rounded to the value of world.tick_lag.

Assuming the slowest you want something to move is 1 tile every 2 seconds (which is SLOOOOOW), about 40 at 20 world FPS:

move_delays would be acceptable as:

0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0 ... 20.0


I like 2.5 as a base move value. It's 4 tiles a second, which is a good clip. Assuming each tile is a 5x5 foot area, that's a rough speed of 13 MPH. 4x the pace a normal person walks, and about half the speed a person can sprint.

Also, I went and edited my code above. I had some small mistakes in there that were gonna cause some problems for you.




Notably though, your ceil() definition is wrong. Just wanna save you the time your exact mistake cost me.

#define ceil(x) -round(-x)


I had this exact same line in my code at one point as a part of StdLib. Unfortunately, I didn't catch my rather silly mistake. I knew exactly what preprocessor macros did: They are sort of like code shorthand. When your game compiles, preprocessor macros are swapped out:

var/x = 5.1
world << ceil(4-x)


One might expect that ceil(4-5.1) would be ceil(-1.1), which is of course, -1. But you'll be scratching your head going absolutely crazy when this exact line of code spits out -10.

The reason it's doing this is because #defines aren't like procs. They just look like them. They don't exist at runtime. What happens when you compile your code, is the defined macro gets replaced in your code:

var/x = 5.1
world << -round(-4-x) //this is what the compiled code looks like.


Since the ceil() macro is being passed 4-x as the argument x, x is being replaced directly with 4-x.

-round(-4-5.1)
//evaluate:
-round(-9.1)
//evaluate:
-10


The unary minus (additive inverse) operator (-x) is right-associative, and has a higher precedence than the subtraction operator (x-y)

This can be fixed with parentheses:

#define ceil(x) -round(-(x))


var/x = 5.1
world << -round(-4-x)
//replaced with: world << -round(-(4-x))
//evaluate: world << -round(-(4-5.1))
//evaluate: world << -round(-(-1.1))
//evaluate: world << -round(1.1)
//evaluate: world << -1


I prefer two parentheses for certain things because I've run into other situations where I've confused the compiler:

#define ceil(x) (-round(-(x)))


The outer parentheses aren't really required. They get thrown away during compilation, as parentheses are for ordering operations and not real operators the bytecode has to muck with. There's no real loss from having unnecessary parentheses except readability and maybe a completely negligible number of wasted cycles during compilation.
I appreciate that you take the time to teach people about this stuff, I really do. So thank you!

Login to reply.