Game physics & frame rate

In my jellifish game I tied physics to the framerate. In an effort to slow down the mobs I ended up reducing the framerate to only 15fps. This was still too fast for some of the movements I wanted. This led me to adding this snip to a number of my movement functions:

  if(frameCounter % 4 == 0){
     tentX += xSpeed;  //normaly only adding 1
     xLoc+= xSpeed;}

To further slow down the movements. I’ve been trying to think of a better way to implement this and was wondering how other people have handled framerate and physics interacting.

At the moment I’ve been thinking about making a ticker object that could count frames and encapsulate that snippet of code into a function.

class ticker{
    private: 
        uint8_t frameCount;
        uint8_t tickRate;  // after x ticks do action
    public:
        void ticker(uint8_t tickRate);
        void update(); // increase / rollover ticker
        boolean preformAction(){
            return if(frameCount % tickRate == 0);
         }

Don’t count frame, count time!

I’d recommend delta timing and fixed points. I used to have a working example but I mistplaced it.

Basically you look at the time of the previous frame and the time of the current frame and figure out the difference, then you express that difference as a fraction of a second (it’ll be between 0 and 1) and then you multiply the amount of movement by that fraction.

Alternatively you can accumulate delta time until 1 second has passed and only update when a second has passed (then subtract that second from the counter).

I wonder if this is overkill in a simple environment like the Arduboy.

The frame rate is constant (assuming the rendering is not taking longer than the allowed) and if you are happy to have actions occur at x frame intervals then the provided solution is probably the simplest and most efficient.

Delta timing accommodates variance in rendering time and other latency issues but these tend not to be a big issue in a static Arduboy environment.

I have extended the Aruboy2 class and exposed the underlying framecount in a helper method to do exactly what @Freezingsnail did with his if (frameCounter % 4 == 0) {. This removes the need to maintain your own frameCounter var.

Arduboy2Ext.h

#pragma once

#include <Arduboy2.h>

class Arduboy2Ext : public Arduboy2 {

  public:

    Arduboy2Ext();

    uint16_t getFrameCount() const;
    uint8_t getFrameCount(uint8_t mod) const;
};

and Arduboy2Ext.cpp

#include "Arduboy2Ext.h"

Arduboy2Ext::Arduboy2Ext() : Arduboy2() { }

uint16_t Arduboy2Ext::getFrameCount() const {

  return frameCount;

}

uint8_t Arduboy2Ext::getFrameCount(uint8_t mod) const {

  return frameCount % mod;

}

I agree.

I think creating a subclass to add a method that just applies a mod operator is even a bigger overkill :slight_smile:

(really just do arduboy.frameCount % whatever)

:smile: I agree too! I have a number of other methods in this class but stripped them out for the example. You are right, it does look a little over engineered in its reduced state!

I didn’t realize until a while after writing the extension that frameCount was public. I am guessing that @Freezingsnail doesn’t realise that too as he seems to be maintaining a similar variable called frameCounter in his original example.

1 Like

Experimenting:

#include <Arduboy2.h>
#define FRAMERATE 60

Arduboy2 arduboy;

class Movable
{
protected:
  uint8_t pos;
  uint8_t movementPeriod;
  
public:
  void setSpeed(uint8_t pixelsPerSec)
  {
    if (pixelsPerSec == 0) // don't divide by 0
    {
      this->movementPeriod = 0;
      return;
    }
    
    this->movementPeriod = FRAMERATE / pixelsPerSec;
  }
  
  void update()
  {
    if (this->movementPeriod == 0)
      return;
    
    if (arduboy.frameCount % (this->movementPeriod) == 0)
      pos += 1;
  }
  
  uint8_t getPos()
  {
    return this->pos;
  }
};

#define MOVABLES 64
Movable m[MOVABLES];

void setup()
{
  arduboy.begin();
  arduboy.setFrameRate(FRAMERATE);

  for (uint8_t i = 0; i < MOVABLES; ++i)
    m[i].setSpeed(i);
}

void loop()
{
  if (!(arduboy.nextFrame()))
    return;

  arduboy.clear();
  
  for (uint8_t i = 0; i < MOVABLES; ++i)
  {
    m[i].update();
    arduboy.drawPixel(m[i].getPos(),i,WHITE);
  }

  arduboy.display();
}

ArduboyRecording

advantages:

  • Simple.
  • No floating point.
  • Seems good for small speeds.
  • Doesn’t move more than 1 pixel per frame, which can be good for collision detection (if you have discreet collision detection, then high speeds can result in jumping through walls etc.).

disadvantages:

  • Can’t have speed (pixels/sec) higher than framerate (the pixels at the bottom of screen don’t move). (You could at least make it set it to maximum when higher, e.g. setSpeed(70) would do setSpeed(60).)
  • Speed gets rounded (more noticeable when approaching framerate - the big moving line).

That’s exactly what arduboy.everyXFrames(x) does.

bool Arduboy2Base::everyXFrames(uint8_t frames)
{
  return frameCount % frames == 0;
}
1 Like

Not quite. Mine returns the result as an uint8_t.

I also have a function that I used for ‘flashing’ something - text or an image - that had two parameters and would return bool if the frame rate mod was less than half of the parameter.

bool Arduboy2Ext::getFrameCountHalf(uint8_t mod) const {

  return (frameCount % mod) < (mod / 2);

}

Having said that, I am not sure how this relates to the OP.

You forgot ‘uses macros’ :P

I was going to say ‘relies on constant framerate’, but it seems there is no arduboy.getFrameRate() (I’ll log an issue about that).


@Freezingsnail
I found an old thread where I discussed delta timing:

Here’s an example of the time accumulation technique that I was talking about:

#include <Arduboy2.h>

Arduboy2 arduboy;

class DeltaTimer
{
public:
	using Milliseconds = decltype(millis());

private:
	Milliseconds previousTime = 0;
	Milliseconds currentTime = 0;
	Milliseconds timeDifference = 0;
	
public:
	Milliseconds getDeltaTime(void) const
	{
		return this->timeDifference;
	}

	void update(void)
	{
		if(this->previousTime == 0)
			this->previousTime = millis();
		
		this->currentTime = millis();
		this->timeDifference = this->currentTime - this->previousTime;
		this->previousTime = this->currentTime;
	}
};

class TimeAccumulator
{
public:
	using Milliseconds = decltype(millis());
	using Ticks = Milliseconds;
	
public:
	static constexpr Milliseconds MillisecondsPerSecond = 1000;

private:
	Milliseconds accumulatedTime = 0;
	Milliseconds targetTime = 0;
	
public:
	TimeAccumulator(Milliseconds targetTime) : targetTime(targetTime) {}

	Ticks getElapsedTicks(void) const
	{
		return this->accumulatedTime / this->targetTime;
	}

	void accumulate(Milliseconds milliseconds)
	{
		this->accumulatedTime += milliseconds;
	}
	
	bool consumeTick(void)
	{
		if(this->accumulatedTime < this->targetTime)
			return false;
			
		this->accumulatedTime -= this->targetTime;
		return true;
	}
};

struct Object
{
	uint8_t position = 0;
	uint8_t speed = 0;
};

constexpr size_t ObjectCount = HEIGHT;
Object objects[ObjectCount];

void update(void)
{
	for(uint8_t i = 0; i < ObjectCount; ++i)
	{
		const auto newPosition = (objects[i].position + objects[i].speed) % WIDTH;
		objects[i].position = newPosition;
	}
}

void render()
{
	for(uint8_t i = 0; i < ObjectCount; ++i)
		arduboy.drawPixel(objects[i].position, i);
}

constexpr auto ticksPerSecond = 8;
TimeAccumulator accumulator = TimeAccumulator(TimeAccumulator::MillisecondsPerSecond / ticksPerSecond);
DeltaTimer deltaTimer = DeltaTimer();

void setup(void)
{
	arduboy.begin();
	
	// Object 0 = 1 pixel per tick ... Object 63 = 32 pixels per tick
	for(uint8_t i = 0; i < ObjectCount; ++i)
		objects[i].speed = (i + 1) / 2;
		
	deltaTimer.update();
}

constexpr uint8_t frameChange = 5;
uint8_t frameRate = 60;

void loop(void)
{
	if(!arduboy.nextFrame())
		return;

	arduboy.pollButtons();
	
	if(arduboy.justPressed(UP_BUTTON))
		if(frameRate < 255)
		{
			frameRate += frameChange;
			arduboy.setFrameRate(frameRate);
		}
		
	if(arduboy.justPressed(DOWN_BUTTON))
		if(frameRate > 0)
		{
			frameRate -= frameChange;
			arduboy.setFrameRate(frameRate);
		}
	
	deltaTimer.update();
	const auto deltaTime = deltaTimer.getDeltaTime();
	accumulator.accumulate(deltaTime);
		
	while(accumulator.consumeTick())
		update();

	arduboy.clear();
	
	render();
	arduboy.println(frameRate);
	
	arduboy.display();
}

It works better than the pure delta timing approach, but a hybrid approach works even better because it’s easier to define speed as fractions of a second.

This is version of the above using fixed points and ‘time slices’:

#include <Arduboy2.h>
#include <FixedPointsCommon.h>

Arduboy2 arduboy;

class DeltaTimer
{
public:
	using Milliseconds = decltype(millis());

private:
	Milliseconds previousTime = 0;
	Milliseconds currentTime = 0;
	Milliseconds timeDifference = 0;
	
public:
	Milliseconds getDeltaTime(void) const
	{
		return this->timeDifference;
	}

	void update(void)
	{
		if(this->previousTime == 0)
			this->previousTime = millis();
		
		this->currentTime = millis();
		this->timeDifference = this->currentTime - this->previousTime;
		this->previousTime = this->currentTime;
	}
};

class TimeAccumulator
{
public:
	using Milliseconds = decltype(millis());
	using Ticks = Milliseconds;
	
public:
	static constexpr Milliseconds MillisecondsPerSecond = 1000;

private:
	Milliseconds accumulatedTime = 0;
	Milliseconds targetTime = 0;
	
public:
	TimeAccumulator(Milliseconds targetTime) : targetTime(targetTime) {}

	Ticks getElapsedTicks(void) const
	{
		return this->accumulatedTime / this->targetTime;
	}

	void accumulate(Milliseconds milliseconds)
	{
		this->accumulatedTime += milliseconds;
	}
	
	bool consumeTick(void)
	{
		if(this->accumulatedTime < this->targetTime)
			return false;
			
		this->accumulatedTime -= this->targetTime;
		return true;
	}
};

struct Object
{
	UQ8x8 position = 0;
	UQ8x8 speed = 0;
};

constexpr size_t ObjectCount = HEIGHT;
Object objects[ObjectCount];

void update(UQ8x8 deltaTime)
{
	for(uint8_t i = 0; i < ObjectCount; ++i)
	{
		auto newPosition = (objects[i].position + (objects[i].speed * deltaTime));
		
		// Can't think of a better way to do this at the moment
		if(newPosition > WIDTH) newPosition -= WIDTH;
		
		objects[i].position = newPosition;
	}
}

void render()
{
	for(uint8_t i = 0; i < ObjectCount; ++i)
		arduboy.drawPixel(objects[i].position.getInteger(), i);
}

constexpr auto ticksPerSecond = 8;
TimeAccumulator accumulator = TimeAccumulator(TimeAccumulator::MillisecondsPerSecond / ticksPerSecond);
DeltaTimer deltaTimer = DeltaTimer();

void setup(void)
{
	arduboy.begin();
	
	// Object 0 = 1 pixel per tick ... Object 63 = 32 pixels per tick
	for(uint8_t i = 0; i < ObjectCount; ++i)
		objects[i].speed = (i + 1) / 2;
		
	deltaTimer.update();
}

constexpr uint8_t frameChange = 5;
uint8_t frameRate = 60;

void loop(void)
{
	if(!arduboy.nextFrame())
		return;

	arduboy.pollButtons();
	
	if(arduboy.justPressed(UP_BUTTON))
		if(frameRate < 255)
		{
			frameRate += frameChange;
			arduboy.setFrameRate(frameRate);
		}
		
	if(arduboy.justPressed(DOWN_BUTTON))
		if(frameRate > 0)
		{
			frameRate -= frameChange;
			arduboy.setFrameRate(frameRate);
		}
	
	deltaTimer.update();
	const auto deltaTime = deltaTimer.getDeltaTime();
	accumulator.accumulate(deltaTime);
		
	constexpr UQ8x8 timeSlice = UQ8x8(1) / UQ8x8(ticksPerSecond);
	while(accumulator.consumeTick())
		update(timeSlice);

	arduboy.clear();
	
	render();
	arduboy.println(frameRate);
	
	arduboy.display();
}

And this is a version demonstrating speeds as ‘pixels per second’:

#include <Arduboy2.h>
#include <FixedPointsCommon.h>

Arduboy2 arduboy;

class DeltaTimer
{
public:
	using Milliseconds = decltype(millis());

private:
	Milliseconds previousTime = 0;
	Milliseconds currentTime = 0;
	Milliseconds timeDifference = 0;
	
public:
	Milliseconds getDeltaTime(void) const
	{
		return this->timeDifference;
	}

	void update(void)
	{
		if(this->previousTime == 0)
			this->previousTime = millis();
		
		this->currentTime = millis();
		this->timeDifference = this->currentTime - this->previousTime;
		this->previousTime = this->currentTime;
	}
};

class TimeAccumulator
{
public:
	using Milliseconds = decltype(millis());
	using Ticks = Milliseconds;
	
public:
	static constexpr Milliseconds MillisecondsPerSecond = 1000;

private:
	Milliseconds accumulatedTime = 0;
	Milliseconds targetTime = 0;
	
public:
	TimeAccumulator(Milliseconds targetTime) : targetTime(targetTime) {}

	Ticks getElapsedTicks(void) const
	{
		return this->accumulatedTime / this->targetTime;
	}

	void accumulate(Milliseconds milliseconds)
	{
		this->accumulatedTime += milliseconds;
	}
	
	bool consumeTick(void)
	{
		if(this->accumulatedTime < this->targetTime)
			return false;
			
		this->accumulatedTime -= this->targetTime;
		return true;
	}
};

struct Object
{
	UQ8x8 position = 0;
	UQ8x8 speed = 0;
};

constexpr size_t ObjectCount = HEIGHT;
Object objects[ObjectCount];

void update(UQ8x8 deltaTime)
{
	for(uint8_t i = 0; i < ObjectCount; ++i)
	{
		auto newPosition = (objects[i].position + (objects[i].speed * deltaTime));
		
		// Can't think of a better way to do this at the moment
		if(newPosition > WIDTH) newPosition -= WIDTH;
		
		objects[i].position = newPosition;
	}
}

void render()
{
	for(uint8_t i = 0; i < ObjectCount; ++i)
		arduboy.drawPixel(objects[i].position.getInteger(), i);
}

constexpr auto ticksPerSecond = 8;
TimeAccumulator accumulator = TimeAccumulator(TimeAccumulator::MillisecondsPerSecond / ticksPerSecond);
DeltaTimer deltaTimer = DeltaTimer();

void setup(void)
{
	arduboy.begin();
	
	// Object 0 = 1 pixel per second ... Object 63 = 64 pixels per second
	for(uint8_t i = 0; i < ObjectCount; ++i)
		objects[i].speed = (i + 1);
		
	deltaTimer.update();
}

constexpr uint8_t frameChange = 5;
uint8_t frameRate = 60;

void loop(void)
{
	if(!arduboy.nextFrame())
		return;

	arduboy.pollButtons();
	
	if(arduboy.justPressed(UP_BUTTON))
		if(frameRate < 255)
		{
			frameRate += frameChange;
			arduboy.setFrameRate(frameRate);
		}
		
	if(arduboy.justPressed(DOWN_BUTTON))
		if(frameRate > 0)
		{
			frameRate -= frameChange;
			arduboy.setFrameRate(frameRate);
		}
	
	deltaTimer.update();
	const auto deltaTime = deltaTimer.getDeltaTime();
	accumulator.accumulate(deltaTime);
		
	constexpr UQ8x8 timeSlice = UQ8x8(1) / UQ8x8(ticksPerSecond);
	while(accumulator.consumeTick())
		update(timeSlice);

	arduboy.clear();
	
	render();
	arduboy.println(frameRate);
	
	arduboy.display();
}

As long as ‘frameRate’ is higher than ‘ticksPerSecond’ (and there’s not too much lag in the loop itself due to excessive amounts of drawing/processing) then everything remains smooth regardless of the frame rate.

All three use less than 30% progmem.
Though I suspect that would escalate with more complex setups.

I didn’t know that the arduboy2 had a frame counting variable. @Pharap’s answer is definitely better in any modern setting. Old games being locked to 30fps is a pain. With the limits of this system though I wondered what other people had done.

@Pharap what kind of setting would your method give a better result on the arduboy?

On the Arduboy the time accumulation is most beneficial if you need/want the framerate uncapped because it gives you a steady rate of updating regardless of the framerate.

It’s also beneficial for making a game that’s easily ported to other systems.

Though using fixed points for fractional speeds and positions would be good for any game that needs objects moving at different speeds. (E.g. 1943.)


The problem with integers is that because you can’t have a fraction of a second you have to count frames to decide when to move next (e.g. if(arduboy.everyXFrames(30)) ++x; to move 2 pixels every 1 second, if(arduboy.everyXFrames(120)) ++x; to move 1 pixel every 2 seconds).

With fixed points you just set a speed and use the same logic for everything: object.position += object.speed, where object.speed = 0.5 gives you 1 pixel every 2 frames, object.speed = 0.25 gives you 1 pixel every 4 frames etc.

Combining that with the time accumulation system allows you to use seconds instead of frames.
You could achieve the same with frame counting, but (possibly counterintuitively) time accumulation and fixed points results in simpler logic and possibly smaller code in some cases.


It’s worth noting that the frame counter overflows, so actually something like if(arduboy.everyXFrames(10)) will actually run for less than 10 frames after roughly 1092 seconds (about 18 minutes).
It’s not a major thing because that’s a long time and it’s a tiny difference, but it’s still undesirable.

That doesn’t happen with accumulating time because you’re constantly reducing the accumulator and the use of the difference between two times rather than an absolute counter means that overflow isn’t an issue anyway.

One thing to note is that while everyXFrames() is convenient to use, it uses the % operator which requires a divide. This could take many cycles to execute.

If you have multiple events that occur, each after a given number of frames have elapsed, it may be better to keep your own local counters that you just have to decrement or increment, test and possibly reset on each frame.


#define FRAMES_PER_EVENT 4

uint8_t eventFrameCounter = FRAMES_PER_EVENT;

void loop() {
  if (!arduboy.nextFrame()) {
    return;
  }

  // Use something like this for constantly recurring events
  if (--eventFrameCounter == 0) {
    eventFrameCounter = FRAMES_PER_EVENT;
    performEvent();
  }
}

Though of course, that also means more RAM usage.
So it depends on what’s more important, RAM or speed.

Programming: the process of making trade-offs.

1 Like

I’ll keep that in mind for the future.

That’s similar to what I did, except I used a spaghetti global.

1 Like

I think this is negligible if you do it only once every loop. Where it could matter would be an iteration inside the main loop – e.g. checking everyXFrames() for 50 enemies each frame – in which case you can simply store the result of everyXFrames() in a bool before the loop begins and then check that. Right?

Sure. When I said multiple events I meant multiple different number of frames for different events, so you would have to use everyXFrames() more than once.

Also, even if all 50 enemies move at the same speed, you might want to stagger their movements. You would have some move on one frame then others 2 frames later, etc. This way you’re not doing all the enemy moving on one frame, possibly exceeding the time allotted for that frame, and then having lots of free time in other frames. This may also give the game a smoother appearance. everyXFrames() doesn’t allow for this.

1 Like

Unless all your enemies move at different speeds and thus need different inputs to everyXFrames.
You could track all the possible inputs and then cache them, or cache the inputs as you go, but since we’re talking arbitrary integers, you’d either need a lot of memory for that hash table to cover all your ranges, or you’d need a hash table which is potentially slower than the modulo.

Multiplying a UQ8x8 is cheaper because the compiler can actually use the CPU’s MUL instruction and then just shift some bits away (or if it’s being clever, just do some addressing - I’ve discovered GCC doesn’t always realise it can do this unfortunately), which should be faster than division.
Using UQ16x16 is another story though, MUL won’t stretch that far.

Also, if you’re using the hybrid approach and you hardcode the deltaTime as constant then the compiler should pick up on that and simplify the multiplication in respect to the constant value.
E.g. if x * deltaTime is x * UQ8x8(0.25), hopefully the compiler can realise that it can constant-fold that into x >> 2.

1 Like