Advanced Resources
ΠΡΡΠ΅ΠΊΡΠΈΠ²Π½ΠΎΠ΅ ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΡΠ΅ΡΡΡΡΠ°ΠΌΠΈ ΠΊΡΠΈΡΠΈΡΠ΅ΡΠΊΠΈ Π²Π°ΠΆΠ½ΠΎ Π΄Π»Ρ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΠΈ ΠΈΠ³ΡΡ. Π ΡΡΠΎΠΌ ΡΠ°Π·Π΄Π΅Π»Π΅ ΡΠ°ΡΡΠΌΠ°ΡΡΠΈΠ²Π°ΡΡΡΡ ΠΏΡΠΎΠ΄Π²ΠΈΠ½ΡΡΡΠ΅ ΡΠ΅Ρ Π½ΠΈΠΊΠΈ ΡΠ°Π±ΠΎΡΡ Ρ ΡΠ΅ΡΡΡΡΠ°ΠΌΠΈ Π² Wudgine.
ΠΠ²Π΅Π΄Π΅Π½ΠΈΠ΅
Π£ΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΡΠ΅ΡΡΡΡΠ°ΠΌΠΈ β ΠΎΠ΄ΠΈΠ½ ΠΈΠ· ΠΊΠ»ΡΡΠ΅Π²ΡΡ Π°ΡΠΏΠ΅ΠΊΡΠΎΠ² ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠΈ ΠΈΠ³Ρ, ΠΎΡΠΎΠ±Π΅Π½Π½ΠΎ Π΄Π»Ρ ΠΏΡΠΎΠ΅ΠΊΡΠΎΠ² Ρ Π±ΠΎΠ»ΡΡΠΈΠΌ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎΠΌ ΠΊΠΎΠ½ΡΠ΅Π½ΡΠ°. Wudgine ΠΏΡΠ΅Π΄ΠΎΡΡΠ°Π²Π»ΡΠ΅Ρ ΡΠ°ΡΡΠΈΡΠ΅Π½Π½ΡΠ΅ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΠΈ Π΄Π»Ρ ΠΎΠΏΡΠΈΠΌΠΈΠ·Π°ΡΠΈΠΈ Π·Π°Π³ΡΡΠ·ΠΊΠΈ, Ρ ΡΠ°Π½Π΅Π½ΠΈΡ ΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ ΠΈΠ³ΡΠΎΠ²ΡΡ ΡΠ΅ΡΡΡΡΠΎΠ².
ΠΡΠΈΠ½Ρ ΡΠΎΠ½Π½Π°Ρ Π·Π°Π³ΡΡΠ·ΠΊΠ° ΡΠ΅ΡΡΡΡΠΎΠ²
ΠΡΠΈΠ½Ρ ΡΠΎΠ½Π½Π°Ρ Π·Π°Π³ΡΡΠ·ΠΊΠ° ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ Π·Π°Π³ΡΡΠΆΠ°ΡΡ ΡΠ΅ΡΡΡΡΡ Π² ΡΠΎΠ½ΠΎΠ²ΠΎΠΌ ΡΠ΅ΠΆΠΈΠΌΠ΅, Π½Π΅ Π±Π»ΠΎΠΊΠΈΡΡΡ ΠΎΡΠ½ΠΎΠ²Π½ΠΎΠΉ ΠΏΠΎΡΠΎΠΊ ΠΈΠ³ΡΡ.
Π‘ΠΈΡΡΠ΅ΠΌΠ° Π°ΡΠΈΠ½Ρ ΡΠΎΠ½Π½ΠΎΠΉ Π·Π°Π³ΡΡΠ·ΠΊΠΈ
// ΠΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ Π°ΡΠΈΠ½Ρ
ΡΠΎΠ½Π½ΠΎΠΉ Π·Π°Π³ΡΡΠ·ΠΊΠΈ ΡΠ΅ΠΊΡΡΡΡ
Wudgine::Resources::TextureHandle textureHandle =
resourceManager.LoadTextureAsync("textures/large_terrain.png",
[](Wudgine::Resources::TextureHandle handle) {
// ΠΡΠΎΡ ΠΊΠΎΠ΄ Π²ΡΠΏΠΎΠ»Π½ΠΈΡΡΡ ΠΏΠΎΡΠ»Π΅ Π·Π°Π³ΡΡΠ·ΠΊΠΈ ΡΠ΅ΠΊΡΡΡΡΡ
std::cout << "Π’Π΅ΠΊΡΡΡΡΠ° Π·Π°Π³ΡΡΠΆΠ΅Π½Π°!" << std::endl;
});
// ΠΡΠΎΠ²Π΅ΡΠΊΠ° ΡΡΠ°ΡΡΡΠ° Π·Π°Π³ΡΡΠ·ΠΊΠΈ
if (resourceManager.IsLoading(textureHandle)) {
// Π Π΅ΡΡΡΡ Π²ΡΠ΅ Π΅ΡΠ΅ Π·Π°Π³ΡΡΠΆΠ°Π΅ΡΡΡ
float progress = resourceManager.GetLoadingProgress(textureHandle);
std::cout << "ΠΡΠΎΠ³ΡΠ΅ΡΡ Π·Π°Π³ΡΡΠ·ΠΊΠΈ: " << progress * 100.0f << "%" << std::endl;
} else {
// Π Π΅ΡΡΡΡ Π·Π°Π³ΡΡΠΆΠ΅Π½ ΠΈ Π³ΠΎΡΠΎΠ² ΠΊ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ
auto texture = resourceManager.Get<Wudgine::Resources::Texture>(textureHandle);
}
ΠΡΡΠΏΠΏΡ Π·Π°Π³ΡΡΠ·ΠΊΠΈ
// Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π³ΡΡΠΏΠΏΡ Π·Π°Π³ΡΡΠ·ΠΊΠΈ
Wudgine::Resources::LoadGroup levelResources;
// ΠΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΡΠ΅ΡΡΡΡΠΎΠ² Π² Π³ΡΡΠΏΠΏΡ
levelResources.AddTexture("textures/terrain.png");
levelResources.AddTexture("textures/buildings.png");
levelResources.AddModel("models/player.obj");
levelResources.AddSound("sounds/ambient.wav");
// ΠΠ°ΠΏΡΡΠΊ Π°ΡΠΈΠ½Ρ
ΡΠΎΠ½Π½ΠΎΠΉ Π·Π°Π³ΡΡΠ·ΠΊΠΈ Π³ΡΡΠΏΠΏΡ
resourceManager.LoadGroupAsync(levelResources, [](Wudgine::Resources::LoadGroup& group) {
// ΠΡΠΎΡ ΠΊΠΎΠ΄ Π²ΡΠΏΠΎΠ»Π½ΠΈΡΡΡ ΠΏΠΎΡΠ»Π΅ Π·Π°Π³ΡΡΠ·ΠΊΠΈ Π²ΡΠ΅Ρ
ΡΠ΅ΡΡΡΡΠΎΠ² Π³ΡΡΠΏΠΏΡ
std::cout << "ΠΡΠ΅ ΡΠ΅ΡΡΡΡΡ ΡΡΠΎΠ²Π½Ρ Π·Π°Π³ΡΡΠΆΠ΅Π½Ρ!" << std::endl;
});
// ΠΡΡΠ»Π΅ΠΆΠΈΠ²Π°Π½ΠΈΠ΅ ΠΏΡΠΎΠ³ΡΠ΅ΡΡΠ° Π·Π°Π³ΡΡΠ·ΠΊΠΈ Π³ΡΡΠΏΠΏΡ
float groupProgress = resourceManager.GetGroupLoadingProgress(levelResources);
ΠΠΎΡΠΎΠΊΠΎΠ²Π°Ρ Π·Π°Π³ΡΡΠ·ΠΊΠ°
ΠΠΎΡΠΎΠΊΠΎΠ²Π°Ρ Π·Π°Π³ΡΡΠ·ΠΊΠ° ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ Π·Π°Π³ΡΡΠΆΠ°ΡΡ ΠΈ Π²ΡΠ³ΡΡΠΆΠ°ΡΡ ΡΠ΅ΡΡΡΡΡ Π² ΡΠ΅Π°Π»ΡΠ½ΠΎΠΌ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ Π² Π·Π°Π²ΠΈΡΠΈΠΌΠΎΡΡΠΈ ΠΎΡ ΠΏΠΎΡΡΠ΅Π±Π½ΠΎΡΡΠ΅ΠΉ ΠΈΠ³ΡΡ.
Π‘ΠΈΡΡΠ΅ΠΌΠ° ΠΏΠΎΡΠΎΠΊΠΎΠ²ΠΎΠΉ Π·Π°Π³ΡΡΠ·ΠΊΠΈ
// ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° ΡΠΈΡΡΠ΅ΠΌΡ ΠΏΠΎΡΠΎΠΊΠΎΠ²ΠΎΠΉ Π·Π°Π³ΡΡΠ·ΠΊΠΈ
Wudgine::Resources::StreamingSystem streamingSystem;
streamingSystem.SetMaxMemoryUsage(1024 * 1024 * 1024); // 1 ΠΠ
streamingSystem.SetLoadingBudgetPerFrame(5 * 1024 * 1024); // 5 ΠΠ Π·Π° ΠΊΠ°Π΄Ρ
// Π Π΅Π³ΠΈΡΡΡΠ°ΡΠΈΡ ΠΎΠ±ΡΠ°Π±ΠΎΡΡΠΈΠΊΠ° Π΄Π»Ρ ΠΎΠΏΡΠ΅Π΄Π΅Π»Π΅Π½ΠΈΡ ΠΏΡΠΈΠΎΡΠΈΡΠ΅ΡΠ° ΡΠ΅ΡΡΡΡΠΎΠ²
streamingSystem.SetPriorityHandler([](const Wudgine::Resources::ResourceInfo& resource) {
// ΠΡΡΠΈΡΠ»Π΅Π½ΠΈΠ΅ ΠΏΡΠΈΠΎΡΠΈΡΠ΅ΡΠ° Π½Π° ΠΎΡΠ½ΠΎΠ²Π΅ ΡΠ°ΡΡΡΠΎΡΠ½ΠΈΡ Π΄ΠΎ ΠΊΠ°ΠΌΠ΅ΡΡ
float distance = CalculateDistanceToCamera(resource.position);
return 1.0f / (distance + 1.0f); // Π§Π΅ΠΌ Π±Π»ΠΈΠΆΠ΅, ΡΠ΅ΠΌ Π²ΡΡΠ΅ ΠΏΡΠΈΠΎΡΠΈΡΠ΅Ρ
});
// ΠΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΡΠΈΡΡΠ΅ΠΌΡ ΠΏΠΎΡΠΎΠΊΠΎΠ²ΠΎΠΉ Π·Π°Π³ΡΡΠ·ΠΊΠΈ ΠΊΠ°ΠΆΠ΄ΡΠΉ ΠΊΠ°Π΄Ρ
void Update(float deltaTime) {
// ΠΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΠΏΠΎΠ·ΠΈΡΠΈΠΈ ΠΊΠ°ΠΌΠ΅ΡΡ
streamingSystem.SetCameraPosition(camera.GetPosition());
// ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° Π·Π°Π³ΡΡΠ·ΠΊΠΈ/Π²ΡΠ³ΡΡΠ·ΠΊΠΈ ΡΠ΅ΡΡΡΡΠΎΠ²
streamingSystem.Update(deltaTime);
}
Π£ΡΠΎΠ²Π½ΠΈ Π΄Π΅ΡΠ°Π»ΠΈΠ·Π°ΡΠΈΠΈ (LOD)
// ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° LOD Π΄Π»Ρ ΠΌΠΎΠ΄Π΅Π»ΠΈ
Wudgine::Resources::ModelHandle modelHandle = resourceManager.LoadModel("models/character.obj");
auto model = resourceManager.Get<Wudgine::Resources::Model>(modelHandle);
// ΠΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΡΡΠΎΠ²Π½Π΅ΠΉ Π΄Π΅ΡΠ°Π»ΠΈΠ·Π°ΡΠΈΠΈ
model->AddLOD(1, resourceManager.LoadModel("models/character_lod1.obj"));
model->AddLOD(2, resourceManager.LoadModel("models/character_lod2.obj"));
model->AddLOD(3, resourceManager.LoadModel("models/character_lod3.obj"));
// ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° ΡΠ°ΡΡΡΠΎΡΠ½ΠΈΠΉ Π΄Π»Ρ ΠΏΠ΅ΡΠ΅ΠΊΠ»ΡΡΠ΅Π½ΠΈΡ LOD
model->SetLODDistance(1, 10.0f); // ΠΠ΅ΡΠ΅ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅ Π½Π° LOD1 Π½Π° ΡΠ°ΡΡΡΠΎΡΠ½ΠΈΠΈ 10 Π΅Π΄ΠΈΠ½ΠΈΡ
model->SetLODDistance(2, 30.0f); // ΠΠ΅ΡΠ΅ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅ Π½Π° LOD2 Π½Π° ΡΠ°ΡΡΡΠΎΡΠ½ΠΈΠΈ 30 Π΅Π΄ΠΈΠ½ΠΈΡ
model->SetLODDistance(3, 100.0f); // ΠΠ΅ΡΠ΅ΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅ Π½Π° LOD3 Π½Π° ΡΠ°ΡΡΡΠΎΡΠ½ΠΈΠΈ 100 Π΅Π΄ΠΈΠ½ΠΈΡ
ΠΠΈΡΡΡΠ°Π»ΡΠ½Π°Ρ ΡΠ΅ΠΊΡΡΡΡΠ½Π°Ρ ΠΏΠ°ΠΌΡΡΡ
ΠΠΈΡΡΡΠ°Π»ΡΠ½Π°Ρ ΡΠ΅ΠΊΡΡΡΡΠ½Π°Ρ ΠΏΠ°ΠΌΡΡΡ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ ΡΡΡΠ΅ΠΊΡΠΈΠ²Π½ΠΎ ΡΠ°Π±ΠΎΡΠ°ΡΡ Ρ ΡΠ΅ΠΊΡΡΡΡΠ°ΠΌΠΈ, ΡΠ°Π·ΠΌΠ΅Ρ ΠΊΠΎΡΠΎΡΡΡ ΠΏΡΠ΅Π²ΡΡΠ°Π΅Ρ Π΄ΠΎΡΡΡΠΏΠ½ΡΡ Π²ΠΈΠ΄Π΅ΠΎΠΏΠ°ΠΌΡΡΡ.
ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° Π²ΠΈΡΡΡΠ°Π»ΡΠ½ΠΎΠΉ ΡΠ΅ΠΊΡΡΡΡΠ½ΠΎΠΉ ΠΏΠ°ΠΌΡΡΠΈ
// ΠΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΡ ΡΠΈΡΡΠ΅ΠΌΡ Π²ΠΈΡΡΡΠ°Π»ΡΠ½ΡΡ
ΡΠ΅ΠΊΡΡΡΡ
Wudgine::Resources::VirtualTextureSystem vtSystem;
vtSystem.Initialize(4096, 4096); // Π Π°Π·ΠΌΠ΅Ρ Π²ΠΈΡΡΡΠ°Π»ΡΠ½ΠΎΠΉ ΡΠ΅ΠΊΡΡΡΡΡ 4096x4096
vtSystem.SetPageSize(128); // Π Π°Π·ΠΌΠ΅Ρ ΡΡΡΠ°Π½ΠΈΡΡ 128x128 ΠΏΠΈΠΊΡΠ΅Π»Π΅ΠΉ
vtSystem.SetCacheSize(512 * 1024 * 1024); // ΠΡΡ 512 ΠΠ
// ΠΠ°Π³ΡΡΠ·ΠΊΠ° ΠΌΠ΅Π³Π°ΡΠ΅ΠΊΡΡΡΡΡ
Wudgine::Resources::VirtualTextureHandle vtHandle =
vtSystem.LoadVirtualTexture("textures/terrain_mega.vtex");
// ΠΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ Π²ΠΈΡΡΡΠ°Π»ΡΠ½ΠΎΠΉ ΡΠ΅ΠΊΡΡΡΡΡ Π² ΡΠ΅ΠΉΠ΄Π΅ΡΠ΅
shader->SetVirtualTexture("terrainTexture", vtHandle);
// ΠΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΡΠΈΡΡΠ΅ΠΌΡ Π²ΠΈΡΡΡΠ°Π»ΡΠ½ΡΡ
ΡΠ΅ΠΊΡΡΡΡ ΠΊΠ°ΠΆΠ΄ΡΠΉ ΠΊΠ°Π΄Ρ
void Update(float deltaTime) {
// ΠΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ Π²ΠΈΠ΄ΠΈΠΌΡΡ
ΠΎΠ±Π»Π°ΡΡΠ΅ΠΉ
vtSystem.UpdateVisibleRegions(camera);
// ΠΠ°Π³ΡΡΠ·ΠΊΠ° Π½Π΅ΠΎΠ±Ρ
ΠΎΠ΄ΠΈΠΌΡΡ
ΡΡΡΠ°Π½ΠΈΡ
vtSystem.UpdatePageCache();
}
Π‘ΠΆΠ°ΡΠΈΠ΅ ΡΠ΅ΡΡΡΡΠΎΠ²
Wudgine ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΈΠ²Π°Π΅Ρ ΡΠ°Π·Π»ΠΈΡΠ½ΡΠ΅ ΡΠΎΡΠΌΠ°ΡΡ ΡΠΆΠ°ΡΠΈΡ Π΄Π»Ρ ΠΎΠΏΡΠΈΠΌΠΈΠ·Π°ΡΠΈΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ ΠΏΠ°ΠΌΡΡΠΈ.
Π‘ΠΆΠ°ΡΠΈΠ΅ ΡΠ΅ΠΊΡΡΡΡ
// ΠΠ°Π³ΡΡΠ·ΠΊΠ° ΡΠ΅ΠΊΡΡΡΡΡ Ρ ΡΠΊΠ°Π·Π°Π½ΠΈΠ΅ΠΌ ΡΠΎΡΠΌΠ°ΡΠ° ΡΠΆΠ°ΡΠΈΡ
Wudgine::Resources::TextureLoadOptions options;
options.compressionFormat = Wudgine::Resources::TextureCompressionFormat::BC7;
options.generateMipmaps = true;
options.mipmapFilter = Wudgine::Resources::MipmapFilter::Kaiser;
Wudgine::Resources::TextureHandle textureHandle =
resourceManager.LoadTexture("textures/albedo.png", options);
Π‘ΠΆΠ°ΡΠΈΠ΅ ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ
// ΠΠ°Π³ΡΡΠ·ΠΊΠ° ΠΌΠΎΠ΄Π΅Π»ΠΈ Ρ ΠΎΠΏΡΠΈΠΌΠΈΠ·Π°ΡΠΈΠ΅ΠΉ
Wudgine::Resources::ModelLoadOptions options;
options.optimizeMesh = true;
options.compressVertexData = true;
options.vertexCacheOptimization = true;
Wudgine::Resources::ModelHandle modelHandle =
resourceManager.LoadModel("models/complex_scene.obj", options);
ΠΠΎΡΡΡΠ°Ρ ΠΏΠ΅ΡΠ΅Π·Π°Π³ΡΡΠ·ΠΊΠ° ΡΠ΅ΡΡΡΡΠΎΠ²
ΠΠΎΡΡΡΠ°Ρ ΠΏΠ΅ΡΠ΅Π·Π°Π³ΡΡΠ·ΠΊΠ° ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ ΠΎΠ±Π½ΠΎΠ²Π»ΡΡΡ ΡΠ΅ΡΡΡΡΡ Π±Π΅Π· ΠΏΠ΅ΡΠ΅Π·Π°ΠΏΡΡΠΊΠ° ΠΈΠ³ΡΡ, ΡΡΠΎ ΡΡΠΊΠΎΡΡΠ΅Ρ ΠΏΡΠΎΡΠ΅ΡΡ ΡΠ°Π·ΡΠ°Π±ΠΎΡΠΊΠΈ.
ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° Π³ΠΎΡΡΡΠ΅ΠΉ ΠΏΠ΅ΡΠ΅Π·Π°Π³ΡΡΠ·ΠΊΠΈ
// ΠΠΊΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΡΠΈΡΡΠ΅ΠΌΡ Π³ΠΎΡΡΡΠ΅ΠΉ ΠΏΠ΅ΡΠ΅Π·Π°Π³ΡΡΠ·ΠΊΠΈ
Wudgine::Resources::HotReloadSystem hotReloadSystem;
hotReloadSystem.Initialize(resourceManager);
hotReloadSystem.WatchDirectory("assets/textures");
hotReloadSystem.WatchDirectory("assets/models");
hotReloadSystem.WatchDirectory("assets/shaders");
// Π Π΅Π³ΠΈΡΡΡΠ°ΡΠΈΡ ΠΎΠ±ΡΠ°Π±ΠΎΡΡΠΈΠΊΠ° ΡΠΎΠ±ΡΡΠΈΠΉ
hotReloadSystem.OnResourceReloaded([](const std::string& path) {
std::cout << "Π Π΅ΡΡΡΡ ΠΏΠ΅ΡΠ΅Π·Π°Π³ΡΡΠΆΠ΅Π½: " << path << std::endl;
});
// ΠΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΡΠΈΡΡΠ΅ΠΌΡ Π³ΠΎΡΡΡΠ΅ΠΉ ΠΏΠ΅ΡΠ΅Π·Π°Π³ΡΡΠ·ΠΊΠΈ
void Update(float deltaTime) {
hotReloadSystem.Update();
}
ΠΡΠ»Ρ ΡΠ΅ΡΡΡΡΠΎΠ²
ΠΡΠ»Ρ ΡΠ΅ΡΡΡΡΠΎΠ² ΠΏΠΎΠ·Π²ΠΎΠ»ΡΡΡ ΡΡΡΠ΅ΠΊΡΠΈΠ²Π½ΠΎ ΡΠΏΡΠ°Π²Π»ΡΡΡ ΡΠ°ΡΡΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌΡΠΌΠΈ ΠΎΠ±ΡΠ΅ΠΊΡΠ°ΠΌΠΈ.
Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΈ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΏΡΠ»Π°
// Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΠΏΡΠ»Π° ΡΠ°ΡΡΠΈΡ
Wudgine::Resources::ResourcePool<Particle> particlePool(1000);
// ΠΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΠΎΠ±ΡΠ΅ΠΊΡΠ° ΠΈΠ· ΠΏΡΠ»Π°
Particle* particle = particlePool.Acquire();
if (particle) {
// ΠΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΡ ΡΠ°ΡΡΠΈΡΡ
particle->position = position;
particle->velocity = velocity;
particle->lifetime = 2.0f;
particle->color = color;
}
// ΠΠΎΠ·Π²ΡΠ°Ρ ΠΎΠ±ΡΠ΅ΠΊΡΠ° Π² ΠΏΡΠ»
particlePool.Release(particle);
// ΠΡΠΈΡΡΠΊΠ° Π½Π΅ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌΡΡ
ΠΎΠ±ΡΠ΅ΠΊΡΠΎΠ²
particlePool.GarbageCollect();
ΠΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΡΠ΅ΡΡΡΡΠΎΠ²
ΠΡΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΏΠΎΠ·Π²ΠΎΠ»ΡΠ΅Ρ ΡΡΠΊΠΎΡΠΈΡΡ Π΄ΠΎΡΡΡΠΏ ΠΊ ΡΠ°ΡΡΠΎ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌΡΠΌ ΡΠ΅ΡΡΡΡΠ°ΠΌ.
ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° ΠΊΡΡΠ° ΡΠ΅ΡΡΡΡΠΎΠ²
// ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° ΠΊΡΡΠ° ΡΠ΅ΡΡΡΡΠΎΠ²
Wudgine::Resources::ResourceCache cache;
cache.SetMaxSize(1024 * 1024 * 512); // 512 ΠΠ
cache.SetEvictionPolicy(Wudgine::Resources::EvictionPolicy::LRU);
// ΠΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΡΠ΅ΡΡΡΡΠ° Π² ΠΊΡΡ
cache.Add("textures/ui.png", textureHandle);
// ΠΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΡΠ΅ΡΡΡΡΠ° ΠΈΠ· ΠΊΡΡΠ°
if (cache.Contains("textures/ui.png")) {
auto handle = cache.Get("textures/ui.png");
auto texture = resourceManager.Get<Wudgine::Resources::Texture>(handle);
}
// ΠΡΠΈΡΡΠΊΠ° ΠΊΡΡΠ°
cache.Clear();
ΠΡΠ΅Π΄Π²Π°ΡΠΈΡΠ΅Π»ΡΠ½Π°Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΡΠ΅ΡΡΡΡΠΎΠ²
Wudgine ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΈΠ²Π°Π΅Ρ ΠΏΡΠ΅Π΄Π²Π°ΡΠΈΡΠ΅Π»ΡΠ½ΡΡ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΡ ΡΠ΅ΡΡΡΡΠΎΠ² Π΄Π»Ρ ΠΎΠΏΡΠΈΠΌΠΈΠ·Π°ΡΠΈΠΈ ΠΈΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ Π² ΠΈΠ³ΡΠ΅.
ΠΠΎΠ½Π²Π΅ΠΉΠ΅Ρ ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ ΡΠ΅ΡΡΡΡΠΎΠ²
// ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅ΡΠ° ΠΎΠ±ΡΠ°Π±ΠΎΡΠΊΠΈ ΡΠ΅ΠΊΡΡΡΡ
Wudgine::Resources::TextureProcessor textureProcessor;
textureProcessor.AddStep<Wudgine::Resources::ResizeStep>(2048, 2048);
textureProcessor.AddStep<Wudgine::Resources::NormalMapGeneratorStep>();
textureProcessor.AddStep<Wudgine::Resources::CompressionStep>(Wudgine::Resources::TextureCompressionFormat::BC5);
// ΠΠ±ΡΠ°Π±ΠΎΡΠΊΠ° ΡΠ΅ΠΊΡΡΡΡΡ
textureProcessor.Process("textures/input.png", "textures/output.dds");
Π£ΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΠΏΠ°ΠΌΡΡΡΡ
ΠΡΡΠ΅ΠΊΡΠΈΠ²Π½ΠΎΠ΅ ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΠΏΠ°ΠΌΡΡΡΡ ΠΊΡΠΈΡΠΈΡΠ΅ΡΠΊΠΈ Π²Π°ΠΆΠ½ΠΎ Π΄Π»Ρ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΠΈ ΠΈΠ³ΡΡ.
ΠΡΡΠ»Π΅ΠΆΠΈΠ²Π°Π½ΠΈΠ΅ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ ΠΏΠ°ΠΌΡΡΠΈ
// ΠΠΎΠ»ΡΡΠ΅Π½ΠΈΠ΅ ΠΈΠ½ΡΠΎΡΠΌΠ°ΡΠΈΠΈ ΠΎΠ± ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠΈ ΠΏΠ°ΠΌΡΡΠΈ
Wudgine::Resources::MemoryStats memoryStats = resourceManager.GetMemoryStats();
std::cout << "ΠΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΏΠ°ΠΌΡΡΠΈ:" << std::endl;
std::cout << "- Π’Π΅ΠΊΡΡΡΡΡ: " << memoryStats.textureMemory / (1024 * 1024) << " ΠΠ" << std::endl;
std::cout << "- ΠΠΎΠ΄Π΅Π»ΠΈ: " << memoryStats.modelMemory / (1024 * 1024) << " ΠΠ" << std::endl;
std::cout << "- ΠΠ²ΡΠΊΠΈ: " << memoryStats.audioMemory / (1024 * 1024) << " ΠΠ" << std::endl;
std::cout << "- ΠΡΠ΅Π³ΠΎ: " << memoryStats.totalMemory / (1024 * 1024) << " ΠΠ" << std::endl;
// Π£ΡΡΠ°Π½ΠΎΠ²ΠΊΠ° ΠΎΠ³ΡΠ°Π½ΠΈΡΠ΅Π½ΠΈΠΉ ΠΏΠ°ΠΌΡΡΠΈ
resourceManager.SetMemoryLimit(Wudgine::Resources::ResourceType::Texture, 1024 * 1024 * 512); // 512 ΠΠ Π΄Π»Ρ ΡΠ΅ΠΊΡΡΡΡ
ΠΠ΅ΡΡΠ°Π³ΠΌΠ΅Π½ΡΠ°ΡΠΈΡ ΠΏΠ°ΠΌΡΡΠΈ
// ΠΠ°ΠΏΡΡΠΊ Π΄Π΅ΡΡΠ°Π³ΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ ΠΏΠ°ΠΌΡΡΠΈ
resourceManager.DefragmentMemory();
// ΠΠ°ΡΡΡΠΎΠΉΠΊΠ° Π°Π²ΡΠΎΠΌΠ°ΡΠΈΡΠ΅ΡΠΊΠΎΠΉ Π΄Π΅ΡΡΠ°Π³ΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ
resourceManager.SetAutoDefragment(true);
resourceManager.SetDefragmentThreshold(0.3f); // ΠΠ°ΠΏΡΡΠΊ ΠΏΡΠΈ 30% ΡΡΠ°Π³ΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ
ΠΡΠΈΠΌΠ΅ΡΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ
ΠΠ°Π³ΡΡΠ·ΠΊΠ° ΡΡΠΎΠ²Π½Ρ Ρ ΡΠΊΡΠ°Π½ΠΎΠΌ Π·Π°Π³ΡΡΠ·ΠΊΠΈ
// ΠΠ»Π°ΡΡ Π΄Π»Ρ ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΡ Π·Π°Π³ΡΡΠ·ΠΊΠΎΠΉ ΡΡΠΎΠ²Π½Ρ
class LevelLoader {
public:
void LoadLevel(const std::string& levelName) {
// ΠΠΎΠΊΠ°Π·Π°ΡΡ ΡΠΊΡΠ°Π½ Π·Π°Π³ΡΡΠ·ΠΊΠΈ
m_LoadingScreen.Show();
// Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π³ΡΡΠΏΠΏΡ Π·Π°Π³ΡΡΠ·ΠΊΠΈ Π΄Π»Ρ ΡΡΠΎΠ²Π½Ρ
Wudgine::Resources::LoadGroup levelGroup;
// ΠΠ°Π³ΡΡΠ·ΠΊΠ° ΠΌΠ°Π½ΠΈΡΠ΅ΡΡΠ° ΡΡΠΎΠ²Π½Ρ
auto manifestHandle = m_ResourceManager.LoadJson("levels/" + levelName + "/manifest.json");
auto manifest = m_ResourceManager.Get<Wudgine::Resources::JsonResource>(manifestHandle);
// ΠΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΡΠ΅ΡΡΡΡΠΎΠ² ΠΈΠ· ΠΌΠ°Π½ΠΈΡΠ΅ΡΡΠ°
for (const auto& textureInfo : manifest->GetArray("textures")) {
levelGroup.AddTexture(textureInfo["path"].GetString());
}
for (const auto& modelInfo : manifest->GetArray("models")) {
levelGroup.AddModel(modelInfo["path"].GetString());
}
// ΠΠ°ΠΏΡΡΠΊ Π°ΡΠΈΠ½Ρ
ΡΠΎΠ½Π½ΠΎΠΉ Π·Π°Π³ΡΡΠ·ΠΊΠΈ
m_ResourceManager.LoadGroupAsync(levelGroup, [this, levelName](Wudgine::Resources::LoadGroup& group) {
// ΠΠ°Π³ΡΡΠ·ΠΊΠ° Π·Π°Π²Π΅ΡΡΠ΅Π½Π°, ΡΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΡΡΠΎΠ²Π½Ρ
CreateLevel(levelName);
// Π‘ΠΊΡΡΡΡ ΡΠΊΡΠ°Π½ Π·Π°Π³ΡΡΠ·ΠΊΠΈ
m_LoadingScreen.Hide();
});
}
void Update(float deltaTime) {
// ΠΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΠΏΡΠΎΠ³ΡΠ΅ΡΡΠ° Π·Π°Π³ΡΡΠ·ΠΊΠΈ
if (m_LoadingScreen.IsVisible()) {
float progress = m_ResourceManager.GetGroupLoadingProgress(m_CurrentLoadGroup);
m_LoadingScreen.SetProgress(progress);
}
}
private:
Wudgine::Resources::ResourceManager& m_ResourceManager;
Wudgine::UI::LoadingScreen m_LoadingScreen;
Wudgine::Resources::LoadGroup m_CurrentLoadGroup;
void CreateLevel(const std::string& levelName) {
// Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΡΡΠΎΠ²Π½Ρ Π½Π° ΠΎΡΠ½ΠΎΠ²Π΅ Π·Π°Π³ΡΡΠΆΠ΅Π½Π½ΡΡ
ΡΠ΅ΡΡΡΡΠΎΠ²
// ...
}
};
Π‘ΠΈΡΡΠ΅ΠΌΠ° ΠΏΠΎΡΠΎΠΊΠΎΠ²ΠΎΠΉ Π·Π°Π³ΡΡΠ·ΠΊΠΈ ΠΎΡΠΊΡΡΡΠΎΠ³ΠΎ ΠΌΠΈΡΠ°
// Π‘ΠΈΡΡΠ΅ΠΌΠ° ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΡ ΡΠ°Π½ΠΊΠ°ΠΌΠΈ ΠΎΡΠΊΡΡΡΠΎΠ³ΠΎ ΠΌΠΈΡΠ°
class OpenWorldChunkManager {
public:
void Initialize(int worldSize, int chunkSize) {
m_WorldSize = worldSize;
m_ChunkSize = chunkSize;
// ΠΡΡΠΈΡΠ»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²Π° ΡΠ°Π½ΠΊΠΎΠ²
m_ChunksCount = worldSize / chunkSize;
// ΠΠ½ΠΈΡΠΈΠ°Π»ΠΈΠ·Π°ΡΠΈΡ ΠΌΠ°ΡΡΠΈΠ²Π° ΡΠ°Π½ΠΊΠΎΠ²
m_Chunks.resize(m_ChunksCount * m_ChunksCount);
}
void Update(const Wudgine::Math::Vector3& playerPosition) {
// ΠΠΏΡΠ΅Π΄Π΅Π»Π΅Π½ΠΈΠ΅ ΡΠ΅ΠΊΡΡΠ΅Π³ΠΎ ΡΠ°Π½ΠΊΠ° ΠΈΠ³ΡΠΎΠΊΠ°
int chunkX = static_cast<int>(playerPosition.x) / m_ChunkSize;
int chunkZ = static_cast<int>(playerPosition.z) / m_ChunkSize;
// ΠΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ Π²ΠΈΠ΄ΠΈΠΌΠΎΡΡΠΈ ΡΠ°Π½ΠΊΠΎΠ²
for (int z = 0; z < m_ChunksCount; z++) {
for (int x = 0; x < m_ChunksCount; x++) {
int distance = std::max(std::abs(x - chunkX), std::abs(z - chunkZ));
Chunk& chunk = GetChunk(x, z);
if (distance <= m_ViewDistance) {
// Π§Π°Π½ΠΊ Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±ΡΡΡ Π·Π°Π³ΡΡΠΆΠ΅Π½
if (!chunk.isLoaded) {
LoadChunk(x, z);
}
} else {
// Π§Π°Π½ΠΊ Π΄ΠΎΠ»ΠΆΠ΅Π½ Π±ΡΡΡ Π²ΡΠ³ΡΡΠΆΠ΅Π½
if (chunk.isLoaded) {
UnloadChunk(x, z);
}
}
}
}
}
private:
struct Chunk {
bool isLoaded = false;
Wudgine::Resources::LoadGroup resources;
Wudgine::ECS::Entity chunkEntity;
};
std::vector<Chunk> m_Chunks;
int m_WorldSize;
int m_ChunkSize;
int m_ChunksCount;
int m_ViewDistance = 3;
Chunk& GetChunk(int x, int z) {
return m_Chunks[z * m_ChunksCount + x];
}
void LoadChunk(int x, int z) {
Chunk& chunk = GetChunk(x, z);
// Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ Π³ΡΡΠΏΠΏΡ Π·Π°Π³ΡΡΠ·ΠΊΠΈ Π΄Π»Ρ ΡΠ°Π½ΠΊΠ°
Wudgine::Resources::LoadGroup chunkGroup;
// ΠΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΡΠ΅ΡΡΡΡΠΎΠ² ΡΠ°Π½ΠΊΠ°
std::string chunkPath = "world/chunks/" + std::to_string(x) + "_" + std::to_string(z);
chunkGroup.AddTexture(chunkPath + "/terrain.png");
chunkGroup.AddModel(chunkPath + "/terrain.obj");
// ΠΠ°ΠΏΡΡΠΊ Π°ΡΠΈΠ½Ρ
ΡΠΎΠ½Π½ΠΎΠΉ Π·Π°Π³ΡΡΠ·ΠΊΠΈ
m_ResourceManager.LoadGroupAsync(chunkGroup, [this, x, z](Wudgine::Resources::LoadGroup& group) {
// Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΡΡΡΠ½ΠΎΡΡΠΈ ΡΠ°Π½ΠΊΠ°
CreateChunkEntity(x, z);
// ΠΡΠΌΠ΅ΡΠΊΠ° ΡΠ°Π½ΠΊΠ° ΠΊΠ°ΠΊ Π·Π°Π³ΡΡΠΆΠ΅Π½Π½ΠΎΠ³ΠΎ
GetChunk(x, z).isLoaded = true;
});
// Π‘ΠΎΡ
ΡΠ°Π½Π΅Π½ΠΈΠ΅ Π³ΡΡΠΏΠΏΡ Π·Π°Π³ΡΡΠ·ΠΊΠΈ
chunk.resources = chunkGroup;
}
void UnloadChunk(int x, int z) {
Chunk& chunk = GetChunk(x, z);
// Π£Π΄Π°Π»Π΅Π½ΠΈΠ΅ ΡΡΡΠ½ΠΎΡΡΠΈ ΡΠ°Π½ΠΊΠ°
m_World.DestroyEntity(chunk.chunkEntity);
// ΠΡΠ³ΡΡΠ·ΠΊΠ° ΡΠ΅ΡΡΡΡΠΎΠ² ΡΠ°Π½ΠΊΠ°
m_ResourceManager.UnloadGroup(chunk.resources);
// ΠΡΠΌΠ΅ΡΠΊΠ° ΡΠ°Π½ΠΊΠ° ΠΊΠ°ΠΊ Π²ΡΠ³ΡΡΠΆΠ΅Π½Π½ΠΎΠ³ΠΎ
chunk.isLoaded = false;
}
void CreateChunkEntity(int x, int z) {
Chunk& chunk = GetChunk(x, z);
// Π‘ΠΎΠ·Π΄Π°Π½ΠΈΠ΅ ΡΡΡΠ½ΠΎΡΡΠΈ Π΄Π»Ρ ΡΠ°Π½ΠΊΠ°
chunk.chunkEntity = m_World.CreateEntity();
// ΠΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½ΡΠΎΠ²
auto& transform = m_World.AddComponent<Wudgine::ECS::TransformComponent>(chunk.chunkEntity);
transform.position = Wudgine::Math::Vector3(x * m_ChunkSize, 0, z * m_ChunkSize);
// ΠΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ ΠΌΠΎΠ΄Π΅Π»ΠΈ
auto& model = m_World.AddComponent<Wudgine::ECS::ModelComponent>(chunk.chunkEntity);
std::string modelPath = "world/chunks/" + std::to_string(x) + "_" + std::to_string(z) + "/terrain.obj";
model.handle = m_ResourceManager.GetHandle(modelPath);
}
Wudgine::Resources::ResourceManager& m_ResourceManager;
Wudgine::ECS::World& m_World;
};
Π§ΡΠΎ Π΄Π°Π»ΡΡΠ΅?
- ΠΠ·ΡΡΠΈΡΠ΅ ΠΎΡΠ»Π°Π΄ΠΊΡ ΠΈ ΠΏΡΠΎΡΠΈΠ»ΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ Π΄Π»Ρ Π°Π½Π°Π»ΠΈΠ·Π° ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΠΈ ΡΠΈΡΡΠ΅ΠΌΡ ΡΠ΅ΡΡΡΡΠΎΠ²
- ΠΠ·Π½Π°ΠΊΠΎΠΌΡΡΠ΅ΡΡ Ρ ΡΠ΅ΡΠ΅Π²ΡΠΌ Π²Π·Π°ΠΈΠΌΠΎΠ΄Π΅ΠΉΡΡΠ²ΠΈΠ΅ΠΌ Π΄Π»Ρ ΡΠΎΠ·Π΄Π°Π½ΠΈΡ ΠΌΠ½ΠΎΠ³ΠΎΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΠ΅Π»ΡΡΠΊΠΈΡ ΠΈΠ³Ρ
- ΠΡΡΠ»Π΅Π΄ΡΠΉΡΠ΅ ΠΏΡΠΈΠΌΠ΅ΡΡ ΠΎΠΏΡΠΈΠΌΠΈΠ·Π°ΡΠΈΠΈ ΡΠ΅ΡΡΡΡΠΎΠ² Π² ΡΠ΅ΠΏΠΎΠ·ΠΈΡΠΎΡΠΈΠΈ ΠΏΡΠΈΠΌΠ΅ΡΠΎΠ² Wudgine
ΠΠ΅ΡΡΡΠ΅ΠΊΡΠΈΠ²Π½ΠΎΠ΅ ΡΠΏΡΠ°Π²Π»Π΅Π½ΠΈΠ΅ ΡΠ΅ΡΡΡΡΠ°ΠΌΠΈ ΠΌΠΎΠΆΠ΅Ρ ΠΏΡΠΈΠ²Π΅ΡΡΠΈ ΠΊ ΡΡΠ΅ΡΠΊΠ°ΠΌ ΠΏΠ°ΠΌΡΡΠΈ ΠΈ ΡΠ½ΠΈΠΆΠ΅Π½ΠΈΡ ΠΏΡΠΎΠΈΠ·Π²ΠΎΠ΄ΠΈΡΠ΅Π»ΡΠ½ΠΎΡΡΠΈ. Π Π΅Π³ΡΠ»ΡΡΠ½ΠΎ ΠΏΡΠΎΠ²ΠΎΠ΄ΠΈΡΠ΅ ΠΏΡΠΎΡΠΈΠ»ΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΈ ΠΎΠΏΡΠΈΠΌΠΈΠ·Π°ΡΠΈΡ ΡΠΈΡΡΠ΅ΠΌΡ ΡΠ΅ΡΡΡΡΠΎΠ².