Skip to content

Instantly share code, notes, and snippets.

@jrepp
Last active November 22, 2019 01:24
Show Gist options
  • Select an option

  • Save jrepp/171a8b2ad18674cfa5e489c6399b57f1 to your computer and use it in GitHub Desktop.

Select an option

Save jrepp/171a8b2ad18674cfa5e489c6399b57f1 to your computer and use it in GitHub Desktop.
A test program that shows how you might implement slippery ice blocks in a top down game
//-------------------------------------------------------------------------------------------
//
// This is the sample code in C++
//
// You are mostly concerned with the update() function on the actor class. This implements
// the friction and movement math.
//
// The rest of the code might be interesting but is just 'scaffolding' to create a test
// and output that shows what's happening.
//
//-------------------------------------------------------------------------------------------
#include <stdio.h> // for printf()
#include <assert.h> // for assert()
#include <math.h> // for sqrt() and abs()
#include <queue> // for priority queue (just for test harness)
#include <string> // for std::string
#include <vector> // for event queue
#include <functional> // for std::function
// Settings for friction, normal blocks slow you down much faster
static float normal_block_friction = 40.0f;
static float ice_block_friction = 8.0f;
static float max_speed = 10.0f;
//
// This is a simple vector class for demo purposes. It only
// has a small amount of functionality. You should use a library
// that provides this.
//
class vector2 {
public:
float x, y;
vector2() : x(), y() {}
vector2(float x_, float y_) : x(x_), y(y_) {}
vector2(const vector2& b) : x(b.x), y(b.y) {}
vector2& operator=(const vector2& b) { x = b.x; y = b.y; return *this; }
~vector2() {}
void set(float x_, float y_) {
x = x_; y = y_;
}
float length() const {
return sqrt(x*x + y*y);
}
};
//
// Some simple vector math
//
inline vector2 operator+(const vector2& a, const vector2& b) {
vector2 add(a.x + b.x, a.y + b.y);
return add;
}
inline vector2 operator*(const vector2& a, const vector2& b) {
vector2 multiply(a.x * b.x, a.y * b.y);
return multiply;
}
inline vector2 operator*(const vector2& a, float scalar) {
vector2 multiply(a.x * scalar, a.y * scalar);
return multiply;
}
//
// Controller position (normalized) like a joy stick [-1,1] in the X and Y
//
static vector2 controller;
//
// This is the actor, it has to have a position and a velocity that controls how it is moving.
//
class actor {
public:
// position and direction of the actor (could be a player or other moving unit in the game)
vector2 position;
vector2 direction;
// speed in pixels or units per second, you decide
float speed;
// true when the actor is standing on the ice
bool standing_on_ice;
// actor starts at position 0,0 with no velocity and a speed setting of 1 unit / second
actor() : position(0, 0), direction(0, 0), speed(1), standing_on_ice(false) {}
~actor() {}
// the main function that you care about for the purposes of this sample
void update(float seconds) {
float friction;
if (standing_on_ice) {
friction = ice_block_friction;
} else {
friction = normal_block_friction;
}
// First your new position is your current direction multiplied by the speed and elapsed time
// e.g. if you are traveling 10 pixels per second and a 1/10 of a second elapsed you only went one pixel
position = position + (direction * (seconds * speed));
// Next you need to change the velocity based on the friction of the block you are standing on
// note that this is also in terms of seconds - the more time that goes by the more the velocity decreases
float slow_down = seconds * friction;
speed = speed - slow_down;
// Clamp the speed at 0, it doesn't make sense for speed to be negative
if (speed < 0) speed = 0;
// We now allow the controller to change the player direction and if the controller
// is down we use the controller power to affect the speed.
// NOTE!
// This is a very important part of the feel of the game
// How quickly does the player adjust to controller input
// How quickly does normal friction affect the input
//
// There is really cool things you can do here
direction = controller;
// The new speed will be influed by the friction of the current block, less friction will
// cause the actor to speed up slower
speed = speed + (friction * controller.length());
// The final step is to make sure we clamp the speed to prevent it from
// getting out of control
if (speed > max_speed) speed = max_speed;
}
// a function that prints out the actor so you can see what's happening
void print() {
printf( "Actor\n"
" Position: <%.2f, %.2f>\n"
" Direction: <%.2f, %.2f>\n"
" Speed: %.2f\n"
" OnIce?: %s\n",
position.x, position.y,
direction.x, direction.y,
speed,
standing_on_ice ? "true" : "false");
}
};
//
// event, add_event and run_events are just for testing purposes
// this uses a concept called a priority queue which is really good
// for scheduling things that need to be done and figuring out the 'next thing'
// to do very quickly
//
class event {
public:
typedef std::function<void()> function_type;
float time;
std::string name;
function_type function;
event(float time_, const std::string& name_, const function_type& function_) :
time(time_), name(name_), function(function_) {}
};
constexpr bool operator <(const event&a, const event &b) {
return a.time > b.time;
}
// Add events to a queue
static std::priority_queue<event> events;
void add_event(float time, const std::string& name, const event::function_type& function) {
events.push(event(time, name, function));
}
// A helper function to run all events up to and including (time)
void run_events(float time) {
// Nothing to do
if (events.empty())
return;
// It's not time for the next event
const event& next_event = events.top();
if (time < next_event.time)
return;
// Run the event code
printf("! @%.2f %s\n", next_event.time, next_event.name.c_str());
next_event.function();
// Remove the event
events.pop();
}
//
// The program begins execution here
//
int main() {
// Time in seconds you want to update. We set this to
// 20 frames per second for demo purposes
float elapsed_time_in_seconds = 1.0f / 20.0f;
// The player
actor player;
// The demo runs for one second, at .5 seconds the player will hit
// some ice which will affect their velocity
float time = 0;
float run_time = 1.0;
float hit_ice_time = .3f;
// These are simple test timing values
//
// 1. Simulation starts with player pressing right on controller
// 2. At 1/10 of a second the player releases the control stick
// 3. At 1/3 of a second the player hits ice
// 4. At 1/2 of a second the player presses up
// 5. At 7/10s of a second the player releases the controller again
// 1. Simulation starts with player pressing right on controller
float controller_release_right_time = .1f;
float controller_up_time = .5f;
float controller_release_up_time = .7f;
// Add all the test events and timing. The code will run at the specified time
add_event(0, "Controller Right", []() { controller.set(1, 0); });
add_event(hit_ice_time, "Hit Ice", [&player]() { player.standing_on_ice = true; });
add_event(controller_release_right_time, "Controller Release", []() { controller.set(0, 0); });
add_event(controller_up_time, "Controller Up", [] { controller.set(0, 1); });
add_event(controller_release_up_time, "Controller Release", []() { controller.set(0, 0); });
printf("Starting a game loop for %.2f second(s)\n", run_time);
// Run game for one second
while (time < run_time) {
printf("[Time: @%.2f]\n", time);
run_events(time);
// Core simulation that would exist in a game engine
player.update(elapsed_time_in_seconds);
time += elapsed_time_in_seconds;
player.print();
}
printf("! Done\n");
return 0;
}
# This builds the sample, run using ./ice
ice: ice_movement_sample.cpp
g++ --std=c++17 -Wall -ggdb -O0 -o ice ice_movement_sample.cpp
Starting a game loop for 1.00 second(s)
[Time: @0.00]
! @0.00 Controller Right
Actor
Position: <0.00, 0.00>
Direction: <1.00, 0.00>
Speed: 10.00
OnIce?: false
[Time: @0.05]
Actor
Position: <0.50, 0.00>
Direction: <1.00, 0.00>
Speed: 10.00
OnIce?: false
[Time: @0.10]
! @0.10 Controller Release
Actor
Position: <1.00, 0.00>
Direction: <0.00, 0.00>
Speed: 8.00
OnIce?: false
[Time: @0.15]
Actor
Position: <1.00, 0.00>
Direction: <0.00, 0.00>
Speed: 6.00
OnIce?: false
[Time: @0.20]
Actor
Position: <1.00, 0.00>
Direction: <0.00, 0.00>
Speed: 4.00
OnIce?: false
[Time: @0.25]
Actor
Position: <1.00, 0.00>
Direction: <0.00, 0.00>
Speed: 2.00
OnIce?: false
[Time: @0.30]
! @0.30 Hit Ice
Actor
Position: <1.00, 0.00>
Direction: <0.00, 0.00>
Speed: 1.60
OnIce?: true
[Time: @0.35]
Actor
Position: <1.00, 0.00>
Direction: <0.00, 0.00>
Speed: 1.20
OnIce?: true
[Time: @0.40]
Actor
Position: <1.00, 0.00>
Direction: <0.00, 0.00>
Speed: 0.80
OnIce?: true
[Time: @0.45]
Actor
Position: <1.00, 0.00>
Direction: <0.00, 0.00>
Speed: 0.40
OnIce?: true
[Time: @0.50]
! @0.50 Controller Up
Actor
Position: <1.00, 0.00>
Direction: <0.00, 1.00>
Speed: 8.00
OnIce?: true
[Time: @0.55]
Actor
Position: <1.00, 0.40>
Direction: <0.00, 1.00>
Speed: 10.00
OnIce?: true
[Time: @0.60]
Actor
Position: <1.00, 0.90>
Direction: <0.00, 1.00>
Speed: 10.00
OnIce?: true
[Time: @0.65]
Actor
Position: <1.00, 1.40>
Direction: <0.00, 1.00>
Speed: 10.00
OnIce?: true
[Time: @0.70]
! @0.70 Controller Release
Actor
Position: <1.00, 1.90>
Direction: <0.00, 0.00>
Speed: 9.60
OnIce?: true
[Time: @0.75]
Actor
Position: <1.00, 1.90>
Direction: <0.00, 0.00>
Speed: 9.20
OnIce?: true
[Time: @0.80]
Actor
Position: <1.00, 1.90>
Direction: <0.00, 0.00>
Speed: 8.80
OnIce?: true
[Time: @0.85]
Actor
Position: <1.00, 1.90>
Direction: <0.00, 0.00>
Speed: 8.40
OnIce?: true
[Time: @0.90]
Actor
Position: <1.00, 1.90>
Direction: <0.00, 0.00>
Speed: 8.00
OnIce?: true
[Time: @0.95]
Actor
Position: <1.00, 1.90>
Direction: <0.00, 0.00>
Speed: 7.60
OnIce?: true
! Done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment