# Game Development

## BytePath - A Full Game in Lua

### Part #1: Game Loop

• A Primer on Lua
• All values are true, except for false and nil
• If one references a variable that is not defined, nil is returned
• love.load function is called before game loop is started - use to prepare game state & load assets
• love.update(dt) and love.draw() are called every frame
• The screen is automatically cleared before each call to love.draw()
• Default window size is 800x600
• Load an image with love.graphics.newImage(imagePath)
• Draw an image with love.graphics.draw(love2dimage, x, y)
• Default love2d game loop is provided if a user does not define love.run()
• First, a random seed value is set by using the current OS time
• love.load(args) is called, being passed the commmand line arguments passed to love2d as an argument
• The timer takes a step forward so time spent in love.load doesn’t affect our first update & render frames
• Delta time variable is declared
• The main loop is entered
• love.events.pump is called to push events to event queue
• Iterate over all events in queue with love.events.poll
• love.timer.step is called to step forward to measure the time between the two last frames. Delta time is updated with love.timer.getDetla
• love.update(dt) is called
• The backbuffer is cleared using love.graphics.clear(love.graphics.getBackgroundColor())
• love.graphics.origin is called to reset all transformations
• love.draw() is called
• Buffers are swapped with love.graphics.present
• Finally, love.timer.sleep(0.001 is called. (reason why)
• The main loop in love2d is considered a “Variable Delta Time” in “Fix your Timestep!”
• Individual love2d systems can be enabled or disabled in conf.lua

### Part #2: Libraries

• In addition to love2d, the libraries being used in this project:
• In Lua, one can use require to “include” a file (in reality, it will execute/interpret the file). All global variables defined in that file will now also be in our global scope after requiring
• e.g.: require 'libraries/foo'
• Modules can also return a specific value by including a return statement at the end of the file and returning local defined variables
• love.filesystem.getDirectoryItems(dirPath) returns a table of all files & folders within a given directory
• In Lua, object:method vs object.method:
• When a method needs access to self, define and call the method using : colons syntax. self will implicitly be passed as the first argument
• When a method does not need self, use a . dot
• Classes exercise answers

• 4: The value of counter_table.value will be 2. The increment function receives an argument named self because it is being defined as a closure (?) The argument can renamed to anything & the example would still work. Here, self represents the table returned by createCounterTable()
• 5: See code below

function returnTable()
return {
a = 1,
b = 2,
c = 3,
sum = function(self) self.c = self.a + self.b + self.c end,
}
end
• 6: If a class has attribute cat, there cannot be a method named cat on the same class. The attribute will override the method.

• 7: Global variables/state are held in a table structure. Any variables references that are not local to current scope are looked up in the global table

• 8: There is no guarantee being made that our parent classes will be included before child classes. The only solutions I can think of aren’t exactly clean: either store all base classes in a seperate folder that gets loaded first, or manually include all base classes before including all child classes.

• 9: We can alter the requireFiles function to work with local class definitions with the following adjustments:

function requireFiles(files)
for _, file in ipairs(files) do
local file = file:sub(1, -5)
print('requiring ' .. file)
_G[file] = require(file)
end
end

If the file name is HyperCircle.lua, we will add a new attribute to the global table (referred to by the special variable _G) with the name of the file - in this example, _G[HyperCircle].

• Input exercises answsers:

• 1: When mouse1 is pressed, a random number is printed. Nothing happens on button release or hold down
• 3: Multiple keys can be bound to same action. Holding both keys at the same time will cause two separate actions to be performed. Multiple actions can also be bound to the same key.
• 4: Don’t have a gamepad hooked up to Linux PC
• 5: Don’t have a gamepad hooked up to Linux PC
• 6: Don’t have a gamepad hooked up to Linux PC
• The hump.timer library provides useful methods such as after(seconds, callback), every(seconds, callback) (or every(seconds, callbacks, numTimes)). every() can also be canceled by returning false from the callback

• The most useful method is tween(), which will interpolate two values over a period of time using different “tween modes”. This is useful for animation. (list of all tween modes)
• tween() can also take an additional argument after tween mode which is a callback that is fired when tween() finishes
• All timer methods return a handle that can be used to prematurely cancel a tween with timer:cancel(handle)
• Timer exercises:

• 6: As far as I can tell, you can’t tween a value with timer if it isn’t in a table, so this isn’t possible?

### Part #3: Rooms and Areas

• Using the idea of a “Room” (or state) from GameMaker’s docs
• This article confusingly uses “Room” in two different contexts:
• First, as an alernataive term for State
• Secondly, as a location in a game (e.g.: Binding of Isaac/classic Zelda dungeons are made of interconnected rooms.)
• I’ll be referring to the first kind of room as State for the rest of the notes to reduce confusion
• Simple implementation of State in Love2D
• love.load creates a global variable named current_state that holds our (drumroll) current state!
• We create states objects with a common interface (update(dt), draw(), etc) so we can have current_state be our single point of interaction with states
• In our gotoRoom(room_type) function, we instantiate a new instance of the state being passed and give current_state a reference to our newly created state
• This method has limitations: what if we want to switch between states without creating/removing the old instances of said states?
• Nuclear Throne’s game flow (of states): Menu -> CharacterSelect -> Game -> MutationSelect -> Game -> …
• Persistent state implementation:
• Keep a table called states which holds all states with a string identifier
• When moving to a new state, check if the destination state exists in the table
• If it does, simply make it the new active state
• If not, create the state, add it to the table and then make it the new active state
• State (“Room”) exercises
• 2: Equivalent of State in other engines:
• Unity3D: Scenes are the object most similar to “Rooms” in GameMaker
• Godot: Scenes are the object most similar to “Rooms”
• HaxeFlixel: FlxState
• Construct2: Layouts
• Phaser: State
• 3:
• Persona 5 potential state flow
• MainMenu -> FileSelect (can be used as both save & load) -> Field -> Transition -> Field -> Battle -> Field -> Cutscene -> Battle
• NieR: Automata potential state flow
• MainMenu -> FileSelect -> Loading -> Field (free roam) -> Field (Hacking) -> Cutscene
• NieR has different play styles (character action, twin stick shooter, shmup) within the same state - how does this work?
• 4: Lua garbage collector: incremental mark-and-sweep
• When an object is no longer referenced by Lua, it will be maraked as deletaable and may be deleted on the next run of the garbage collector
• Garbage collector pause: How long to wait until starting a new cycle
• Garbage collector step multiplier: Controls speed of collector relative to memory allocation
• Mark and sweep: Travel through all variables in our code and see which they reference. If we find any unreachable objects, they are marked for deletion.
• Pros: No reference counting, no cyclic referencing problems
• Cons: Can be slow/expensive
• Iterating through an array/table of objects backwards means that when removing an entry, we don’t end up skipping any as we’ve already processed the prior entries that would have been affected by this operation
• In lua, prefixing an array/table with # returns the length of given array
• Table manipulation in native Lua is done via the global table object’s methods
• Area exercisese:
• 4: The purpose of local opts = opts or {} is to provide a “default paramete” of an empty table if no opts argument is passed to the addGameObject method.

### Part #4: Exercises

• 2: a = 2, b = 2, c = 3, d = 4, e = 4, f = 1, g = 2
• 5: We can trigger a garbage collection cycle with the global function collectgarbage('collect'). The first argument can be other values than "collect" which would perform different operations with the GC
• 6: The collectgarbage('count') method with the "count" argument will return
• 7: I’d use a combination of error(message) and os.exit(error_code)
• 12: If a method or attribute exists on an object, trying to access it (e.g.: foo.value for a value, or foo.method without the function argument list or colon syntax for methods

## State Management

### Managing Game States in C++

• GameState interface / class should have usual methods:
• init()
• cleanup()
• pause()
• resume()
• update()
• draw()
• State manager is used to control states, switch between them, etc
• A stack of states is used (in Rust, an internal Vec<GameState> is a member of our StateManager struct)
• Has same methods as GameState with some additions:
• change_state(GameState)
• push_state(GameState)
• pop_state()
• A way to quit, and a private member variable to keep track whether we have quit (e.g.: whether we are still running or not)
• States themselves should have the ability to change the current state

### SO: “How can I implement a main menu?”

• Think of certain “states” as “overlays” instead (e.g.: pause menu, Tetris game over menu)
• Buuut we’re not going to do that, we’ll just implement it as a state stack
• When going through menus, push() when entering and pop() when leaving
• When entering main game, clear stack & add game
• If stack isn’t empty in the main game, update & render each state in the stack
• This allows for having transluscent pause screens

## Rendering

### ECS & Rendering with OpenGL

• No, actors should not draw themselves.
1. It violates the single responsibility principle
2. Makes extension difficult; if the way drawing is done in general is changed, every actor will require some code changes.
• Better for a RenderSystem to handle the drawing. RenderSystem’s draw() method takes a render description object, holding things like:
• (Probably shared) geometric data (VBO)
• Instance-specific transformations
• Material props. such as colour The actors then just hold a render description object that can be changed.

SO: How to handle materials in an Entity/Component system

“Materials are a graphics concept, and belong in your renderer. A renderer is too low-level a piece of architecture to be built on top of an entity system. Entity systems should be for higher-level game objects. Not everything needs to be a component, and in fact, it’s generally a bad idea to try to force everything into a single paradigm like that. It creates a least-common-denominator solution.” - Josh Petrie

This is good advice, IMO.

• A ‘material’ is a type in the renderer. Another type, ‘Renderable’, which holds a reference to that material & API for manipulation.
• ‘Aspect’ component contains a reference to ‘Renderable’.

Reddit: OpenGL rendering and ECS

• Render classes separate from ECS
• Systems that provide content (ResourceManager?)
• … still uses ECS for sprites though, which is weird.

GitHub: rodrigosetti / azteroids

• RenderSystem: for all entities with Appearance component, get entity’s position & appearance components. If it has a position, update uniforms. However, this system then has apperance->render, meaning the entity renderse itself? Weird…

### SDL2: Resolution-independent rendering

With the Rust SDL2 bindings, instances of Renderer have the set_logical_size(w, h) method. If window size is different, SDL will scale everything appropriately.

However, this approach will letter box the viewport in certain window sizes. If writing a tiling-based engine, this could cause artifacts between tiles. Use set_integer_scale to fix that.

Sources: 1

### 2D Pixel Art Rendering & Asset Resolution

• Example resolutions to target
• 320x200 (scales to 720p/1080)
• 1280x720 (scales up/down easily)
• 400x240 (2) - doesn’t scale to 16:9 resolutions
• 320x180 - fits 16:9, scales to 1080p but small
• 320x216 - bigger, fits 16:9
• 480x272
• 256x224
• 1920x1080
• 1080x620
• 960x640
• 640x480
• Just use a 16:9 aspect ratio (unique design)
• Low resolution & scale up in-game (pixel art)
• Example asset sizes
• 16x16 for all tiles, make characters 1.5 - 2 tiles high
• 24x24,32x32,40x40,48x48 sprites, 16x16 tiles
• 32x32 for tiles, 64x64 for character sprites
• Mario/Megaman are roughtly 1/9th the height of the screen
• Godot Settings
• Display
• pixel_perfect_snapping: ??? (2)
• stretch_mode: 2D (1), viewport (1)
• stretch_aspect: keep (2)
• Render
• default_clear_color: self-explanatory (set to black)
• filter: off (don’t need filtering with 2D pixel art) (3)
• gen_mipmaps: off (not needed for pixel art games i guess?) (2)
• Physics2d
• default_gravity_vector: If making a top-down game, set this to Vector2(0, 0)

## Catlike Coding Unity3D Tutorials

### General Tips

• In Scene view, to get a similar perspective as the camera, select the camera and from the menu select Game Object \/ Align View
• Destroy(gameObject) to destroy the current object that script is attached to
• When a script has the [RequiredComponent] decorator, it will ensure that component exists on whatever Object the script is added to by adding the component itself
• Gets all components of type in children: GetComponentsInChildren<TYPE>();
• To get the camera to point at your current view in the editor, select the camera and select Game Object -> Align With View

### 1.1 - Clock

• In Unity3D, everything that is placed in a scene is a GameObject. GameObject’s have the following in common:
• They have a name
• They have a tag
• They have a layer
• They have a Transform component
• They can be marked as static
• By itself, it’s just an empty container
• A GameObject can be the child of another GameObject. The child inherits all transformations, which are applied prior to the child’s transformations.
• Rotations orbit around parent
• using UnityEngine imports stuff from the UnityEngine namespace into the current file (module?)
• MonoBehaviour is a class from UnityEngine. At this point, it seems like anything that is going to be used in Unity as a component has to inherit from MonoBehaviour
• Scripts (or ~MonoBehaviour~s) can be attached to Unity objects by:
• Dragging the script from Project to the Hierarchy view
• Add Component button in the Inpsector
• To programmatically move something from a script, we need to access its Transform component.
• Adding public Transform class variables to our class will show spots for us to drag and drop components from the Unity editor to “attach” them
• If a MonoBehaviour has a method implemented with the signature private void Update (), it will be called once per frame.
• It doesn’t need to be public as only Unity will invoke the method
• System.DateTime is a struct with a static property, Now, that has the current time. Can be used to calculate time deltas
• By directly setting the localRotation of the Transform objects we have as class variables, we can rotate our clock arms
• A quanternion represents a 3D rotation
• Transform.rotation refers to the final rotation of an object, after accounting for the parent object’s rotation
• TimeSpan & DateTime.Now.TimeOfDay (which is a TimeSpan) gives us access to fractional representations of elapsed hours, minutes and seconds. We can use these to move the arms of the clock in a more analog, “natural” fashion (as opposed to DateTime which returns its values as integers).

### 1.1 - Fractal

• What is a fractal? It’s an abstract object (i.e.: doesn’t refer to any one physical object) that can exhbit similar patterns at increasingly small scales. Also known as expanding symmetry or evolving symmetry. Fractals “also include the idea of a detailed pattern that repeats itself” (Fractal, Wikipedia, June 23 2017)
• In this scene, we will be utilizing the idea that details of a fractal can resemble the entire object
• We can programatically define what our object looks like by giving it public Material and Mesh variables
• In our Start() method, we can add new gameobjects to use these variables in the following way:

gameObject.AddComponent<MeshFilter>().mesh = mesh;

This line boths add a MeshFilter component to our current game object, and assigns our public Mesh to the MeshFilter's mesh property.

• If this line was in our Start() method of our game object, we would not see these newly added components on the Inspector until we’ve entered Play Mode
• A mesh is a collection of points that make up triangles - these form the geometric basis of our objects

• A material is represents the visual properties of an object. They consist of a shader and any associated data

• We can create new instances of an object with the new keyword.

• We can add components to game objects with gameObject.AddComponent<TYPE>();

• this refers to the current instance of an object within class methods

• In this example, Initialize() is called before Start():

private void Start () {
if (depth < maxDepth) {
new GameObject("Fractal Child").
}
}

private void Initialize (Fractal parent) {
mesh = parent.mesh;
material = parent.material;
maxDepth = parent.maxDepth;
depth = parent.depth + 1;
}

First, a new game object is created. Then the Fractal component is added. If the object has Awake () and onEnable () defined, those would now run. AddComponent() finishes and Initialize () runs directly after. Start () gets called on the following frame.

• “The parent–child relationship between game objects is defined by their transformation hierarchy.” So if we want to establish a parent-child relationship between GameObject, set the child’s transform.parent to the parent’s transform component.

• Vector3.one is an identity vector of (1,1,1)

• We can scale an object by setting its transform.localScale property to Vector.one * OUR_SCALE_VALUE*

• A coroutine is similar to generators in other languages - a function that can be paused/resumed

• Return type is IEnumatator
• Requires a yield statement somewhere in the method
• We can start a coroutine by passing our called method to StartCoroutine ()

• We can get Unity to pause for a certain amount of time with the WaitForSeconds (float) method

• Coroutines are essentially iterators - each frame, a new value is iterated over until the coroutine is exhausted or forever. Objects are returned from iterators via the yield keyword

• If Random.Range() is called with two floats, the range is inclusive. If called with two integers, the range is exclusive (? this seems weird)

• The static method Colour.Lerp(a, b, t) will linearly interpolate between colours a and b by t.

• Dynamic batching is when meshes with the same material are combined to reduce the amount of communication needed between CPU and GPU

• By changing our material’s colour, we create a duplicate of that material which disabled dynamic batchinga

• The default value for variables that are not primitive (e.g.: arrays, objects) is null

### 1.1 - FPS

• Drag an object from the Scene Hierarchy view to the Project/Asset view to turn it into a prefab: an Object we can clone easily, pretty much an Object factory
• FixedUpdate() is called every fixed framerate frame. This is where dealing with Rigidbody should happen instead of Update(). This is because it’s easier to have the physics be deterministic (run the same on all systems) when it’s on a fixed timestep.
• Using the Profiler tool within Unity can show CPU or memory spikes caused by running the project from within the Unity editor. Create a standalone build with Autoconnect Profiler enabled to get a clearer profiling view.
• Unlike Time.deltaTime, Time.unscaledDeltaTime is not influenced by the time scale set in the project settings. Use this value to get accurate FPS readings (1f / Time.unscaledDeltaTime).
• Unity’s UI system needs a Canvas object to be drawn on. Then use Panels, Text, and others.
• Anchors are used to make sure objects stay place regardless of window size
• We can make sure an object contains a certain component with the decorator [RequireComponent(typeof(Rigidbody))]

### 1.1 - Object Pools

• Object pools are a way to reuse objects instead of constantly creating & destroying objects
• We’re using the FPS counter from the last tutorial as a prefab here.
• Anytime we’re spawning prefabs with a script, we need at least the following:
• A timeBetweenSpawns float: duration to wait between spawning a new object
• A timeSinceLastSpawn float: duration until the next spawn
• An array of the prefabs: Refer to them as an array of the type we created in a C# script (e.g.: If our script has a class called Stuff and we want our prefabs to be of this type, declare an array like so: public Stuff[] prefabs
• Tags are useful to compare against (e.g.: what trigger was entered in OnTriggerEnter()
• Can make an invisible trigger area where, if objects enter, they get destroyed - helps prevent too many objects being created and crapping the system out
• Marking a property as [System.NonSerialized] means that field will not be saved as a part of the prefab.
• By using DontDestroyOnLoad(obj), we can ask Unity not to delete an object when loading a new scene. Useful for Object Pools

### 1.1 - Curves and Splines

• This chapter goes over how to create in-editor tools and handles. I’m not taking extensive notes, just casually going through this chapter.

### 1.2 - Procedural Grid

• A mesh is “a construct used by the graphics hardware to draw complex stuff” 1
• A collection of points defined by vertices “plus a set of triangles […] that connect these points”
• /So I’m guessing that they mean that the vertices that define points are just given out of order, and we usually have to do something like tellling the shader (or GPU) how to interpret those points as triangles/
• Or maybe this means something like we need to tell the vertex shader how to interpret the vertices in order to make triangles
• In Unity3D, if we want to have a game object dsplay a 3D model, we need the following components:
• MeshFilter - holds a reference to the mesh we wish to show
• MeshRenderer - use it configure how the mesh is rendered: which material to use (multiple can be used at once), shadow settings, etc
• A material defines how the mesh apears - in Unity3D, this is a default diffused (matte) solid white
• One of the options that can be set on a material is providing an Albedo map. An albedo map is a texture that represents the basic colour of a material. Not quite the same as diffuse map but similar enough (?)
• How do we know how to project the texture onto our mesh? With the use of 2D texture coordinates. The two dimensions are usually referred to U and V and have values between (0, 0) and (1, 1)
• Gizmos “are visual cues” visible in the Unity editor. We can use them to draw objects, usually done in a OnDrawGizmos() method
• Gizmos won’t move with the object - they are drawn directly into world space, not the object’s local space. When drawing the gizmo, use the object’s transform.TransformPoint(vert) method to transform the point into the object’s local space
• Seems like a useful place to put ‘debug info’?
• How would we create our own mesh? For example, a rectangle?
• Create a script that has the dimensions as public properties: public int xSize, ySize
• Use the RequireComponent decorator to automatically add both MeshFilter and MeshRenderer components (which, as discussed before, are both needed to display something in Unity3D)
• We need an array of 3D vectors for the points. # of vertices depends on size of grid. Vertices = (x_length + 1)(y_length + 1)
• After verifying that the vertices were in the correct position with the use of Gizmos, we can start creating the mesh. We need to create a private reference to the mesh in our component, create it by getting a reference to that now-emtpy component and using new to create a Mesh. We need to also assign this reference to our component’s MeshFilter
• We also want to assign our created vertices to our mesh’s vertices property
• At this point, nothing shows up because we haven’t supplied the second part of the mesh: the list of triangles. Triangles are defined by an array of vertex indices. Each triangle has three vertices so three consecutive indices describe a single triangle.
• A normal is a vector perpendicular to a surface. They point outwards. They are usually unit lengthed (1). Used to determine angle at which light ray hits surface
• Normals are defined in tangent space - a 3D space that flows around the surface of an object. Normals represnt up but which way is right? This is defined by the tangent.

### 1.2 - Rounded Cube

• Previous chapter was 2D; time for 3D!
• A cube has six faces. We can use six instances of the grid from the previous chapter to construct it
• Number of vertices needed for cube: 2((# x + 1) (# y + 1) + (# x + 1) (# z + 1) + (# y + 1) (# z + 1))
• When edges of faces touch, the vertices will overlap - this is pretty common

## Interesting Articles

### Fix Your Timestep!

#### Simple: Fixed Delta Time

let mut t: 0.0;
let dt = 1.0 / 60.0;

loop {
update(t, dt);
render();

t += dt;
}

If physics delta time matches refresh rate and one can guarantee the updae loop will take less than a frame, this is ideal. However, it’s nearly impossible to make this guarantees across all systems.

#### Variable Delta Time

let mut t: 0.0;
let mut current_time = std::time::Instant::now();

loop {
let new_time = std::time::Instant::now();
let frame_time = new_time.from(current_time).as_nanos / 1000000000; // from ns to s
current_time = new_time;

update(t, frame_time);
render();

t += frame_time;
}

Allows delta time to be variable but this will affect physics with inconsistent delta times.

#### Semi-fixed Timestep

let mut t: 0.0;
let dt = 1.0 / 60.0;
let mut current_time = std::time::Instant::now();

loop {
let new_time = std::time::Instant::now();
let mut frame_time = new_time.from(current_time).as_nanos / 1000000000; // from ns to s
current_time = new_time;

while frame_time > 0.0 {
let delta_time = std::cmp::min(frame_time, dt);
update(t, delta_time);
frame_time -= delta_time;
t += delta_time;
}

render();
}

Delta time has a max. value here; it is “semi-fixed.

However, this has a problem: multiple update steps are taken every render frame. If an update is more expensive than a render, this will lead to issues such as the “spiral of death”. This occurs when simulating N seconds of physics takes M real-world seconds, and M > N. The update loop will always be behind and struggling to catch up but it will always be too expensive to do so.

#### “Free the Physics” / Back to Fixed Step

const dt = 0.01;

let mut t: 0.0;
let mut current_time = std::time::Instant::now();
let mut accumulator = 0.0;

loop {
let new_time = std::time::Instant::now();
let mut frame_time = new_time.from(current_time).as_nanos / 1000000000; // from ns to s
current_time = new_time;

accumulator += frame__time;

while accumulator >= dt {
update(t, dt);
accumulator -= dt;
t += dt;
}

render();
}

We want a fixed delta time for simulation & the ability to render at different frame rates.

Instead of thinking about it like “updates need to take less than renders”, frame it (HA!) like this: Render frames produce time and the simulation consumes it in discreet delta time-sized chunks. This allows render FPS to vary but simulations are always moved forward in delta-time sized chunks.

The remaining time in the accumulator will build up over time. This will sometimes cause two sim. frames per render. This can cause a slight difference in sim. FPS and render FPS, causing what is known as “temporal aliasing”.

#### The Final Timestep

const dt = 0.0;

let mut t = 0.0;
let mut current_time = std::time::Instant::now();
let mut accumulator = 0.0;

let mut prev: State = State::new();
let mut curr: State = State::new();

loop {
let new_time = std::time::Instant::now();
let mut frame_time = new_time.from(current_time).as_nanos / 1000000000; // from ns to s
if frame_time > 0.25 { // where did this constant come from?
frame_time = 0.25;
}
current_time = new_time;

accumulator += frame__time;

while accumulator >= dt {
prev = curr;
update(curr, t, dt);
accumulator -= dt;
t += dt;
}

let alpha = accumulator / dt;
let state: State = curr * alpha + prev * (1.0 - alpha);

render(state); // render state should match physics state
}

The ‘remainder’ in the accumulator can be used to see how much time is required before the next sim. step. We can use that time value (the ‘alpha’) to belond (via linear interlopation) between the current & previous physics states to keep sim. and render steps in porportional sync.

### Breaking the NES for Shovel Knight

• Each “pixel” in SK is 4.5x4.5 pixels at 1080p; virtual res of 400x240
• Background tiles (like most NES games) are 16x16
• Same amount of vertical tiles as an NES game; more horizontal space though

• Sprite flickering occured on the NES when more than 8 sprites were displayed on the same horizontal line

• NES could only display 54 different colours (source)

• Sprites on NES were limited to 4 colours (or 3 + transparency)

• NES game had to fit in 32KB of memory, although on-board chips called “memory mappers” could increase storage size to 0.5 - 0.75MB