← back

2019-10-13: basic concepts of game design

Modern game development relies on a number of basic design concepts and fundamental ideas in order for all of the features players expect to; textures, models, animations, audio, particle systems, and good enough performance to run on the current hardware.

This blog post will mostly be a collection of notes that I found helpful when learning about the fundamentals of video games.

Good game design is about the in-game systems, problem solving, player experience, and game loops that keep the player hooked.

Quaternion is composed of four components, one in the real part, and the other three in the imaginary part. A quaternion is usually denoted as:

q = w + xi + yj + zk

Fundamental formula for Quaternions:

i^2 = j^2 = k^2 = i * j * k = -1

Game object orientation can be represented by axis-angles, with a mapping between an axis-angle pair and a unit quaternion

Frame coherence is a measurement of how much your game scene changes across time steps. If the position and velocity of rigid bodies do not change a lot between two time steps, then these two frames have high frame coherence.

Vertex shaders are a programmable Shader stage in the rendering pipeline that handles the processing of individual vertices. Typically they are used to perform transformations to post-projection space, for consumption by vertex post-processing.

Rasterization is the process by which each individual primitive is broken down into discrete elements called fragments, based on the sample coverage of the primitive.

Fragmentation shader is the Shader stage that will process a Fragment generated by the Rasterization into a set of colours and a single depth value.

Timeslicing is a useful technique to improve the performance of batched algorithms. Consider using it for elements of the game, such as handling NPC or enemy decision logic, assuming the game features a lot of NPCs or monsters.

During the game loop, you can account for a given object's velocity and position by adjusting for the change in time...

Velocity += acceleration * delta Time
Position += velocity * delta Time

Common degree-to-radian mapping:

30 degrees == pi/6
45 degrees == pi/4
60 degrees == pi/3
90 degrees == pi/2
180 degrees == pi

Spherical linear interpolation (slerp), where t is the interpolation parameter between 0 or 1.

Slerp(q1,q2,t) = (sin((1-t) * omega) * q1) / sin(omega) + (sin(t * omega) * q2) / sin(omega)
Omega = cos-1(q1*q2)

How to create a hovering motion:

Vector3 hover =
  new Vector3
    RadiusX * Mathf.Sin(RateX * Time.time + OffsetX),
    RadiusY * Mathf.Sin(RateY * Time.time + OffsetY),
    RadiusZ * Mathf.Sin(RateZ * Time.time + OffsetZ)

obj.transform.position = basePosition + hover;

Deadlock is when two threads are stuck (i.e. locked), each waiting for the other to finish.

Race conditions occur when two or more threads try to access shared data and then try to change it at the same time. This can be addressed by using immutables (consts) more and add additional logic (mutex) to ensure proper isolation or exclusion.

Heaps are specialized tree-based data structure which is satisfies the heap property, specifically it serves as a priority queue.

Keep in mind the different paradigms between Atomic vs Mutex; atomics allows for lockless concurrency, while mutex means mutually exclusive and involves locks.

Maps and sets in C++ are the classic sorted associative containers, with O(log n) access / insertion / erase, and O(1) move complexity. Typically they are implemented as a red / black tree, which is not cache-friendly and the lookup time is not constant. In C++11 this is addressed by structures like std::unordered_set and std::unordered_map, which have average constant time on insert / erase / lookup.

An alternative to the standard hash map or set, there is also the idea of "open addressing" which offers better hash performance, but it has a very high space and time requirement.

If you find you have difficulty with cache-misses, try using VTune to gather data about bottlenecks in code.