This Is (NOT!) A Car Club - BIBLE

how the game's code works

Files: game3.php - loadfirst3.js - ascend3.js (readable) - functions3.js (readable.) - basic3.js (readable) - input3.js (readable) - main3.js (readable)

pop culture version

Most actual game content is in cut_set() (dialogue and story events), calculate_height() & calculate_height_after() (terrain shapes), generate_objects_array() & generate_objects_array_after() (trees, houses, etc) and create_3d_meshes() (textures for the terrain, trees, houses, etc). Also, a lot of sprites for characters, and sound effect mp3's, are listed outside of functions in basic.js (for now). Races and roads are created in ascend.js with race_create().

cut and frame_counter are two numbers that control what happens in the game at any given moment: Where characters are going, what they're saying, which music plays, and so on.

the cut concept:
I have one number variable (cut) that says where in the game you are. I have one number for "paused", "intro menu" and then one for every event in the game. Then one function that runs in the main loop (cut_set()) does whatever needs to be done depending on that number. If the number is divideable by 100 you're in "freeroam", if it's divideable by 50 you're in a race, and all other numbers are dialogue. Freeroam and race has same physics but race has some additional rules for the actual race, dialogue has no physics. When in dialogue, a mouseclick adds 1 to the story number, which means next dialogue.

the frame_counter concept::
frame_counter starts at 0 and increases with 1 every frame. At 20 000 it wraps around. That's one whole day. This controls how bright the sun is and the rhythm of what people do and say in more detail. Divide the time number by 834 and you get the current hour. Convienient for visualizing when during the days things actually happen.

car AI:
I have an accelerate factor and a max speed. Then I set a goal position that the npc tries to go towards. Then now and then i randomize a part-goal so it doesn't go straight all the time. Then if this part-goal gets too far from the straight path towards the goal I steer it back to the straight path. (I've also realized that rather than trying to simulate real human errors and inconsistencies for the npcs, I instead make the npcs 100% consistent but just a bit slower)

cars:
I have an array of cars, with one place for each character, and an array that keeps track of which characters are in the current scene, so I can stop calculating AI & physics for the ones that aren't. I can use constant values to avoid having to remember the number of every character (for example "const CAR_ADELE = 0;"). And these variables control if the characters are in the current scene or not: "car_turned_on[CAR_ADELE] = 0|1".

camera:
The camera has three modes: splash screen (shown at game start & in the pause menu), free roam/race (for when you drive around in the 3D terrain) & cutscene (for when you're in the "rooms"). One single function (camera_set()) handles the camera position and rotation for these three modes.

save function:
Progress (cut value and position) automatically gets saved in a browser cookie and in the URL. If you want to continue the game in another browser, just copy and paste the URL into the new browser.

basic terrain generation:
First, I found an orientation game from the 80s called "Forest", that simulates infinite terrain by doing simple mathematical functions on a manually created "profile" consisting of 256 heightvalues (https://grelf.itch.io/forest, read TerrainGeneration.pdf). The creator of the game, grelf, was nice enough to let me use his code, and from his basic algorithm that creates a pseudo-random terrain that looks the same every time if you give it the same parameters (a random seed, and a value for the terrain's flatness and wideness), I created functions to modify the terrain, like raising and lowering it, creating lakes, islands and flatlands, and tilting whole areas to give more dramatic variation, etc. The terrain is a square grid of 1x1 m blocks, much like in Minecraft, with a heightvalue at each corner. These heightvalues are calculated mathematically on the fly to create the actual 3D terrain, so no 3D model files are needed. In this case, doing thousands of mathematical calculations to create terrain is MUCH faster than loading a 3D model file with the same terrain. The calculations are actually done on the player's computer (because that's how Javascript works), so we don't even have to think about the internet speed for this. Nothing is downloaded except the code that creates the terrain.

I only create the terrain that's close to the player, enough for the player to have something to drive around in and look at. The rest of the world/terrain has to be crested DURING the game, and it has to be created faster than the player drives, or the player will reach an empty world that hasn't been created yet. This is called "dynamic loading". For the game to run at 60 FPS, the screen has to update every 17 milliseconds. In between this I have to create the terrain for my world. If I use more than 17 milliseconds for this, the screen won't update because it will be busy with my world creation, and this will result in LAG. So, every 17 milliseconds I have to pause the terrain creation to let the screen update. Then I have to get back to the terrain creation, so I need to know exactly where I was in the process when I last "took a break". So the terrain creation must be divided into small steps that I can pause from and return to with full consistensy, and it needs to get enough stuff done that the terrain is ready when the player reach new places. Do too much = lag, do too little = terrain won't be created in time. In programming language, I had to make a while loop that I could hop in and out of, which is the opposite of what a while loop does. Tricky.

I create the terrain in "chunks". Each chunk is 50x50 m big, they get loaded and unloaded as the player moves to new areas, and you only see the closest 5x5 chunks at a given time. If I would show more chunks, it would take longer time, and we don't have time for that. The dynamic loading makes sure that the terrain chunks outside of this 5x5 area are created in time, so they are ready to be loaded (become visible) when you reach that area.

Two things here can make the game run slow: the mathematical generation of heightvalues for the terrain, and the 3D rendering to the screen.

Functions that run every frame (or at least regularly and repeating)

ascend_main()
- runs every frame and updates the 3D terrain etc

cars_control()
- controls both the player's and AI characters' car movement

cars_physics()
- handles both the player's and AI characters' physics, for example friction, collision, gravity, etc

cars_sound()
- plays sounds depending on what you or AI characters do with the cars

layout_set()
- updates all layout

camera_set()
- updates the camera

light_set()
- updates daylight and all other lights (mostly daylight)

fog_set()
- updates fog

chunk_set()
- everything related to the current chunk

gameplay_fps()
- first person shooter gameplay code

cut_set()
- everything related to cut value. one big part of it is a list of where all characters should be placed and move towards at certain events, and one big part is the full list of story dialogue. there's also a bit other stuff.

Dialogue & story functions

dialog_set_metadata(at_object)
- sets character name & image and plays character sound at dialogue

cc(at_object, fdialog, from_x, from_z)
- shows dialogue in cutscene

cc2(at_object, fdialog, fdialog2, from_x, from_z)
- copy of cc() with different dialog depending on player's last answer

ccS(fdialog, fdialog2)
- copy of cc() for player's answer

ccS3(fdialog, fdialog2, fdialog3)
- copy of cc() for player's answer (3 answers!)

bet_pers(at_object, fdialog)
- shows dialogue BETween cutscenes. dialogue changes by timer

rolling_dialog()
- rolling text in dialog

rolling_dialog2()
- copy of rolling_dialog() for player's answer

rolling_dialog3()
- copy of rolling_dialog() for player's answer (3d answer!)

room_set(wall_texture, floor_texture)
- creates a room with a specific wall and floor texture, for dialogue "cutscenes"

place_cz_in_room(fobject, direction, height, distance)
- places characters/objects close to the player, in a specific direction (n,s,e,w,ne,nw,se,sw). good for quickly making a dialogue scene that looks OK

place_cz_in_room_ground(fobject, direction, height, distance)
- place_cz_in_room() but characters/objects get placed right above ground automatically

World functions

x_to_chunk_no(x)
- convert x or z position to chunk number

x_to_x_in_chunk(x)
- convert x or z position to x or z position relative to chunk

x_in_chunk_to_x(fi1, fi)
- convert chunk-relative x or z position to "real" x or z position

distance_get_xz(fx1, fz1, fx2, fz2)
- get distance between two points

distance_get(fobject1, fobject2)
- get distance between two czs/objects

height_get_xz(x, z)
- get height at position, not exact height but heightgrid height

height_get_xz_exact(x, z)
- get exact height at position (interpolates between four points (height values) to find out the exact value - https://codeplea.com/triangular-interpolation)

height_get(fobject)
- get exact height at a cz's/object's position

object_get(object)
- get object value (another thing than cz/object. this is part of the terrain: trees, houses, etc) at cz's/object's position

place_sprite(fobject, fx, fz)
- Makes sure characters and/or objects get the correct height (just above ground)

place_sprite_noidle(fobject, fx, fz)
- same as place_sprite() but with no idle stuff

talk_char(fsprite, fpos_x, fpos_z, fcut, fdistance, fradius)
- abstraction for talk characters in chunk_set()

talk_char_still(fsprite, fpos_x, fpos_z, fcut, fdistance)
- talk_char() but for characters that stand still

talk_char_when(fsprite, fpos_x, fpos_z, fcut, fcut_after, fcut_last, fdistance, fradius)
- variant of talk_char

talk_char_when_still(fsprite, fpos_x, fpos_z, fcut, fcut_after, fcut_last)
- variant of talk_char() for going to different cuts depending on which cut you are at (=where in the game's story)

Terrain generation functions

distance_get_oval_xz(fx1, fz1, fx2, fz2, factorx, factorz)
- distance functions turn out to be useful for creating circles, or in this case ovals, in the terrain.

grelf()
- From Graham Relf's game The Forest. All terrain creation in the game is based on this

grelf3()
- variation on grelf()

grelf_detail()
- a more "high-res" version of grelf()

grelf_detail2()
- variation on grelf_detail()

grelf_objects()
- just a bit different grelf(), i think

grelf_objects_detail()
- a more "high-res" version of grelf_objects(), i think

grelf_houses()
- just a bit different grelf(), i think

grelf_weird()
- a high-res version of grelf()

set_wideness_highness(fi1, fj1)
- here i set values that decide how the weights in set_weights() will turn out (= how the terrain will look)

set_weights(fi1, fj1, fi, fj)
- here weights get set that decide how the hm and object generation will turn out. everything here should come originally from the seed variable

calculate_chunklevel(fi1, fj1, fi, fj)
- tilts a whole chunk. complicated to explain but it's cool and effective

terrain_amplify(fi1, fj1, fi, fj, depth)
- creating a lake/hole in ONE chunk. or mountain

terrain_amplify_4chunks(fi1_start, fj1_start, fi1, fj1, fi, fj, depth, fpower)
- creating a lake/hole in 4 chunks. or mountain

terrain_make_plains(fi1, fj1, fi, fj, base)
- make plains in terrain

calculate_height(fi1, fj1, fi, fj)
- generate heightvalues to the hm[][][][] array

calculate_height_after(fi1, fj1)
- heightvalues generated by the in-game "minecraft" editor. this runs only one time per chunk, otherwise it's very slow. these should get added after everything else (if everything is set up right).

generate_objects_array(fi1, fj1, fi, fj)
- generate object values to the om[][][][] array

generate_objects_array_after(fi1, fj1)
- objects generated by the in-game "minecraft" editor. this runs only one time per chunk, otherwise it's very slow. these should get added after everything else (if everything is set up right).

Terrain handling/creating functions

create_terrain_3d_vertices(fi1, fj1, fi, fj)
- create terrain vertices from hm

create_objects_3d_ver_houses(fi1, fj1, fi, fj, ver_array, hght, roofheight)
- create house vertices

create_objects_3d_vertices(fi1, fj1, fi, fj, number)
- create vertices for object mesh/sprites/etc

create_3d_meshes(fi1, fj1)
- dynamic function for creating meshes (and pointsprites, etc) from vertices

show_and_hide_terrain_chunks()
- show and hide terrain chunks

hide_bushes_grass_etc_outside_current_chunk()
- hide some things that should only be seen in the current chunk (to save draw calls so the game is faster)

ascend_intro(master_seed)
- creates the terrain. runs at the start. also, a lot of code that just lies outside of functions in ascend.js also runs at start, before this.

create_too_large_arrays(fi1, fj1)
- function for creating a few time-consuming laaarge arrays. this is made into a function so we can create only the arrays we need, when we need them. creating all of them at the same time will take too long time.

dynload_hm()
- dynamic heightmap generation

dynload_objects()
- dynamic objects generation

create_terrain_chunks_before_showing_them()
- add and remove chunks when moving into a new place

Music/sound functions

pause_all_cz_sounds()
- pauses all character sounds (obvious from name)

sound_play(fsound)
- play a sound from the START!

music_play(file, fvolume, floop, fspeed)
- plays mp3 file with Web Audio API

Race/road functions (most race functionality actually runs in cut_set()!)

add_road_block(fx, fz)
- abstraction for race_create()

race_create(x_array, z_array, number)
- creates a road, from an array of coordinates, that can be used for race. it also adds a bit of randomization in between the coordinates. unlike most other world generation that is automatic from simple seed values etc, this is semi-manual.

Misc. functions

hexcol(fobject, hex)
- function without the three.js stuff

pseudorandom(number)
- get pseudorandom value. always returns the same value if number is the same

get_time_by_minutes_resolution(minutes_resolution)
- get current in-game time. you set minutes_resolution to 15 you get the current quarter, 60 you get the current hour, and so on. the game's time comes from frame_counter which increases with 1 every frame, so it's good to be able to get less exact time

lookat_datass(fobject1, fobject2)
- my own version of lookAt() that only works in x and z dimensions (=you can't look upwards). for characters/objects. because rotations and quaternions suck

lookat_datass_xz(fx1, fz1, fx2, fz2)
- my own version of lookAt() that only works in x and z dimensions (=you can't look upwards). for xz positions. because rotations and quaternions suck

rotation_real_get(angle)
- get the actual rotation in radians. my own function because the standard functions don't work (because rotations and quaternions suck).

ts_start()
- timescene abstraction

ts_end(fcut)
- timescene abstraction

ts_end_from_talk(fcut)
- timescene abstraction

ts_during()
- timescene abstraction

fog_area(fred,fgreen,fblue, from_i1, to_i1, from_j1, to_j1)
- abstraction for fog_set()

hide_cut_sprites()
- hides all characters that then can be shown in cut_set()

tex(file, r1, r2, mirrored)
- create texture material

snd(file, fvolume, floop)
- create sound

spr(file, size, width)
- create sprite

psp(file, fsize)
- create pointsprites material


(background image by sachikomiliart)