Last active
November 22, 2019 01:24
-
-
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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //------------------------------------------------------------------------------------------- | |
| // | |
| // 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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