Какая именно версия Unreal Engine и Voxel Plugin (Free/Pro)? Это - pro Нужно ли сохранять мир между сессиями? Где хранить (локально на хосте / на бэкенде)? да нужно сохранять - локально на хосте На каких платформах - на ПК Требуется ли поддержка автономного редактирования чанков (редактор карт) - нет Если хотите — могу: привести пример сигнатур RPC на C++ и сериализации правок (скелет ServerRequestEdit / MulticastApplyEdit); набросать структуру данных для хранения - да нужно
Отлично — на основе ваших ответов (Unreal Engine + Voxel Plugin Pro, PC, сохранять локально на хосте, без автономного редактора чанков) ниже даю:
a) структуры:
// Псевдо-код / UE C++
UENUM()
enum class EEditOpType : uint8
{
SetValue,
SetMaterial,
// ... другие типы
};
struct FEditOperation
{
FIntVector LocalPos; // локальная координата внутри чанка (x,y,z)
EEditOpType OpType;
float Value; // для SetValue
uint16 MaterialIndex; // для SetMaterial
bool NetSerialize(FArchive& Ar)
{
Ar << LocalPos.X << LocalPos.Y << LocalPos.Z;
Ar << *(uint8*)&OpType;
Ar << Value;
Ar << MaterialIndex;
return true;
}
};
struct FChunkEdit
{
FIntVector ChunkCoords;
uint32 ServerVersion;
TArray<FEditOperation> Ops;
void SerializeToBytes(TArray<uint8>& OutBytes)
{
FMemoryWriter Writer(OutBytes, true);
Writer << ChunkCoords.X << ChunkCoords.Y << ChunkCoords.Z;
Writer << ServerVersion;
int32 OpsCount = Ops.Num();
Writer << OpsCount;
for (auto& Op : Ops)
{
Op.NetSerialize(Writer);
}
}
bool DeserializeFromBytes(const TArray<uint8>& InBytes)
{
FMemoryReader Reader(InBytes, true);
Reader << ChunkCoords.X << ChunkCoords.Y << ChunkCoords.Z;
Reader << ServerVersion;
int32 OpsCount = 0;
Reader << OpsCount;
Ops.Reset();
Ops.Reserve(OpsCount);
for (int32 i = 0; i < OpsCount; ++i)
{
FEditOperation Op;
Op.NetSerialize(Reader);
Ops.Add(Op);
}
return true;
}
};
b) сжатие / контроль целостности (пример):
// Сжать OutBytes -> CompressedBytes
int32 UncompressedSize = OutBytes.Num();
TArray<uint8> Compressed;
Compressed.SetNumUninitialized(FCompression::CompressMemoryBound(NAME_LZ4, UncompressedSize));
int32 CompressedSize = 0;
FCompression::CompressMemory(NAME_LZ4, Compressed.GetData(), Compressed.Num(), OutBytes.GetData(), UncompressedSize, COMPRESS_BiasSpeed);
Compressed.SetNum(CompressedSize);
// можно хранить перед сжатием размер и CRC32
uint32 CRC = FCrc::MemCrc32(OutBytes.GetData(), OutBytes.Num());
UCLASS()
class AMyVoxelServerManager : public AActor
{
GENERATED_BODY()
public:
// Клиент -> Сервер: запрос на правку
UFUNCTION(Server, Reliable, WithValidation)
void ServerRequestEdit(APlayerController* RequestingPC, const TArray<uint8>& CompressedEditPayload);
void ServerRequestEdit_Implementation(APlayerController* RequestingPC, const TArray<uint8>& CompressedEditPayload);
bool ServerRequestEdit_Validate(APlayerController* RequestingPC, const TArray<uint8>& CompressedEditPayload);
// Сервер -> все клиенты: применять правку
UFUNCTION(NetMulticast, Reliable)
void MulticastApplyEdit(const TArray<uint8>& CompressedEditPayload);
void MulticastApplyEdit_Implementation(const TArray<uint8>& CompressedEditPayload);
};
Валидация (ServerRequestEdit_Validate) должна:
Реализация (ServerRequestEdit_Implementation) — сервер декомпрессирует, проверит и применяет правку в атомарной операции (лод/блокировка чанка), увеличит версию чанка, сохранит в очередь на диск и вызовет MulticastApplyEdit для распространения.
void AMyVoxelServerManager::ServerRequestEdit_Implementation(APlayerController* RequestingPC, const TArray<uint8>& CompressedEditPayload)
{
// 1) Проверки размера/подписи (omitted)
// 2) Распаковка
TArray<uint8> Decompressed;
// узнаем UncompressedSize каким-то образом (можно положить в header)
FCompression::UncompressMemory(NAME_LZ4, Decompressed.GetData(), UncompressedSize, CompressedEditPayload.GetData(), CompressedEditPayload.Num());
FChunkEdit Edit;
Edit.DeserializeFromBytes(Decompressed);
// 3) Авторитетная валидация: пересечение дальности игрока, права, sane coords и т.д.
if (!IsEditAllowed(RequestingPC, Edit)) return;
// 4) Применение к серверной структуре (нужна блокировка/синхронизация)
ApplyEditToServerWorld(Edit);
// 5) Сохранение на диск (асинхронно) — MarkChunkDirtyAndScheduleSave(Edit.ChunkCoords)
// 6) Рассылка всем клиентам (можно отправить тот же сжатый payload)
MulticastApplyEdit(CompressedEditPayload);
}
void AMyVoxelServerManager::MulticastApplyEdit_Implementation(const TArray<uint8>& CompressedEditPayload)
{
// Клиенты распаковывают и применяют локально (без проверки авторизации)
TArray<uint8> Decompressed;
// Uncompress...
FChunkEdit Edit;
Edit.DeserializeFromBytes(Decompressed);
// Здесь инициировать Voxel Plugin API apply: e.g. FVoxelData->Set* или использовать транзакции Voxel Plugin
ApplyEditLocally(Edit);
}
Замечание: при больших payload'ах RPC может не подойти — в этом случае лучше использовать replication graph/streaming или кастомный сетевой канал (UDP/TCP) / chunked upload.
Если хотите, могу сейчас:
Что предпочитаете?