Understanding Screen & World Space

Learn to implement coordinate space conversions in C++ to position game objects correctly on screen.
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Get Started for Free
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Posted

Every non-trivial game needs to represent positions in at least two different ways: where things are in the game world, and where they appear on the player's screen.

These different representation systems are called the world space and the screen space, and converting between them is a core skill in game development. In this lesson, we'll explore some of the main reasons why we need to work across multiple spaces, and how to transform our objects between them.

We’ll be using the Vec2 struct we created earlier in the course. A complete version of it is available below:

#pragma once
#include <iostream>

struct Vec2 {
  float x;
  float y;

  float GetLength() const {
    return std::sqrt(x * x + y * y);
  }

  float GetDistance(const Vec2& Other) const {
    return (*this - Other).GetLength();
  }

  Vec2 Normalize() const {
    return *this / GetLength();
  }

  Vec2 operator*(float Multiplier) const {
    return Vec2{x * Multiplier, y * Multiplier};
  }

  Vec2 operator/(float Divisor) const {
    if (Divisor == 0.0f) { return Vec2{0, 0}; }

    return Vec2{x / Divisor, y / Divisor};
  }

  Vec2& operator*=(float Multiplier) {
    x *= Multiplier;
    y *= Multiplier;
    return *this;
  }

  Vec2& operator/=(float Divisor) {
    if (Divisor == 0.0f) { return *this; }

    x /= Divisor;
    y /= Divisor;
    return *this;
  }

  Vec2 operator+(const Vec2& Other) const {
    return Vec2{x + Other.x, y + Other.y};
  }

  Vec2 operator-(const Vec2& Other) const {
    return *this + (-Other);
  }

  Vec2& operator+=(const Vec2& Other) {
    x += Other.x;
    y += Other.y;
    return *this;
  }

  Vec2& operator-=(const Vec2& Other) {
    return *this += (-Other);
  }

  Vec2 operator-() const {
    return Vec2{-x, -y};
  }
};

inline Vec2 operator*(float M, const Vec2& V) {
  return V * M;
}

inline std::ostream& operator<<(
  std::ostream& Stream, const Vec2& V) {
  Stream << "{ x = " << V.x
    << ", y = " << V.y << " }";
  return Stream;
}

Screen Space

Previously, we’ve been creating and managing our objects in screen space. Screen space is the coordinate system we use to position objects on the player’s screen.

Our screen has been represented using the SDL_Surface corresponding to the SDL_Window where our game has been running. This surface configures the space it manages such that the top left corner is the origin, with increasing x values moving right and increasing y values moving down.

The space looks like with, with an example position x=500,y=200x = 500, y = 200 highlighted:

Diagram showing the SDL screen space

When we start building more complex projects, this approach of programming everything in screen space has a few problems. For example:

  • We don’t necessarily know the dimensions of the screen space at the time we’re writing our code. Users may be able to resize our window or, if our window is running in full screen, we may not know the resolution of that screen in advance.
  • The screen-space coordinate system is rarely convenient. For example, we typically want downward movement to correspond with reducing values, but the SDL window’s coordinate system works in the opposite way. To move downwards within an SDL_Surface, we need to increase the y value, which isn’t intuitive.
  • What is currently on the screen does not necessarily correspond to everything in our world. For example, many games involve the player controlling a character, and that character can only see a small part of the world at any given time. But the rest of the world is still there and the player may visit it in the future. As such, we need a space where objects in that world can continue to exist and be updated, even if they’re not being rendered to the screen right now.

Predictably, the space containing all the objects of our world, even when they’re not on the screen, is called the world space.

World Space

Most of our positioning and simulation are done within a coordinate system called the world space. When we create our game levels and worlds, the objects we position within them are in this world space.

We’re free to set this space up in whatever way is most convenient for the game we’re making. In 2D games, our world space typically uses an x dimension where increasing values correspond to moving right, and a y coordinate where increasing the value corresponds to moving up. In a 3D world space, the third dimension is typically labeled z and is perpendicular to both x and y.

In addition to choosing a coordinate system for our space, we also need to choose where the origin is - that is, what the x = 0, y = 0 position represents. A popular choice is to set up our space such that the origin represents the center of the world:

Diagram showing an example world space with a bottom-left origin

We can adjust our coordinate system and origin as needed based on the type of game and our preferences. For 2D games in particular, we typically don’t change the coordinate system from the previous example, but it may be more convenient to change what the origin position, (0,0)(0, 0) represents.

We can adjust our coordinate system and origin as needed based on the type of game and our preferences. For 2D games in particular, we typically maintain the coordinate system (with xx increasing to the right and yy increasing upward) from the previous example, but it may be more convenient to change what the origin position (0,0)(0,0) represents.

For example, we could define the origin such that x = 0 aligns with the left edge of our level and for y = 0 to align with the bottom edge:

Diagram showing an example world space with a bottom-left origin

This is slightly less efficient as it means we won’t be using the "negative" range of our numeric types. However, that rarely matters for small levels, and not having to deal with negative numbers can make our lives slightly easier.

Transformations

Even though our objects are positioned in world space, the point of our game is ultimately to render those objects onto the player’s screen. So, we need some way to transform objects from their world space position to the corresponding screen space position.

This can be a challenge, as our spaces typically have different properties. There are three properties in particular where our spaces can differ:

  • they can use different coordinate systems
  • what the origin, (0,0)(0, 0), represents can have a different meaning
  • they can have a different size

In most games, the world space and screen space differ across all three of these properties.

Example World Space and Screen Space

Later in this chapter, we’ll work with examples where our spaces are dynamically defined. For now, let’s work with a fixed example so we can establish the basics. We’ll imagine our world space looks like this:

Diagram showing an example world space that we'll use in this lesson

The key properties are:

  • The x dimension is horizontal, where increasing values correspond to moving right
  • The y dimension is vertical, where increasing values correspond to moving up
  • The origin represents the bottom left of our world
  • We want everything in our world space to be rendered to the screen, and everything in our space is within the range (0, 0) to (1400, 600)

Our screen space is the SDL_Surface associated with an SDL_Window. We’ll imagine the space looks like this:

Diagram showing an example screen space that we'll use in this lesson

The key properties are:

  • The x dimension has the same meaning as the world space - it is horizontal, where increasing values correspond to moving right
  • The y dimension is also vertical, but increasing values correspond to moving down rather than up
  • The origin represents the top left of our screen
  • The window’s dimensions are 700x300 - half the size of our world space

Transforming World Space to Screen Space

Later, we'll render our characters as images, so we'll represent the characters' positions at their top left corners. This corresponds to the x and y values we'd use in our SDL_Rect that controls where the image is blitted.

Despite our spaces being defined differently, we need our objects to be positioned in a visually consistent way across both spaces. That means we need to define logic that updates the x and y coordinates of an object in world space to what their equivalent values would be in screen space.

The following shows the start and end point of that process with two example objects - a dwarf and a dragon character:

Diagram showing our characters in world space and screen space

The challenge is determining what transformation logic to use, given how our screen space properties (coordinate system, size, and origin) differ from the world space properties.

Based on the properties we listed above for our example world and screen spaces, the transformation needs to:

  1. Account for the different size: Scale vectors down by 50% to account for the screen space being half the size of the world space. This corresponds to moving positions 50% closer to the origin.
  2. Account for the different coordinate system: Invert the y coordinates to account for the screen space’s y axis pointing in the opposite direction to the world space’s y axis.
  3. Account for the the different origin: Increase y coordinates by 300 units (the height of the screen space) to account for the origin being at the top left of our screen space, rather than the bottom left. Given increasing y values corresponds to moving down in screen space, this step of our transformation will move (or "translate") our vectors down.

Let’s walk through this visually so we can better understand the logic we need to implement. First, let’s just place our characters in screen space, but using their world space coordinates without transformation. This helps us see the problem we need to solve:

Diagram showing our characters in screen space but using their world space coordinates

The horizontal position of our dwarf has shifted slightly, whilst our dragon is not even on the screen.

Step 1: Accounting for the Different Size

Let’s apply the first step of our transformation, multiplying all of the position vectors by 0.50.5. This scales them down to half of their current value, which is equivalent to moving them closer to the origin:

Diagram showing step one of the transformation

Step 2: Accounting for the Different Coordinate System

For step 2, we negate our objects’ vertical positions by multiplying their yy positions by 1-1. This moves them above the desired range and out of view, but we’ve made some progress - their positions relative to each other are now correct:

Diagram showing step two of the transformation

Step 3: Accounting for the Different Origin

Finally, we perform step 3 of our transformation. We increase their yy components by 300300 which, in the screen space coordinate system, corresponds to moving the objects down by 300 units*.* This places our objects in their final, correct position:

Diagram showing step three of the transformation

Implementing our transformation in code could look like this:

Vec2 ToScreenSpace(const Vec2& Position) {
  Vec2 ReturnValue{Position};
  
  // 1: Scale every component by 50%
  ReturnValue *= 0.5;
  
  // 2: Invert the y component
  ReturnValue.y *= -1;
  
  // 3. Increase the y component.  This corresponds to moving
  // objects downwards in the screen space
  ReturnValue.y += 300;
  
  return ReturnValue;
}

Or, equivalently:

Vec2 ToScreenSpace(const Vec2& Pos) {
  return {
    Pos.x * 0.5f,
    (Pos.y * -0.5f) + 300
  };
}

Note that the ordering here is important. If we translated the vectors (step 3) before scaling and inverting the y axis (steps 1 and 2), then that translation would be done within the world space’s definition of y.

We can order it in that way if we want, but it means that our translation logic would need to be adjusted. For our example spaces, we could move objects down by equivalent amounts either by decreasing y values by 600 units in world space or by increasing y values by 300 units in screen space.

Whichever approach we use, we can transform some test points to ensure our logic is correctly mapping world space positions to equivalent screen space positions:

#include <iostream>
#include "Vec2.h"

Vec2 ToScreenSpace(Vec2&) {/*...*/} int main() { // A position in the center of // the 1400x600 world space... Vec2 P{700.0, 300.0}; // ...transformed to the center // of the 700x300 screen space std::cout << "[SCREEN SPACE]"; std::cout << "\nCenter: " << ToScreenSpace(P); std::cout << "\nBottom Left: " << ToScreenSpace({0, 0}); std::cout << "\nTop Left: " << ToScreenSpace({0, 600}); std::cout << "\nBottom Right: " << ToScreenSpace({1400, 0}); std::cout << "\nTop Right: " << ToScreenSpace({1400, 600}); }
[SCREEN SPACE]
Center:       { x = 350, y = 150 }
Bottom Left:  { x = 0, y = 300 }
Top Left:     { x = 0, y = 0 }
Bottom Right: { x = 700, y = 300 }
Top Right:    { x = 700, y = 0 }

Advanced: Transformation Matrices

In higher budget games, these transformations tend to be implemented using transformation matrices and matrix multiplication - concepts from linear algebra. Linear algebra is the formal field of mathematics that studies vectors, spaces, and their transformations.

The linear algebra approach is more advanced, so we don’t cover it in detail in this course - we’ll continue to perform our transformations using regular C++ logic.

However, we’ll briefly introduce the alternative approach in this section, and we’ll also provide an additional, optional, lesson at the end of the chapter that goes a little deeper. That additional lesson will also implement these concepts using help from GLM, a popular library that provides math utilities for working with computer graphics.

The objectives and underlying theory of why we’re performing the transformations remain the same whichever approach we use - they key difference is the low-level mechanics of how the transformation is performed. Rather than creating a function to define the transformation, we can instead define a matrix - a two-dimensional grid of numbers that represents the transformation mathematically.

For example, we created a ToScreenSpace() function in the previous section, which defines a 2D transformation. It also provides the mechanism to perform that transformation - by calling the function with a position vector:

// Define the transformation
Vec2 ToScreenSpace(const Vec2& Pos) {
  return {
    Pos.x * 0.5f,
    (Pos.y * -0.5f) + 300
  };
}

// Perform the transformation
ToScreenSpace({700.0, 300.0});

That same transformation can be defined using a matrix as follows:

[0.50000.5300001] \begin{bmatrix} 0.5 & 0 & 0 \\ 0 & -0.5 & 300 \\ 0 & 0 & 1 \end{bmatrix}

We typically use a math library that help with this, ensuring the desired transformation is correctly represented by positioning appropriate values in appropriate positions within the matrix. We’ll cover this in more detail in our later lesson.

We can use this matrix to transform individual vectors, or we can arrange many vectors into a single matrix, where each vector is a column.

To make our vectors compatible with the transformation process, we need to add an additional component to them. So, for a 2D vector, we’d add a third component. This component has a value of 11 if the vector represents a position and 00 otherwise.

The five vectors we transformed in our previous example could be arranged in the following matrix:

[70000140014003000600060011111] \begin{bmatrix} 700 & 0 & 0 & 1400 & 1400 \\ 300 & 0 & 600 & 0 & 600 \\ 1 & 1 & 1 & 1 & 1 \end{bmatrix}

Performing the transformation is then done by a matrix multiplication, a process that we again typically enlist the help of 3rd party library to implement, and we’ll cover in more detail later.

Matrix transformation involves multiplying our transformation matrix by the matrix of column vectors we want to transform:

[0.50000.5300001][70000140014003000600060011111] \begin{bmatrix} 0.5 & 0 & 0 \\ 0 & -0.5 & 300 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 700 & 0 & 0 & 1400 & 1400 \\ 300 & 0 & 600 & 0 & 600 \\ 1 & 1 & 1 & 1 & 1 \end{bmatrix}

The result of this multiplication is a matrix where each of our column vectors has been transformed:

[350007007001503000300011111]\begin{bmatrix} 350 & 0 & 0 & 700 & 700 \\ 150 & 300 & 0 & 300 & 0 \\ 1 & 1 & 1 & 1 & 1 \end{bmatrix}

Comparing this to our earlier program using the ToScreenSpace() function, we should see both approaches have the same effect:

Positions   World Space -> Screen Space
Center:      (700, 300) -> (350, 150)
Bottom Left:     (0, 0) -> (0, 300)
Top Left:      (0, 600) -> (0, 0)
Bottom Right: (1400, 0) -> (700, 300)
Top Right:  (1400, 600) -> (700, 0)

The matrix approach has a few advantages:

  • We don’t need to write functions for our transformations, which saves time. Whilst our function was quite simple, transformations can get a lot more complex, especially when working with 3D spaces. Matrix techniques provide a general way to quickly create and represent transformations, including those that have rotations, scalings, shearings, and more.
  • Much like vector math, the rules of matrix math have useful properties that can help us solve complex problems. For example, if we have multiple transformations represented as matrices, we can combine their effects to a single transformation by multiplying those matrices together.
  • Computer hardware, especially the graphics processing unit (GPU), is highly optimized for performing matrix multiplication operations. This means that these approaches are typically more performant than providing our transformations as regular C++ functions.

Discrete and Continuous Spaces

When we write code that positions and moves objects through spaces using floating point numbers, we have to consider the fact that, unlike discrete integers, floating point numbers are continuous.

  • Discrete numbers, such as those represented by an int, can only take one of a specific set of values. For example, there are only two possible integers between 22 and 55 - the integers 33 and 44
  • Continuous numbers, such as those represented by a float, can potentially take any value. For example, there are conceptually endless floating point numbers between 22 and 55, with values such as 2.42.4, 2.412.41, and 2.400000012.40000001.

These concepts extend to spaces too. For example:

  • An SDL_Surface is a discrete space, as there are a limited number of possible positions within the space. Each possible position is represented by an x and y integer and corresponds to a pixel on the surface.
  • World space is typically set up as continuous, where we use floating-point numbers to represent a conceptually endless range of possible positions in the space. This gives us, or the simulations we create, the ability to control objects’ positions and movements much more accurately.

One implication of working with continuous spaces is that we need to become accustomed to working with approximations. Even though we tend to think of floating point numbers as having simple values like 2.42.4, we shouldn’t expect to be handling such well-rounded numbers in a continuous space. A value in such a space is just as likely to be 2.4000003172.400000317 as it is to be exactly 2.42.4.

Floating Point Comparisons

The main scenario in which we need to be aware of the inherent approximations of continuous values and work around them is when we’re trying to do equality comparisons.

For example, our gameplay logic might need to determine whether two objects are in the same position, so it might seem reasonable to write a check like this:

struct Object{
  Vec2 Position;
}

bool IsInSamePosition(Object& A, Object& B) {
  return A.Position.x == B.Position.x
    && A.Position.y == B.Position.y; 
}

However, when our objects are being simulated in a continuous space using floating point numbers, it’s very unlikely that their positions would be exactly equal. As such, an equality comparison using == will almost always return false, even if the object positions are so similar we would want to consider them as being equal for our simulation or gameplay needs.

For example, the numbers 2.39999958 and 2.40000031 are not entirely equal. However in most contexts, including computer graphics, we’d consider them close enough to be treated as equal. Therefore, we need to create alternatives to the == and != operators that check if objects are "close enough".

The common way of doing this is creating a function that accepts our two objects, and returns true if the difference between them is sufficiently small. This "sufficiently small" tolerance level is sometimes offered as a third argument with a default value, allowing users of the function to specify how small the difference between the values must be for them to be considered equal.

The following is an example of such a function that compares float objects, and an overload that uses similar concepts for comparing Vec2 objects. If it’s unclear why we’re using std::abs() here, we cover that in the next section:

#include <cmath> // For std::abs

// For Float Comparison
bool NearlyEqual(float A, float B,
                 float Tolerance = 0.00001) {
  return std::abs(A - B) < Tolerance;
}

// For Vec2 Comparison
bool NearlyEqual(const Vec2& A, const Vec2& B,
                 float Tolerance = 0.00001) {
  return std::abs(A.x - B.x) < Tolerance
    && std::abs(A.y - B.y) < Tolerance;
}

Our NearlyEqual() function would replace the == and != operators as follows:

if (A == B) {/*...*/} 
if (NearlyEqual(A, B)) {/*...*/} 

if (A != B) {/*...*/} 
if (!NearlyEqual(A, B)) {/*...*/}

A similar rationale applies to other comparisons, such as <= and <. For example, if A and B are approximately equal, we may not want to consider A to be meaningfully smaller than B even if A < B is technically true.

As such, we may also need to implement alternatives to these operators that make this distinction, should we ever need such logic. However, in practice, this is much less important than the == and != alternatives.

Avoiding Floating Point Numbers

Unlike with discrete numbers like integers, the underlying limitations of computer architecture makes exact floating point representations difficult, even when we’re not doing fine-grained simulations.

This limitation leads to quirks where approximations are used even in contexts where we wouldn’t expect them, such as when we’re directly using somewhat round numbers. For example, the result of 0.1 + 0.2 is not exactly equal to 0.3, which can result in unexpected behavior:

#include <iostream>

int main() {
  if (0.1 + 0.2 == 0.3) {
    std::cout << "Obviously equal";
  } else {
    std::cout << "Somehow not equal";
  }
}
Somehow not equal

As such, it’s often a good idea to determine if our problem (or the entire program) has a reasonable way to avoid floating point numbers entirely.

For example, when dealing with financial values, it would seem intuitive to store a value like $3.57 as the floating point number 3.57. Instead, it is typically recommended that we store such values as cents rather than dollars. This means we would represent a value like $3.57 as the discrete integer 357, avoiding floating point numbers entirely.

There are rarely such simple alternatives when working on things like graphics and physics simulations. In those contexts, we just embrace floating point numbers, remaining mindful of their inexact nature and working around it when required:

#include <iostream>

bool NearlyEqual(float, float){/*...*/} int main() { if (NearlyEqual(0.1 + 0.2, 0.3)) { std::cout << "Obviously equal"; } else { std::cout << "Somehow not equal"; } }
Obviously equal

Vectors and Absolute Values

This previous code is an example where we use the concept of an absolute value, which we can revisit briefly here as it has some renewed meaning in the context of vectors.

As a reminder, we can consider taking the absolute value of a number to be removing that number’s negative component, if it has one.

For example, the absolute value of 3-3 is 33. If a number is not negative, the absolute value is the same as the number itself, so the absolute value of 33 is 33.

To get the absolute value of an expression in C++, we can use std::abs() within the <cmath> standard library or, alternatively, SDL_abs() if we’re using SDL:

#include <iostream>
#include <cmath> // For std::abs
#include <SDL.h> // For SDL_abs

int main() {
  float A{3};
  float B{-3};

  std::cout << "A: " << A;
  std::cout << ", abs(A): " << std::abs(A);

  std::cout << "\nB: " << B;
  std::cout << ", abs(B): " << SDL_abs(B);
}
A: 3, abs(A): 3
B: -3, abs(B): 3

In our NearlyEqual() function, we’re using the absolute value to make the argument order of AA and BB unimportant. ABA - B is not necessarily the same as BAB - A, but the absolute values of ABA - B and BAB - A will be the same:

#include <iostream>
#include <cmath> // For std::abs

int main() {
  float A{1.001};
  float B{0.999};

  using std::abs;
  std::cout << "A - B:    " << A - B;
  std::cout << "\nB - A:    " << B - A;
  std::cout << "\nabs(A-B): " << abs(A - B);
  std::cout << "\nabs(B-A): " << abs(B - A);
}
A - B:    0.00200003
B - A:    -0.00200003
abs(A-B): 0.00200003
abs(B-A): 0.00200003

Conceptually, we can imagine an expression like abs(A - B) calculating the distance between A and B, without caring about the direction. In mathematical notation, the absolute value of a number is represented by vertical bars, for example:

3=3 \lvert -3 \rvert = 3

This vertical bar notation is the same one we use when referencing the magnitude of a vector, as in V\lvert V \rvert, because they’re fundamentally the same idea. The absolute value of some number, x\lvert x \rvert, is how far away that number is from 00, whilst the magnitude of some two-dimensional vector, (x,y)\lvert (x, y) \rvert, is how far away that vector is from (0,0)(0, 0) - the origin.

We can even consider a number like 3-3 to be a one-dimensional vector in a one-dimensional space, which makes the similarity even more obvious. A one-dimensional space is simply a number line:

Diagram showing a number line

Continuous to Discrete Conversions

After we’ve run our simulation and calculated the position of all the objects in our continuous world space, we’ll often need to transform these vectors to equivalent positions in a discrete space. For example, the screen space representation using SDL_Surface is discrete - there is a fixed quantity of pixels on our surface, represented by integers.

We cover how to implement this full world space to screen space pipeline in the next chapter, but for now, we should note that converting a floating point number directly to an integer would remove the floating point component. This is generally less accurate than we’d like - for example, it results in 3.83.8 being converted to 33, even though it’s closer to 44:

#include <iostream>

int main() {
  std::cout << "Converting 3.8 to int: "
    << static_cast<int>(3.8);
}
Converting 3.8 to int: 3

To fix this, we can explicitly round our floating point numbers to the nearest integer. We can do this using std::round() from <cmath>, or SDL_round() if we’re using SDL:

#include <iostream>

#include <cmath> // For std::round
#include <SDL.h> // For SDL_round

int main() {
  std::cout << "Rounding 3.8 to int: "
    << std::round(3.8);
  std::cout << "\nRounding 3.8 to int: "
    << SDL_round(3.8);
}
Rounding 3.8 to int: 4
Rounding 3.8 to int: 4

In the following example, we incorporate this rounding into our transformation function from world space to screen space. This ensures our Vec2 components are integer values:

#include <cmath> // For std::round

Vec2 ToScreenSpace(const Vec2& Pos) {
  return {
    std::round(Pos.x * 0.5f),
    std::round((Pos.y * -0.5f) + 300)
  };
}

Even though these values are now integers, they are being stored in float containers - the x and y variables of our Vec2 type. This is not necessarily a problem as almost every implementation of the float type uses the IEEE 754 standard, which can accurately represent integer values without any loss of precision.

Preserving the floating point type in screen space is also useful if we need to perform further transformations within that space, and having access to floating-point accuracy would be helpful in that work.

In such cases, we’d also want to hold off on rounding our values to integers until we complete those transformations. We don’t want to round our values multiple times, as each rounding has a performance cost and may also represent a loss of data/accuracy.

Summary

In more complex games, objects are typically simulated in the world space, which has different properties to the screen space. To render our object, we need to derive their screen space position based on their world space position. The key takeaways from this lesson are:

  • Screen space uses pixel coordinates with the origin at the top-left corner, while world space typically uses floating-point coordinates with various possible origins.
  • When transforming between spaces, we must account for differences in scale, coordinate system orientation, and origin position.
  • Transformations can be implemented as direct functions or using transformation matrices for more complex operations.
  • Floating-point numbers require special considerations when comparing values or converting to integer pixels.
  • We should be extremely cautious with using the == and != operators with floating point numbers. It’s usually the case that we should consider floating point values to be equivalent if their difference is very small, but not necessarily 00.
  • Converting floating point numbers to integers directly often results in loss of accuracy, as that action just discards the floating point component, effectively always rounding down. We typically want to round them to the nearest integer instead.
Free and Unlimited Access

Professional C++

Unlock the true power of C++ by mastering complex features, optimizing performance, and learning expert workflows used in professional development

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Ryan McCombe
Ryan McCombe
Posted
sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Get Started for Free
Maths, Physics and Collisions
Free and Unlimited Access

Professional C++

Unlock the true power of C++ by mastering complex features, optimizing performance, and learning expert workflows used in professional development

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2025 - All Rights Reserved