These are the topics we are going to cover in class each day. Links to example student videos and slides from class
- Discussed
Time.time
- If either game object is marked as a trigger, then trigger events are fired:
- onTriggerEnter
- onTriggerStay
- onTriggerExit
- If neither game object is marked as a trigger, then collision events are fired:
- onCollisionEnter
- onCollisionStay
- onTriggerStay
- Powerups like coins have the following properties
- They can't push objects
- They aren't pushed
- They don't initiate collision events
- Ground/Elevators/Platforms have the following properties
- They push objects
- They aren't pushed
- They don't initiate collision events
- Sensors (invisible colliders) have the following properties
- They don't push objects
- They aren't pushed
- They initiate collision events
- Characters have the following properties
- They push objects
- They get pushed
- They initiate collision events
- Here is a table with the same information:
| Type | Is Pushed? | Pushes Back? | Initiates Events? | isTrigger? | RigidBody? |
|---|---|---|---|---|---|
| 🪙 Coin | No | No | No | Yes | No |
| 🛗 Floor | No | Yes | No | No | No |
| ❓Sensor | No | No | Yes | Yes | Yes |
| 🏃 Character | Yes | Yes | Yes | No | Yes |
- If two colliders on different game objects do not overlap, there is no interaction
- No Physics, no events
- Break
- If we get here, we know two colliders on different game objects overlap
- If you use Physics.IgnoreCollision, there is no interaction between colliders
- https://docs.unity3d.com/6000.3/Documentation/ScriptReference/Physics.IgnoreCollision.html
- No physics, no events
- Break
- If you disable collisions in the Layer Collision Matrix, then there is not interaction between the colliders on those layer(s)
- https://docs.unity3d.com/6000.3/Documentation/Manual/LayerBasedCollision.html
- No physics, no events
- Break
- If neither of the game objects have a Rigid Body, then there will be no interaction between the colliders
- https://discussions.unity.com/t/what-are-the-oncollisionenter-requirements/104083
- No physics, no events
- Break
- If we reach this point, then the physics engine will respond to the overlap of the colliders. We know that the colliders haven't been disabled with Physics.IgnoreCollisions, we know the layers the colliders are in are allowed to collide in the Layer Collision Matrix, and we know at least one of the game objects has a rigidBody attached
- If either of the colliders is marked as a trigger, then:
- No physics, onTriggerXXX events
- If we get here, neither of the colliders is marked as a trigger and we have at least on rigid body between the pair
- If they are both have rigid bodies
- Physics response on both, onCollisionXXX events
- If one has a rigid body
- Physics response only on the one with a rigid body, onCollisionXXX events for both
- If they are both have rigid bodies
graph TD
A[Start] --> B{Overlap}
B --> |No| Z[No Interaction]
B -->|Yes| D{IgnoreCollision}
D -->|Yes| Y[No Interaction]
D -->|No| E{Layer Matrix}
E -->|Enabled| F[RigidBody Count]
E -->|Disabled| X[No Interaction]
F -->|0| W[No Interaction]
F -->|1| G{1+ Trigger}
F -->|2| M{1+ Trigger}
M -->|Yes| H
M -->|No| N[Both Physics, onCollisionXXX]
G -->|Yes| H[No Physics, onTriggerXXX]
G -->|No| K[One Physics, onCollisionXXX]
Discussion about Event handling
- 1993's The Incredible Machine utilized the mouse to create a novel puzzle game
- Gameplay from The Incredible Machine
- A collider is a component that tells the collision engine that it is listening for collisions
- A collider has a list of points that default to the points listed in the game object's polygon
- Overriding these points can make it so we can collide against simpler geometry that what is displayed, a common technique in games
- We listen for three mouse collision events
- onMouseEnter - The mouse overlapped a collider for the first time this frame
- onMouseOver - The mouse overlapped a collider that it overlapped a previous frame
- onMouseExit - The mouse no longer overlaps a collider that it overlapped the previous frame
- To do this, we need to track mouse collision this frame and the previous frame
- We listen for four mouse button and collision events
- onMouseDown - the mouse overlaps a collider and the mouse went down this frame
- onMouseUp - The mouse overlaps a collider and the mouse went up this frame
- onMouseUpAsButton - The mouse overlaps a collider, the mouse went up this frame, and the mouse has been continuously overlapping this collider since the frame the button went down
- onMouseDrag - The mouse overlaps a collider, the mouse button is down, and the mouse is moving
- To do onMouseUpAsButton this, we need to track the colliders that the mouse overlapped since the last time the mouse button went down
- Notably, when we have an onMouseExit, that collider should be removed from the list
- A naive implementation of onMouseDrag will fail when the user moves the mouse quickly. To solve this, we track on onMouseDrag on all overlaps this frame and the previous frame. If we call onMouseDrag from a collider from the previous frame, then we add readd it to the list of objects that were updated this frame.
- Deja Vu: A Nightmare Come True was an early escape room game
- Gameplay from Deja Vu: A Nightmare Come True
- We often need to know when a key or mouse button goes down or up, not just when it is held down
- For example, when to fire a laser or when to "click" a button
- To do this, we need to track what input events happened each frame.
- We clear what happened each frame with an update function in Input
- Look at collisions in a game: https://www.allsonicgames.net/sonic-the-hedgehog.php
- In order to do collisions, we need to be able to do lot of different operations on vectors
addadds two vectors togetherminussubtracts two vectorsmagnitudeis a property (getter) that returns the length of the vector- The length of a vector is the square root of the sum of the squares of its components
normalizereturns a vector that has a length of one- We do this by dividing
xandyby the magnitude of the vector
- We do this by dividing
orthogonalreturns a vector that is orthogonal (perpendicular) to the given vector
- There are three basic ways to multiply vectors in game programming:
times,scale, anddot- We can also do a cross product, but we don't need that for collision detection
- We use
timeswhen we want to scale a vector by a single number (a scalar). For example, if I want to make a polygon twice as large in all directions, I would multiply each point in the polygon by one number usingtimes - We use
scalewhen we want to scale a vector by another, non-uniform vector. For example, if I want to make a square a rectangle, I would multiple each point in the square by a non-uniform vector usingscale. Thescalefunction is similar to the mathematical idea of component-wise multiplication. - We use
dotwhen we need to find the similarity between two vectors or project one vector onto another vector. For example, if I want to know if the heading of an enemy is nearly the same direction as the heading toward the player, I would multiple those two vectors usingdot. As another example, if I want to project vector 1 on vector 2, I would multiple those two vectors usingdot.- When two vectors have an identical heading, their dot product is 1. If there are orthogonal, their dot product is 0. If they are pointing in opposite directions, then the dot product will be -1.
- Additional information can be found here: https://en.wikipedia.org/wiki/Dot_product
- Consider a point and a polygon. How do I know if the point is inside the polygon?
- What if I had a light that I shown on the point and line. If I can find a position for the light where the shadow of the point and polygon are not overlapping, then they can't be in collision.
- There are infinite positions for the "light". Smart mathematicials that shown that if we shine the light in the direction orthogonal to all the lines in the polygon, we have checked enough "lights"
- This does not work with concave polygons.
- We can use Separate Axis Theorem if we break the polygon into sub-polygons, but that is out of scope for this project.
- Working backward, write a function that takes points projected onto a line. Think of them as points that represent shadows. Can I determine if one point is between all the other points (the collision point and the projection of the polygon)
- See if the point is between the min and max of the other points
- Given a direction of a "flashlight", can I determine if a point is in collision with a polygon
- Project the point and polygon vertices onto a line perpendicular to the direction of the flashlight. See if there is an overlap
- Given a point and polygon, can I determine if there is any overlap?
- Project the point and vertices onto the lines that are perpendicular to the edges of the polygon. Look for overlap.
- Given a point and a game object, can I determine if there is any overlap?
- Move the vertices in the Polygon component based on the Transform component. Then see if there is any overlap.
| Description | Type |
|---|---|
| Collisions without any physics response. For example, Mario touching a coin. | Trigger |
| Collisions when one of the objects is fixed in place. For example, Sonic sanding on the ground. | Collision Static |
| Collisions when both objects respond to physics. For example, two marbles colliding. | Collision |
Additional information about communication. See Day 08
- Pushing to GitHub
- Add
start- We've had start functions but never used them
- Track if a game object has started
- When updating, call start first if we haven't started
- Add
instantiate- Unity has a global function called
instantiate. - We can mimic that behavior
- Unity has a global function called
- Add
destroy- We don't destroy objects immediately
- We mark them as having been deleted and remove them at the end of the update cycle
- Filter objects with
markForDelete- Remove objects that have their mark for delete flag set
- Call
onDestroyon game objects that are destroyedonDestroyis the event we call on components when their parent game object is removed
- We can simplify calls to all components in a game object with
broadcastMessage broadcastMessagecalls a given functions on all components with that function
- To help us find game objects, each game object needs a name
- We add a name variable to
GameObject - We set the name of a game object in its
constructor
- We often need to communicate within a game object
- If two components share a game object, they can communicate with
this.gameObject.getComponent() getComponentreturns a component of a given type usinginstanceof
- We often need to communicate between components on different game objects
- We start by find the game object in question
- We use the static function
findonGameObject findreturns the first game object with a given name
- We use the static function
- We then find the component we need to communicate with
GameObject.find(<name>).getComponent(<type>)
- We can't directly communicate between components on different scenes
- Instead we communicate indirectly
- One way to communicate indirectly is with
Globals - By setting a static variable on
Globals, components in other scenes can query than variable.
Reminder to follow the course policies about academic integrity.
- Time outside of class means times in front of the keyboard coding
- Working inside another engine does not count toward this class
- You can work ahead if you want
- We can create a component that renders text.
- This component is engine-specific, so we can put it in the engine
- In order to do this, we need to have public variables on the component for the text to draw
class TextLabel extends Component{
font = "20px Time"
fillStyle = "black"
text = "[No Text]"
draw(ctx){
ctx.save()
ctx.translate(this.transform.position.x, this.transform.position.y)
ctx.font = this.font
ctx.fillStyle = this.fillStyle
ctx.fillText(this.text, 0, 0)
ctx.restore()
}
}- We can create a component called Polygon that is generic to the engine
- In order to do this, we need to have public variables on the component for the list of points to draw
class Polygon extends Component{
points = []
fillStyle = "black"
strokeStyle = "transparent"
lineWidth = 5
draw(ctx){
ctx.save()
ctx.translate(this.transform.position.x, this.transform.position.y)
ctx.beginPath()
for(const point of this.points){
ctx.lineTo(point.x, point.y)
}
ctx.closePath()
ctx.fillStyle = this.fillStyle
ctx.strokeStyle = this.strokeStyle
ctx.lineWidth = this.lineWidth
ctx.stroke()
ctx.fill()
ctx.restore()
}
}[!Tip] History Moment
The success of the original Nintendo Entertainment System (simply called Nintendo at the time) and the original Super Mario Bros. was electric. People wanted more and Nintendo responded to the demand by inventing a Super Mario Bros. 2. The company took a platformer that had been released in Japan but not the US called ume Kōjō: Doki Doki Panic. Nintendo changed some sprites so the main characters looked like Mario characters and released it as Super Mario Bros. 2. This means Super Mario Bros. 2 is the only game with certain game mechanics, including pulling radishes out of the ground and flinging them at enemies.
- We can set options on the components inside of a game object
- We can update
addComponentso it takes an options argument. - We then take that options argument and apply it to the component
addComponent(component, options){
Object.assign(component, options)
this.components.push(component)
component.gameObject = this
return component
}- One workflow for creating game objects is to create a class for the game object, and instantiate an instance of that class when we create it.
- This kind of game object is called a
prefab - Prefabs should can be when ever you will instantiate a game object more than once
- This kind of game object is called a
- Another workflow for creating game objects is to instantiate a
GameObjectin your scene file and then add components to the new game object.- This kind of game object is called an
anonymous game object - Anonymous game objects can be used when you only instantiate a game object once.
- For example, anonymous game objects are great for text that appears once in one scene.
- This kind of game object is called an
The following code demonstrates both the instantiate of a prefab and the instantiation of an anonymous game object. Note that when we create the anonymous game object, we set the value of the TextLabel component using our new addComponent method
//Instantiate a game object from a prefab
this.instantiate(new TriangleGameObject(), new Vector2(300, 300))
//Example of an anonymous game object
const title = this.instantiate(new GameObject(), new Vector2( 500, 500))
title.addComponent(new TextLabel(), {text: "BAT ATTACK"})- Not all computers run at the same speed, and at times the same computer will run at different speeds during the lifecycle of a game.
- We need to make our game
frame rate independent - We do this by tracking a variable called
deltaTime.- We multiply all movements by this variable.
- This makes movement speed a rate of pixels per second
class Time{
static deltaTime = 1/60
}- Tracking deltaTime by itself will not make our game frame rate independent.
- We need to ask how much time has passed between each frame
- We do this by adding a new argument to our game loop, which gets called by requestAnimationFrame
static gameLoop(time){
if(Engine.lastTimestamp){
const diff = time - Engine.lastTimestamp
const diffInSeconds = diff / 1000
Time.deltaTime = diffInSeconds
Engine.lastTimestamp = time
}
else{
Engine.lastTimestamp = time
}
Engine.update()
Engine.draw()
requestAnimationFrame(Engine.gameLoop)
}- First self-assessment/quiz next Monday
- Copy v transcribe (review AI)
- Upcoming sprint expectations
- You can study JS as part of your sprint
- You can plan the scenes, game objects, and components in your game. Just make sure you can show it to us during the sprint.
- You can review/finish transcribing the engine. You can go through and add comments to help you understand the concepts.
- Otherwise, work on your engine and game
- What is a Scene v Game Object v Component
[!Tip] History Moment
In 1983, there was the first video game crash. Companies has over-invested in video games, flooding the market with competing consoles. Additionally, there was no quality control on games, so developers would rush games to market that were buggy. This created an opening for Nintendo. The created their own console. In order to publish on the Nintendo, you had to have your game reviewed and approved by Nintendo. This meant that games were much less likely to be buggy. This, plus the improved hardware on the Nintendo, lead to a new interest in the video game market.
The flagship game for the Nintendo was Super Mario Bros. which set the standard for scrolling platformers.
- Add an additional game object to the Day 03 code using Game Objects and Components
- The game object should draw a triangle
[!Note] FAQ: How do I add a new scene to my game?
In the
gamefolder, create a new file that follows this pattern:class NewScene extends Scene{ constructor(){ super() this.instantiate(new /*reference to game object class you want to instantiate*/(), new Vector2(/*location of new game object*/)) /* Continue adding game objects as needed */ } }! Don't forget to add a
<script src="[scene file name].js"></src>to yourindex.htmlfile
[!Note] FAQ: How do I add a new game object to my game?
In the
gamefolder, create a new file that follows this pattern:class NewGameObject extends GameObject{ constructor(){ super() this.addComponent(new /*reference to component class you want to add*/()) /* Continue adding components as needed */ } }
- Don't forget to add a
<script src="[game object file name].js"></src>to yourindex.htmlfile- In order for you to see your new game object, it needs a component that draws
- You also need to add the game object to a scene before it will be in your game
[!Note] FAQ: How do I add a new component to my game?
In the
gamefolder, create a new file that follows this pattern:class NewComponent extends Component{ start(){ /* Code for the component when it starts*/ } update(){ /* Code for the component when it update*/ } draw(ctx){ /* Code for the component when it updates*/ } }
- Don't forget to add a
<script src="[component file name].js"></src>to yourindex.htmlfile- In order for your component to be in your game, it needs to be attached to a game object that is in a scene
- How is input handled by the computer?
flowchart TD
requestAnimationFrame-->input[Handle Input]-->requestAnimationFrame2[requestAnimationFrame]-->input2[Handle Input]
- How can we capture keyboard changes?
- 🛝See slides on Input
class Input{
static keysDown = []
static keyDown(event){
Input.keysDown.push(event.code)
}
static keyUp(event){
Input.keysDown = Input.keysDown.filter(k=>k!=event.code)
}
}Add the following to the start() function of Engine.js
addEventListener("keydown", Input.keyDown)
addEventListener("keyup", Input.keyUp)- Move a game object on the screen based on keyboard input
- We do this by listening for keyboard events in the
update()function of a component - Here is an example of what this might look like:
if(Input.keysDown.includes("ArrowRight"))
this.transform.position.x += 1
if(Input.keysDown.includes("ArrowLeft"))
this.transform.position.x -= 1- We don't need most of the code in
index.htmlnow. - We can clear it out so it it just the following:
class Vector2 {
constructor(x, y){
this.x = x
this.y = y
}
x
y
}
Engine.currentScene = new MainScene()
Engine.start()- Why do many games use a combination of inputs, e.g. mouse and keyboard instead of just keyboard or mouse?
- Upcoming sprint expectations
- You can study JS as part of your sprint
- You can plan the scenes, game objects, and components in your game. Just make sure you can show it to us during the sprint.
- You can review/finish transcribing the engine. You can go through and add comments to help you understand the concepts.
- Otherwise, work on your engine and game
- What is a game loop?
- What is a vector?
- Look at a game. For example, look at a classic Nintendo game
- What parts of the game would be in all or most games? These would be engine-specific
- What parts of the game are very specific to this game? These would be game-specific
- By separating our code into engine-specific and game-specific code, we start to create an engine. This makes it easier to create games and prepares us to use a commercial game engine.
[!Tip] History Moment
The 1983 Mario Bros. Game (notice that it is not Super Mario Bros) was released by Nintendo for the Atari console. It is the first game in the Mario franchise to feature Luigi.
- Go through the Day03 code and label the code as being engine-specific or game-specific
- Start
- Update
- Draw
- Engine
- An engine is a collection of scenes.
- An engine tracks the current scene
- Scenes (also levels or stages)
- A scene is a collection of game objects
- Game Objects (also actors or pawns or entities)
- A game object is a collection of components
- Components (also scripts)
- A component has the mutable data about a game object
- A component has the start, update and draw functions for a game object
flowchart LR
Engine --[Collection of]--> Scene
Scene --[Collection of]--> GameObject
GameObject--[Collection of]-->Component
- Create the files for engine-specific classes
- Engine
- Scene
- GameObject
- Component
- Add the start, update, and draw functions to each engine-specific class
- Create the files for game-specific classes
- MainScene
- BatSymbolGameObject
- BatSymbolController
- Add the constructor, start, update, and draw functions to each game-specific class
- Rewrite the code so that the html code uses these new classes (see Final code section below).
- Look at a modern game that isn't even 2D. Where do you see Scenes, GameObjects, and Components
- Can you add a second kind objects that has a random velocity and is colored red using this architecture?
- This is the link for the final code we generated on Day03
- No class on next week
- What is the difference between the Box Model, SVG, and Canvas?
- What is the difference between the JS keyword
letandconst?
- In this class, a game is an enjoyable, interactive, visual simulation.
- How are we going to learn game programming?
- Learn the math
- Learn the architecture
- Practice
- requestAnimationFrame
- 🔗Additional information:
- MVC
- gameLoop formalization
- 🔗Additional information: A blog post about what a game loop is
- What is a vector
- 🔗Additional information: A Wikipedia article about Vectors
- Adding Vectors
- 🔗Additional information: A website about adding vectors
- Velocity
- 🔗Additional information: A Wikipedia article about Velocity
- classes in JS
- 🔗Additional information:
- constructors in JS
- 🔗Additional information:
- class functions in JS
- fields in JS
- 🔗Additional information: MDN article about class fields
- Create a simple bouncing triangle simulation using a new Vector2 class. (See Final Code section.)
- Why is creative mode in Minecraft considered a game while a painting app is not?
- Combining classes, vectors, and our original code, we arrive at our Day 02 Code.
- Can you change the code to make all the vertices of the triangle to have their own independent velocity?
- Can you make the above change using arrays so that you don't need new variables for each vertex?
- Welcome to class
- Get a GitHub account
- Game Programming Course Layout:
-
Loading
graph LR CS2510["CS2510 Introduction to Game Programming"]-->CS3510["CS3510 Advanced Game Programming"] CS2510-->CS4620["CS4620 3D Computer Graphics"]
- CS 2510, Introduction to Game Programming
- Build a 2D game engine and a game from scratch in JavaScript
- CS 3510, Advanced Game Programming
- Build a 3D game using a commercial game engine (Unity) as a team
- CS 4620, 3D Graphics
- Understand how to create and drawing 3D assets
-
- Many students use their capstone to build something game-related
- The art department has courses on developing 2D and 3D assets
- Maverick Meadow in the UNO student organization focused on game development
- We are going to build a 2D game engine and game in JavaScript
- So we can focus on programming, not gathering assets, our games in this class will not include:
- Images (Including emoji)
- Sounds
- I will be using the VS Code IDE in class, but you can use any IDE
- I will be using the Live Server in VS Code, but you don't have to.
- You can see some examples of what previous students have done on YouTube
- Box Model
- SVG
- 🔗Additional information at:
- Canvas
- 🔗Additional information at:
-
Structure of an HTML document
- doctype
- html
- head
- body
- script
- Example code: Code from the instructor showing basic HTML Structure on GitHub
- 🔗Additional information: W3 Schools Introduction to HTML
-
Access elements in JS
- 🔗Additional information: W3 article about Query Selector
-
Declaring variables in JS
- let and const
- Example code: A file written by the instructor that is designed to teach about JavaScript
- 🔗Additional information: Geeks for Geeks about let and const
-
Good Introductory Websites in JS
- Showing color
- See slides: 3 Ways to show Color
- 🔗Additional information: W3 School about named colors
- 🔗Additional information: Website about rgb and hexadecimal values
- Paths/Polygons
- 🔗Additional information: Website about drawing paths
- Circles (Arcs)
- Introduction to radians
- Text
- See slides: Fonts
- 🔗Additional information:
- Example code: Code written by the instructor to show what we are learning
- Take what we have learned about drawing and draw something more advanced. Here are some ideas to try:
- Use an HTML canvas to draw the basic outline of a game you like. We can call this "blocking" out a game.
- Block out a game you enjoy using the basic drawing tools we use in class. Here are some examples of the instructor blocking out the original Super Mario Bros game.
- Example code:










