This repository contains the files to @elmermakkinga and @layetri's final project for the Music & Technology SYSBAS course at HKU University of the Arts Utrecht, the Netherlands. The result is a game called "r0LL", written in P5.js, with a sound generation engine written in MaxMSP. Try it live at layetri's website.
To get started, make sure you have nodejs
and npm
installed. This project depends on express
, which is already included in package.json
.
To install, browse to the project root directory and run npm install
. NPM will now install the project dependencies. After this, you can use npm start
to start the server at your_local_ip:8001
.
The Player
class renders the Player, which is a user controlled orb interacting with the World
and Obstacle
classes.
draw()
draws the player orb on the screen as a sphere at the currentplayer.position
with sizeplayer.size
.move()
is an umbrella function that callsplayer.gravity()
,player.draw()
,player.collide()
and the globalcalibrateViewer()
. It calculates the individualplayer.xSpeed
andplayer.zSpeed
from the totalplayer.speed
and adjusts theplayer.position
accordingly. It also updates the UI's speed display.spin()
calculates the globalxzAngle
rotation based on user input. ThexzAngle
dictates both the distribution ofplayer.speed
over the X and Z axises, as well as the camera rotation. It updates the UI's angle display.gravity()
adjustsplayer.position.y
according to theWorld
'sgetCurrentY()
method.inertia()
increments theplayer.speed
while it isn't greater thanplayer.maxSpeed
according to the user input (ARROW_DOWN
increases the speed,ARROW_UP
decreases the speed until it reachesplayer.maxSpeed * -1
).spinertia()
(UNTESTED) increases theplayer.spinSpeed
while the left or right arrow key is pressed. This gives an inertia effect to the rotation movement.collide()
detects collisions with anyObstacle
class objects in theWorld
. If any collisions occur at aplayer.speed
greater than 60% of itsmaxSpeed
, it locks theplayer
's controls by activating theplayer.selfControlled
flag and triggers theplayer.bounceOff()
method.bounceOff()
freezes the rendering process and bounces theplayer
by moving itsposition.y
along a parabola calculated based on theplayer.speed
at inverted speed on the X and Z axises. It single-frames the rendering process at a 200ms interval, until the animation length reaches the animation duration time calculated based on aspeed / maxSpeed
fraction of 6000ms. Once the bounce animation is complete, it resumes the rendering process and hands back control to the user.
First, in the setup()
of sketch.js
, a new Player()
is created with the fov
variable as its only argument. It is then rendered after the World
and Obstacle
classes, since the Player
depends on values provided by these classes to function. The handleKeys()
function is first called to guide user input to the right places, after which player.move()
and player.rotate()
are called. After that, a check is ran that provides control locking for the ending animation by setting the player.selfControlled
flag to true if the remaining time is less than 5 seconds.
The Obstacle
class renders obstacles in the World
. When the Player
collides with one, it triggers a player.bounceOff()
event.
draw()
draws the obstacle. It also triggers theObstacle.gravity()
method and, iftype
is set tomoving
, calls theObstacle.move()
method too.move()
moves the obstacle within itsrange.x
andrange.z
bounds. It reverses the individual axis speeds when a boundary is hit.gravity()
adjustsObstacle.y
according to theWorld
'sgetCurrentY()
method.
For each two Terrain
classes generated by World.fillArea()
, an Obstacle
is created within the boundaries of the current area.
The Terrain
class renders the individual planes from which the World
is made up.
init()
initializes theTerrain
with itsindex
as the only argument. It calculates the Y position for the plane by taking an average of the Y position of its predecessor ob both the X and Z axis. For the X and Z positions, it takes the current position of theWorld
's renderer (World.fillArea()
).draw()
draws theTerrain
as a cube. If the game is in its final stage (5 seconds beforelength
), it moves the plane's Y position in order to achieve the "world falling apart" effect.
The Terrain
class is only called from World.fillArea()
and shouldn't be used outside this method's scope, as it's an essential part of the rendering process.
The World
class is an umbrella class which renders the Terrain
and Obstacle
classes, while also providing position information to the Player
(like getCurrentPlane()
and getCurrentY()
).
init()
fills theWorld
by callingWorld.fillArea()
with its initial boundaries as its arguments.draw()
first calls theWorld.applyFOV()
method to adjust theplanes
array where needed. After that, it draws every plane in theplanes
array by calling theTerrain
'sdraw()
method.fillArea()
first initializes the globalrayX
andrayZ
variables to the top left corner of its assigned rendering area. It calculates the amount of planes needed to fill the area, after which the rendering process is started. For each needed plane, it first pushes a newTerrain
onto theplanes
array and calls itsinit()
method. After that, it adjusts the globalrayZ
by one position in the desired direction (increase whenrayInvert
is false, otherwise decrease). When the renderer hits the secondzBound
, the rendering process is inverted. This process repeats until the globalrayX
equals the secondxBound
and the rendering process is complete. The rendered area is then filled withObstacle
classes (one for every two planes).applyFOV()
draws an imaginary square around thePlayer
within which theWorld
is rendered. The amount of planes that fit in this square is rounded up, so that the renderedWorld
always covers the entire Field Of View. Planes that are outside of these bounds are obsolete and get deleted to save resources. At the end of each run, all out-of-boundsObstacle
s are removed.getCurrentPlane()
filters theplanes
array for the one(s) in which a given[x, z]
position is contained.getCurrentY()
takes the first result ofWorld.getCurrentPlane()
to get the Y value for a given[x, z]
position.
In setup()
, the World
class is the first class to be created and initialized, since all other classes depend on parameters available to them through the World
class. Its draw()
method is then called in p5's draw()
function.
Boolean that indicates that the game has been started and hasn't ended yet. This message is sent once when the startBtn is clicked (a 1
value), and once more when the timer ends (a 0
value).
Collection that has three sub-addresses for the current x
, y
and z
position of the player
class. All three are integers that are updated at the refresh rate of the client.
Float that indicates the amount of degrees that the player
class is rotated horizontally. The starting position is 0
and it has a range of [-180, 180]
.
Float that indicates the current total speed of the player
class.
Floats that indicate the current speed of the player
class on both horizontal axises. These are a fraction of the total speed.
Event emitted when planes are removed or added to the world.
Boolean that indicates when a player.bounceOff()
event happens. It emits a 1
value while the player orb bounces, and results to 0
otherwise.
Boolean that indicates when nightMode
is triggered. This message is emitted once when the game scene turns to night. The value is always a 1
. It won't be emitted when nightMode
is inactive.
Integer that holds the current clock
value in seconds. This message is sent with every clock
update cycle.
Boolean that is triggered once when the End Of Time is reached (5 seconds before the game length). It holds a value of 1
.