FIT CTU
Adam Vesecký
vesecky.adam@gmail.com
Lecture 2
Engines
Unreal Engine 1
Unreal Engine 4
Unreal Engine 5
Unity 2020.1.6 - current version
Ori
Besiege
Cuphead
Still, they all have similar core components
Hardware
Drivers
OS
3rd Party SDKs
Core Systems
Modules
Game
Initialization process
Game Loop process
Fixed time step
Variable time step
Adaptive time step
1 | int Application::Run() { |
2 | Setup(); |
3 | if (!engine_->Initialize(engineParameters_)) { |
4 | return ErrorExit(); |
5 | } |
6 | |
7 | Start(); |
8 | |
9 | #if !defined(IOS) && !defined(__EMSCRIPTEN__) |
10 | while (!engine_->IsExiting()) |
11 | engine_->RunFrame(); |
12 | Stop(); |
13 | #else |
14 | #if defined(IOS) |
15 | SDL_iPhoneSetAnimationCallback(GetWindow(), 1, &RunFrame, engine_); |
16 | #elif defined(__EMSCRIPTEN__) |
17 | emscripten_set_main_loop_arg(RunFrame, engine_, 0, 1); |
18 | #endif |
19 | #endif |
20 | return exitCode_; |
21 | } |
1 | void Engine::RunFrame() { |
2 | Time* time = GetSubsystem<Time>(); |
3 | Input* input = GetSubsystem<Input>(); |
4 | Audio* audio = GetSubsystem<Audio>(); |
5 | |
6 | time->BeginFrame(timeStep_); |
7 | // ... process input and audio |
8 | Update(); |
9 | |
10 | fpsTimeSinceUpdate_ += timeStep_; |
11 | ++fpsFramesSinceUpdate_; |
12 | if (fpsTimeSinceUpdate_ > ENGINE_FPS_UPDATE_INTERVAL) { |
13 | fps_ = (int)(fpsFramesSinceUpdate_ / fpsTimeSinceUpdate_); |
14 | fpsFramesSinceUpdate_ = 0; |
15 | fpsTimeSinceUpdate_ = 0; |
16 | } |
17 | |
18 | Render(); |
19 | ApplyFrameLimit(); |
20 | time->EndFrame(); |
21 | } |
1 | void Engine::Update() { |
2 | VariantMap& eventData = GetEventDataMap(); |
3 | eventData[P_TIMESTEP] = timeStep_; |
4 | SendEvent(E_UPDATE, eventData); |
5 | |
6 | // Logic post-update event |
7 | SendEvent(E_POSTUPDATE, eventData); |
8 | // Rendering update event |
9 | SendEvent(E_RENDERUPDATE, eventData); |
10 | // Post-render update event |
11 | SendEvent(E_POSTRENDERUPDATE, eventData); |
12 | } |
13 | |
14 | void Engine::Render() { |
15 | // If device is lost, BeginFrame will fail and we skip rendering |
16 | Graphics* graphics = GetSubsystem<Graphics>(); |
17 | if (!graphics->BeginFrame()) return; |
18 | |
19 | GetSubsystem<Renderer>()->Render(); |
20 | GetSubsystem<UI>()->Render(); |
21 | graphics->EndFrame(); |
22 | } |
1 | bool Main::iteration() { |
2 | iterating++; |
3 | |
4 | uint64_t ticks = OS::get_singleton()->get_ticks_usec(); |
5 | Engine::get_singleton()->_frame_ticks = ticks; |
6 | // update elapsed ticks |
7 | // ... |
8 | MainFrameTime advance = main_timer_sync.advance(frame_slice, physics_fps); |
9 | |
10 | for (int iters = 0; iters < advance.physics_steps; ++iters) { |
11 | // ... update PhysicsServer3D |
12 | } |
13 | |
14 | if (DisplayServer::get_singleton()->can_any_window_draw()) { |
15 | // ... draw frame |
16 | } |
17 | |
18 | for (int i = 0; i < ScriptServer::get_language_count(); i++) { |
19 | // ... update scene for each scripting language |
20 | ScriptServer::get_language(i)->frame(); |
21 | } |
22 | |
23 | AudioServer::get_singleton()->update(); |
24 | frames++; |
25 | return exit || auto_quit; |
26 | } |
Object A reads previous state of Object B and Object B reads previous state of Object C
Scene Graph
Scene Manager
Detects input events from devices
Atomic events
Compound events
Special events
Receiving the state of the device
Devices
Dead zone
Normalization
Keyboards for various games
Normalized input
Sequence detection
Controller input remapping
Context-sensitive inputs
1 | // Returns 1 if the cheat was successful, 0 if failed. |
2 | int cht_CheckCheat(cheatseq_t* cht, char key ) { |
3 | int i; |
4 | int rc = 0; |
5 | |
6 | if (firsttime) { |
7 | firsttime = 0; |
8 | for (i=0;i<256;i++) cheat_xlate_table[i] = SCRAMBLE(i); |
9 | } |
10 | |
11 | // initialize if first time |
12 | if (!cht->p) cht->p = cht->sequence; |
13 | if (*cht->p == 0) *(cht->p++) = key; |
14 | else if (cheat_xlate_table[(unsigned char)key] == *cht->p) cht->p++; |
15 | else cht->p = cht->sequence; |
16 | |
17 | if (*cht->p == 1) cht->p++; |
18 | else if (*cht->p == 0xff) { // end of sequence character |
19 | cht->p = cht->sequence; |
20 | rc = 1; |
21 | } |
22 | return rc; |
23 | } |
Custom allocators
Bucket allocator
Heap allocator
1 | void* AllocatorReserve(AllocatorBlock* allocator) { |
2 | if (!allocator->free_) { |
3 | // Free nodes have been exhausted. Allocate a new larger block |
4 | unsigned newCapacity = (allocator->capacity_ + 1) >> 1; |
5 | AllocatorReserveBlock(allocator, allocator->nodeSize_, newCapacity); |
6 | allocator->capacity_ += newCapacity; |
7 | } |
8 | |
9 | // We should have new free node(s) chained |
10 | AllocatorNode* freeNode = allocator->free_; |
11 | void* ptr = (reinterpret_cast<unsigned char*>(freeNode)) + sizeof(AllocatorNode); |
12 | allocator->free_ = freeNode->next_; |
13 | freeNode->next_ = 0; |
14 | return ptr; |
15 | } |
16 | ============================ |
17 | // create node from void* and call the constructor |
18 | Node* newNode = static_cast |
19 | new(newNode) Node(); |
20 | // ... do some stuff |
21 | // delete node |
22 | (newNode)->~Node(); |
23 | AllocatorFree(allocator_, newNode); |
1 | PoolAllocator::ID PoolAllocator::alloc(int p_size) { |
2 | int size_to_alloc = aligned(p_size); |
3 | |
4 | EntryIndicesPos new_entry_indices_pos; |
5 | |
6 | if (!find_hole(&new_entry_indices_pos, size_to_alloc)) { |
7 | /* No hole could be found, try compacting mem */ |
8 | compact(); |
9 | /* Then search again */ |
10 | if (!find_hole(&new_entry_indices_pos, size_to_alloc)) { |
11 | ERR_FAIL_V_MSG(POOL_ALLOCATOR_INVALID_ID, "Memory can't be compacted further."); |
12 | } |
13 | } |
14 | EntryArrayPos new_entry_array_pos; |
15 | bool found_free_entry = get_free_entry(&new_entry_array_pos); |
16 | |
17 | if (!found_free_entry) { |
18 | ERR_FAIL_V_MSG(POOL_ALLOCATOR_INVALID_ID, "No free entry found in PoolAllocator."); |
19 | } |
20 | // ... move all entry indices up and allocate the entry |
21 | // ... |
22 | return retval; |
23 | } |
Level loading
Air locks
World streams
Raptor (1994)
Shadow of the Tomb Raider (2018)
Duke Nukem (1991)
Portal (2007)
World of Warcraft (2004)
Witcher 3 (2015)
Scripting language
Common characteristics
Scripts in games
1 | // Creates boards and markers around mission Area |
2 | _xPos = position (_this select 0) select 0; |
3 | _yPos = position (_this select 0) select 1; |
4 | |
5 | _howBigA = _this select 1; |
6 | _howBigB = _this select 2; |
7 | _tablesC = _this select 3; |
8 | _angle = _this select 4; |
9 | _i = 0; |
10 | |
11 | while (_i < 360) do { |
12 | _x = (_howBighA * (sin _i)); |
13 | _y = (_howBigB * (cos _i)); |
14 | _x_rot = _xPos + _x*(cos _angle) - _y*(sin _angle); |
15 | _y_rot = _yPos + _x*(sin _angle) + _y*(cos _angle); |
16 | _k = createVehicle ["Danger", [_x_rot, _y_rot, 0], [], 0, "NONE"]; |
17 | _m = createMarker [format ["Marker" + str _i], [_x_rot, _y_rot, 0]]; |
18 | format ["Marker" + str _i] setMarkerType "Dot"; |
19 | _k setDir _i; |
20 | format ["Marker" + str _i] setMarkerDir(_i - _angle); |
21 | _i = _i + 360/_tablesC; |
22 | }; |
1 | script 137 (int dir) |
2 | { |
3 | if(!dir) |
4 | { |
5 | Floor_LowerByValue(DoorTag, 16, 64) |
6 | Ceiling_RaiseByValue(DoorTag, 16, 64) |
7 | Delay(120); |
8 | Floor_RaiseByValue(DoorTag, 16, 64) |
9 | Ceiling_LowerByValue(DoorTag, 16, 64) |
10 | } |
11 | } |
1 | local total, completed = GetNumCompletedAchievements(); |
2 | |
3 | if total > completed then |
4 | print("You have completed ", completed, " out of " ,total," achievements"); |
5 | x= completed/total*100; |
6 | print("That is only ",x," percent"); |
7 | end |
Scripted callbacks
Scripted components
Script-driven game
Script-driven engine
1 | // Duktape JS mapping |
2 | static void jsb_class_define_FileSystem(JSVM* vm) { |
3 | duk_context* ctx = vm->GetJSContext(); |
4 | js_class_get_constructor(ctx, "Atomic", "FileSystem"); |
5 | js_class_get_prototype(ctx, "Atomic", "FileSystem"); |
6 | duk_pop_2(ctx); |
7 | js_class_get_prototype(ctx, "Atomic", "FileSystem"); |
8 | duk_push_c_function(ctx, jsb_class_FileSystem_SetCurrentDir, 1); |
9 | duk_put_prop_string(ctx, -2, "setCurrentDir"); |
10 | duk_push_c_function(ctx, jsb_class_FileSystem_CreateDir, 1); |
11 | duk_put_prop_string(ctx, -2, "createDir"); |
12 | ... |
13 | } |
14 | |
15 | // CreateDir method |
16 | static int jsb_class_FileSystem_CreateDir(duk_context* ctx) { |
17 | String __arg0 = duk_to_string(ctx, 0); |
18 | duk_push_this(ctx); |
19 | FileSystem* native = js_to_class_instance<FileSystem>(ctx, -1, 0); |
20 | bool retValue = native->CreateDir(__arg0); |
21 | duk_push_boolean(ctx, retValue ? 1 : 0); |
22 | return 1; |
23 | } |
1 | ATOMIC_EXPORT_API bool csb_Atomic_FileSystem_SetCurrentDir_4667(FileSystem* self, const char* pathName) |
2 | { |
3 | return self->SetCurrentDir(pathName ? String(pathName) : String::EMPTY); |
4 | } |
5 | |
6 | |
7 | ATOMIC_EXPORT_API bool csb_Atomic_FileSystem_CreateDir_4668(FileSystem* self, const char* pathName) |
8 | { |
9 | return self->CreateDir(pathName ? String(pathName) : String::EMPTY); |
10 | } |
11 | |
12 | |
13 | ATOMIC_EXPORT_API void csb_Atomic_FileSystem_SetExecuteConsoleCommands_4669(FileSystem* self, bool enable) |
14 | { |
15 | self->SetExecuteConsoleCommands(enable); |
16 | } |
1 | // ofvec2f 2D vectors mapping via luabridge library |
2 | luabridge::getGlobalNamespace(L) |
3 | .beginClass<ofVec2f>("ofVec2f") |
4 | .addConstructor<void(*)(float, float)>() |
5 | .addFunction(LUA_OPERATOR_PLUS, |
6 | static_cast<ofVec2f(ofVec2f::*)(const ofVec2f &)const>(&ofVec2f::operator+)) |
7 | .addFunction(LUA_OPERATOR_MULT, |
8 | static_cast<ofVec2f(ofVec2f::*)(const ofVec2f &)const>(&ofVec2f::operator*)) |
9 | .addFunction(LUA_OPERATOR_EQ, |
10 | static_cast<bool(ofVec2f::*)(const ofVec2f &)const>(&ofVec2f::operator==)) |
11 | .addData("x", &ofVec2f::x) |
12 | .addData("y", &ofVec2f::y) |
13 | .addFunction("length", &ofVec2f::length) |
14 | .addFunction("dot", &ofVec2f::dot) |
15 | .endClass(); |
In dangerous testing environments, the Enrichment Center promises to always provide useful advice. For instance, the floor here will kill you. Try to avoid it.GLaDOS, Portal