模拟市场上unity引擎研发一款自己的引擎

开发一个类似于Unity的游戏引擎是一个非常复杂和庞大的任务,涉及多个领域的知识,包括图形渲染、物理引擎、音频处理、脚本系统、用户界面、资源管理等。以下是一个高层次的实现细节和内容概述,以及一些示例代码片段。

实现细节和内容

  1. 图形渲染

    • 渲染管线:实现一个基本的渲染管线,包括顶点处理、光栅化、片段处理等。
    • 着色器:支持自定义着色器,使用GLSL或HLSL编写。
    • 材质和纹理:管理材质和纹理资源,支持多种纹理格式。
  2. 物理引擎

    • 碰撞检测:实现基本的碰撞检测算法,如AABB、OBB、球体碰撞等。
    • 刚体动力学:实现刚体的运动和力学计算,包括重力、摩擦力等。
    • 约束和关节:支持物理约束和关节,如铰链关节、弹簧关节等。
  3. 音频处理

    • 音频播放:支持音频文件的加载和播放,支持多种音频格式。
    • 3D音效:实现3D音效,支持音源和听者的位置和方向。
  4. 脚本系统

    • 脚本语言:集成脚本语言,如C#、Lua等,支持脚本编写和执行。
    • 事件系统:实现事件系统,支持事件的注册和触发。
  5. 用户界面

    • UI系统:实现基本的UI系统,支持按钮、文本框、滑动条等控件。
    • UI布局:支持UI布局管理,如水平布局、垂直布局等。
  6. 资源管理

    • 资源加载:实现资源加载和管理,支持异步加载。
    • 资源缓存:实现资源缓存,避免重复加载资源。
  7. 场景管理

    • 场景树:实现场景树结构,管理场景中的对象和层级关系。
    • 场景切换:支持场景的加载和切换。
  8. 输入处理

    • 输入设备:支持键盘、鼠标、手柄等输入设备。
    • 输入事件:实现输入事件的处理和分发。

示例代码

以下是一些示例代码片段,展示如何实现一些基本功能。

图形渲染

使用OpenGL实现一个简单的渲染管线:

// 初始化OpenGL
void InitOpenGL() {
    // 设置清除颜色
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    // 启用深度测试
    glEnable(GL_DEPTH_TEST);
}

// 渲染循环
void RenderLoop() {
    while (!glfwWindowShouldClose(window)) {
        // 清除颜色缓冲和深度缓冲
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // 绘制场景
        DrawScene();

        // 交换缓冲
        glfwSwapBuffers(window);
        // 处理事件
        glfwPollEvents();
    }
}
物理引擎

实现一个简单的碰撞检测:

struct AABB {
    glm::vec3 min;
    glm::vec3 max;
};

bool CheckCollision(const AABB& a, const AABB& b) {
    return (a.min.x <= b.max.x && a.max.x >= b.min.x) &&
           (a.min.y <= b.max.y && a.max.y >= b.min.y) &&
           (a.min.z <= b.max.z && a.max.z >= b.min.z);
}
脚本系统

集成Lua脚本:

extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}

lua_State* L = luaL_newstate();
luaL_openlibs(L);

// 执行Lua脚本
void ExecuteScript(const std::string& script) {
    if (luaL_dofile(L, script.c_str()) != LUA_OK) {
        std::cerr << "Error: " << lua_tostring(L, -1) << std::endl;
    }
}

好的,我们继续探讨如何实现一个类似于Unity的游戏引擎,接下来将涵盖用户界面、资源管理、场景管理和输入处理等方面的内容。

用户界面

实现一个简单的UI系统,可以使用ImGui库来快速构建用户界面。

使用ImGui实现基本UI
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"

// 初始化ImGui
void InitImGui(GLFWwindow* window) {
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO(); (void)io;
    ImGui::StyleColorsDark();
    ImGui_ImplGlfw_InitForOpenGL(window, true);
    ImGui_ImplOpenGL3_Init("#version 130");
}

// 渲染ImGui
void RenderImGui() {
    ImGui_ImplOpenGL3_NewFrame();
    ImGui_ImplGlfw_NewFrame();
    ImGui::NewFrame();

    // 创建一个简单的窗口
    ImGui::Begin("Hello, world!");
    ImGui::Text("This is some useful text.");
    ImGui::End();

    ImGui::Render();
    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}

// 清理ImGui
void CleanupImGui() {
    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGlfw_Shutdown();
    ImGui::DestroyContext();
}

资源管理

实现一个简单的资源管理系统,支持资源的加载和缓存。

资源管理类
#include <unordered_map>
#include <string>
#include <memory>

template <typename T>
class ResourceManager {
public:
    std::shared_ptr<T> Load(const std::string& path) {
        auto it = resources.find(path);
        if (it != resources.end()) {
            return it->second;
        }

        std::shared_ptr<T> resource = std::make_shared<T>();
        if (resource->LoadFromFile(path)) {
            resources[path] = resource;
            return resource;
        }

        return nullptr;
    }

private:
    std::unordered_map<std::string, std::shared_ptr<T>> resources;
};
示例资源类
class Texture {
public:
    bool LoadFromFile(const std::string& path) {
        // 加载纹理文件的代码
        return true;
    }
};

场景管理

实现一个简单的场景管理系统,支持场景的加载和切换。

场景类
#include <vector>
#include <memory>

class Scene {
public:
    void AddObject(const std::shared_ptr<GameObject>& object) {
        objects.push_back(object);
    }

    void Update(float deltaTime) {
        for (auto& object : objects) {
            object->Update(deltaTime);
        }
    }

    void Render() {
        for (auto& object : objects) {
            object->Render();
        }
    }

private:
    std::vector<std::shared_ptr<GameObject>> objects;
};
场景管理类
class SceneManager {
public:
    void SetCurrentScene(const std::shared_ptr<Scene>& scene) {
        currentScene = scene;
    }

    void Update(float deltaTime) {
        if (currentScene) {
            currentScene->Update(deltaTime);
        }
    }

    void Render() {
        if (currentScene) {
            currentScene->Render();
        }
    }

private:
    std::shared_ptr<Scene> currentScene;
};

输入处理

实现一个简单的输入处理系统,支持键盘和鼠标输入。

输入管理类
#include <GLFW/glfw3.h>
#include <unordered_map>

class InputManager {
public:
    void Update() {
        for (auto& key : keys) {
            key.second = glfwGetKey(window, key.first);
        }
    }

    bool IsKeyPressed(int key) const {
        auto it = keys.find(key);
        if (it != keys.end()) {
            return it->second == GLFW_PRESS;
        }
        return false;
    }

    void SetWindow(GLFWwindow* win) {
        window = win;
    }

private:
    GLFWwindow* window;
    std::unordered_map<int, int> keys;
};

综合示例

将上述各个模块整合到一起,形成一个简单的游戏引擎框架。

好的,我们继续整合各个模块,形成一个简单的游戏引擎框架。

主程序

#include <GLFW/glfw3.h>
#include "ResourceManager.h"
#include "SceneManager.h"
#include "InputManager.h"
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_opengl3.h"

// 全局变量
GLFWwindow* window;
ResourceManager<Texture> textureManager;
SceneManager sceneManager;
InputManager inputManager;

// 初始化函数
void Init() {
    // 初始化GLFW
    if (!glfwInit()) {
        exit(EXIT_FAILURE);
    }

    // 创建窗口
    window = glfwCreateWindow(800, 600, "Simple Game Engine", nullptr, nullptr);
    if (!window) {
        glfwTerminate();
        exit(EXIT_FAILURE);
    }

    // 设置OpenGL上下文
    glfwMakeContextCurrent(window);
    glfwSwapInterval(1);

    // 初始化ImGui
    InitImGui(window);

    // 设置输入管理器的窗口
    inputManager.SetWindow(window);

    // 初始化OpenGL
    InitOpenGL();
}

// 清理函数
void Cleanup() {
    // 清理ImGui
    CleanupImGui();

    // 销毁窗口
    glfwDestroyWindow(window);

    // 终止GLFW
    glfwTerminate();
}

// 主循环
void MainLoop() {
    while (!glfwWindowShouldClose(window)) {
        // 更新输入
        inputManager.Update();

        // 更新场景
        float deltaTime = 1.0f / 60.0f; // 假设固定帧率
        sceneManager.Update(deltaTime);

        // 渲染场景
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        sceneManager.Render();

        // 渲染ImGui
        RenderImGui();

        // 交换缓冲
        glfwSwapBuffers(window);

        // 处理事件
        glfwPollEvents();
    }
}

int main() {
    // 初始化
    Init();

    // 主循环
    MainLoop();

    // 清理
    Cleanup();

    return 0;
}

示例游戏对象

为了展示如何使用这个简单的引擎,我们可以创建一个示例游戏对象。

游戏对象类
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

class GameObject {
public:
    GameObject(const std::shared_ptr<Texture>& texture)
        : texture(texture), position(0.0f), scale(1.0f) {}

    void Update(float deltaTime) {
        // 更新逻辑,例如移动、旋转等
    }

    void Render() {
        // 渲染逻辑,例如绑定纹理、设置矩阵等
        glBindTexture(GL_TEXTURE_2D, texture->GetID());
        // 设置模型矩阵
        glm::mat4 model = glm::translate(glm::mat4(1.0f), position);
        model = glm::scale(model, scale);
        // 传递矩阵到着色器
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        // 绘制对象
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }

    void SetPosition(const glm::vec3& pos) { position = pos; }
    void SetScale(const glm::vec3& scl) { scale = scl; }

private:
    std::shared_ptr<Texture> texture;
    glm::vec3 position;
    glm::vec3 scale;
    GLuint modelLoc; // 着色器中模型矩阵的位置
};

示例场景

创建一个示例场景,并添加一些游戏对象。

示例场景类
class ExampleScene : public Scene {
public:
    ExampleScene() {
        // 加载纹理
        auto texture = textureManager.Load("path/to/texture.png");

        // 创建游戏对象
        auto object = std::make_shared<GameObject>(texture);
        object->SetPosition(glm::vec3(0.0f, 0.0f, 0.0f));
        object->SetScale(glm::vec3(1.0f, 1.0f, 1.0f));

        // 添加到场景
        AddObject(object);
    }
};

主程序中加载场景

在主程序中加载示例场景。

void Init() {
    // 初始化GLFW
    if (!glfwInit()) {
        exit(EXIT_FAILURE);
    }

    // 创建窗口
    window = glfwCreateWindow(800, 600, "Simple Game Engine", nullptr, nullptr);
    if (!window) {
        glfwTerminate();
        exit(EXIT_FAILURE);
    }

    // 设置OpenGL上下文
    glfwMakeContextCurrent(window);
    glfwSwapInterval(1);

    // 初始化ImGui
    InitImGui(window);

    // 设置输入管理器的窗口
    inputManager.SetWindow(window);

    // 初始化OpenGL
    InitOpenGL();

    // 加载示例场景
    auto exampleScene = std::make_shared<ExampleScene>();
    sceneManager.SetCurrentScene(exampleScene);
}

结语

以上代码展示了如何实现一个简单的游戏引擎框架,包括图形渲染、物理引擎、音频处理、脚本系统、用户界面、资源管理、场景管理和输入处理等方面的内容。这个框架只是一个起点,实际开发中还需要更多的功能和优化,例如更复杂的渲染技术、物理模拟、动画系统、网络支持等。

希望这些示例代码和实现细节对你有所帮助,祝你在开发自己的游戏引擎时取得成功!

好的,我们继续扩展这个简单的游戏引擎框架,增加更多的功能和优化。接下来,我们将探讨如何实现更复杂的渲染技术、物理模拟、动画系统和网络支持等。

更复杂的渲染技术

为了实现更复杂的渲染技术,我们可以引入光照、阴影、后处理效果等。

光照

实现基本的光照模型,如Phong光照模型。

// 顶点着色器
const char* vertexShaderSource = R"(
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNormal;
layout(location = 2) in vec2 aTexCoords;

out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main() {
    FragPos = vec3(model * vec4(aPos, 1.0));
    Normal = mat3(transpose(inverse(model))) * aNormal;
    TexCoords = aTexCoords;
    gl_Position = projection * view * vec4(FragPos, 1.0);
}
)";

// 片段着色器
const char* fragmentShaderSource = R"(
#version 330 core
out vec4 FragColor;

in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;

uniform sampler2D texture_diffuse;
uniform vec3 lightPos;
uniform vec3 viewPos;
uniform vec3 lightColor;
uniform vec3 objectColor;

void main() {
    // 环境光
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;

    // 漫反射
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;

    // 镜面反射
    float specularStrength = 0.5;
    vec3 viewDir = normalize(viewPos - FragPos);
    vec3 reflectDir = reflect(-lightDir, norm);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
    vec3 specular = specularStrength * spec * lightColor;

    vec3 result = (ambient + diffuse + specular) * objectColor;
    FragColor = texture(texture_diffuse, TexCoords) * vec4(result, 1.0);
}
)";
阴影

实现阴影映射技术。

// 深度着色器
const char* depthVertexShaderSource = R"(
#version 330 core
layout(location = 0) in vec3 aPos;

uniform mat4 lightSpaceMatrix;
uniform mat4 model;

void main() {
    gl_Position = lightSpaceMatrix * model * vec4(aPos, 1.0);
}
)";

const char* depthFragmentShaderSource = R"(
#version 330 core
void main() {
    // 只写入深度缓冲
}
)";

物理模拟

引入第三方物理引擎,如Bullet Physics。

Bullet Physics集成
#include <btBulletDynamicsCommon.h>

// 初始化Bullet
btBroadphaseInterface* broadphase = new btDbvtBroadphase();
btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration();
btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration);
btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver();
btDiscreteDynamicsWorld* dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration);
dynamicsWorld->setGravity(btVector3(0, -9.81, 0));

// 创建刚体
btCollisionShape* groundShape = new btStaticPlaneShape(btVector3(0, 1, 0), 1);
btDefaultMotionState* groundMotionState = new btDefaultMotionState(btTransform(btQuaternion(0, 0, 0, 1), btVector3(0, -1, 0)));
btRigidBody::btRigidBodyConstructionInfo groundRigidBodyCI(0, groundMotionState, groundShape, btVector3(0, 0, 0));
btRigidBody* groundRigidBody = new btRigidBody(groundRigidBodyCI);
dynamicsWorld->addRigidBody(groundRigidBody);

// 更新物理世界
void UpdatePhysics(float deltaTime) {
    dynamicsWorld->stepSimulation(deltaTime, 10);
}

动画系统

实现骨骼动画系统。

好的,我们继续扩展骨骼动画系统和网络支持。

骨骼动画系统

骨骼动画系统通常涉及到骨骼(骨架)和蒙皮(皮肤)的处理。我们可以使用Assimp库来加载模型和动画数据。

使用Assimp加载模型和动画

首先,确保你已经安装了Assimp库,并在项目中包含了相关头文件和库文件。

#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
骨骼和蒙皮数据结构

定义骨骼和蒙皮的数据结构。

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <vector>
#include <map>
#include <string>

struct BoneInfo {
    glm::mat4 BoneOffset;
    glm::mat4 FinalTransformation;
};

class SkinnedMesh {
public:
    SkinnedMesh() {}
    ~SkinnedMesh() {}

    bool LoadMesh(const std::string& filename);
    void Render();
    void Update(float deltaTime);

private:
    void LoadBones(uint32_t meshIndex, const aiMesh* pMesh, std::vector<VertexBoneData>& bones);
    void ReadNodeHierarchy(float animationTime, const aiNode* pNode, const glm::mat4& parentTransform);
    const aiNodeAnim* FindNodeAnim(const aiAnimation* pAnimation, const std::string& nodeName);

    std::vector<BoneInfo> m_BoneInfo;
    std::map<std::string, uint32_t> m_BoneMapping;
    uint32_t m_NumBones;
    glm::mat4 m_GlobalInverseTransform;
    const aiScene* m_pScene;
    Assimp::Importer m_Importer;
};
加载骨骼数据

LoadMesh函数中加载骨骼数据。

bool SkinnedMesh::LoadMesh(const std::string& filename) {
    m_pScene = m_Importer.ReadFile(filename, aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs | aiProcess_CalcTangentSpace);
    if (!m_pScene) {
        return false;
    }

    m_GlobalInverseTransform = glm::inverse(glm::mat4(1.0f));

    for (uint32_t i = 0; i < m_pScene->mNumMeshes; i++) {
        const aiMesh* pMesh = m_pScene->mMeshes[i];
        std::vector<VertexBoneData> bones(pMesh->mNumVertices);
        LoadBones(i, pMesh, bones);
    }

    return true;
}

void SkinnedMesh::LoadBones(uint32_t meshIndex, const aiMesh* pMesh, std::vector<VertexBoneData>& bones) {
    for (uint32_t i = 0; i < pMesh->mNumBones; i++) {
        uint32_t boneIndex = 0;
        std::string boneName(pMesh->mBones[i]->mName.data);

        if (m_BoneMapping.find(boneName) == m_BoneMapping.end()) {
            boneIndex = m_NumBones;
            m_NumBones++;
            BoneInfo bi;
            m_BoneInfo.push_back(bi);
            m_BoneInfo[boneIndex].BoneOffset = glm::mat4(1.0f);
        } else {
            boneIndex = m_BoneMapping[boneName];
        }

        m_BoneMapping[boneName] = boneIndex;
        m_BoneInfo[boneIndex].BoneOffset = glm::mat4(1.0f);

        for (uint32_t j = 0; j < pMesh->mBones[i]->mNumWeights; j++) {
            uint32_t vertexID = pMesh->mBones[i]->mWeights[j].mVertexId;
            float weight = pMesh->mBones[i]->mWeights[j].mWeight;
            bones[vertexID].AddBoneData(boneIndex, weight);
        }
    }
}
更新骨骼动画

好的,我们继续完成骨骼动画系统的实现,并介绍如何进行网络支持。

骨骼动画系统(续)

读取节点层次结构

ReadNodeHierarchy函数中递归读取节点层次结构,并计算每个骨骼的最终变换矩阵。

void SkinnedMesh::ReadNodeHierarchy(float animationTime, const aiNode* pNode, const glm::mat4& parentTransform) {
    std::string nodeName(pNode->mName.data);

    const aiAnimation* pAnimation = m_pScene->mAnimations[0];
    glm::mat4 nodeTransformation = glm::mat4(1.0f);

    const aiNodeAnim* pNodeAnim = FindNodeAnim(pAnimation, nodeName);

    if (pNodeAnim) {
        // Interpolate scaling
        aiVector3D scaling;
        CalcInterpolatedScaling(scaling, animationTime, pNodeAnim);
        glm::mat4 scalingM = glm::scale(glm::mat4(1.0f), glm::vec3(scaling.x, scaling.y, scaling.z));

        // Interpolate rotation
        aiQuaternion rotationQ;
        CalcInterpolatedRotation(rotationQ, animationTime, pNodeAnim);
        glm::mat4 rotationM = glm::mat4_cast(glm::quat(rotationQ.w, rotationQ.x, rotationQ.y, rotationQ.z));

        // Interpolate translation
        aiVector3D translation;
        CalcInterpolatedPosition(translation, animationTime, pNodeAnim);
        glm::mat4 translationM = glm::translate(glm::mat4(1.0f), glm::vec3(translation.x, translation.y, translation.z));

        nodeTransformation = translationM * rotationM * scalingM;
    }

    glm::mat4 globalTransformation = parentTransform * nodeTransformation;

    if (m_BoneMapping.find(nodeName) != m_BoneMapping.end()) {
        uint32_t boneIndex = m_BoneMapping[nodeName];
        m_BoneInfo[boneIndex].FinalTransformation = m_GlobalInverseTransform * globalTransformation * m_BoneInfo[boneIndex].BoneOffset;
    }

    for (uint32_t i = 0; i < pNode->mNumChildren; i++) {
        ReadNodeHierarchy(animationTime, pNode->mChildren[i], globalTransformation);
    }
}
查找节点动画

FindNodeAnim函数中查找节点动画。

const aiNodeAnim* SkinnedMesh::FindNodeAnim(const aiAnimation* pAnimation, const std::string& nodeName) {
    for (uint32_t i = 0; i < pAnimation->mNumChannels; i++) {
        const aiNodeAnim* pNodeAnim = pAnimation->mChannels[i];
        if (std::string(pNodeAnim->mNodeName.data) == nodeName) {
            return pNodeAnim;
        }
    }
    return nullptr;
}

更新骨骼动画
在Update函数中更新骨骼动画。

cpp
复制
void SkinnedMesh::Update(float deltaTime) {
float ticksPerSecond = (float)(m_pScene->mAnimations[0]->mTicksPerSecond != 0 ? m_pScene->mAnimations[0]->mTicksPerSecond : 25.0f);
float timeInTicks = deltaTime * ticksPerSecond;
float animationTime = fmod(timeInTicks, (float)m_pScene->mAnimations[0]->mDuration);

ReadNodeHierarchy(animationTime, m_pScene->mRootNode, glm::mat4(1.
插值计算

实现插值计算函数。

void SkinnedMesh::CalcInterpolatedScaling(aiVector3D& out, float animationTime, const aiNodeAnim* pNodeAnim) {
    if (pNodeAnim->mNumScalingKeys == 1) {
        out = pNodeAnim->mScalingKeys[0].mValue;
        return;
    }

    uint32_t scalingIndex = FindScaling(animationTime, pNodeAnim);
    uint32_t nextScalingIndex = (scalingIndex + 1);
    assert(nextScalingIndex < pNodeAnim->mNumScalingKeys);
    float deltaTime = (float)(pNodeAnim->mScalingKeys[nextScalingIndex].mTime - pNodeAnim->mScalingKeys[scalingIndex].mTime);
    float factor = (animationTime - (float)pNodeAnim->mScalingKeys[scalingIndex].mTime) / deltaTime;
    const aiVector3D& start = pNodeAnim->mScalingKeys[scalingIndex].mValue;
    const aiVector3D& end = pNodeAnim->mScalingKeys[nextScalingIndex].mValue;
    out = start + factor * (end - start);
}

void SkinnedMesh::CalcInterpolatedRotation(aiQuaternion& out, float animationTime, const aiNodeAnim* pNodeAnim) {
    if (pNodeAnim->mNumRotationKeys == 1) {
        out = pNodeAnim->mRotationKeys[0].mValue;
        return;
    }

    uint32_t rotationIndex = FindRotation(animationTime, pNodeAnim);
    uint32_t nextRotationIndex = (rotationIndex + 1);
    assert(nextRotationIndex < pNodeAnim->mNumRotationKeys);
    float deltaTime = (float)(pNodeAnim->mRotationKeys[nextRotationIndex].mTime - pNodeAnim->mRotationKeys[rotationIndex].mTime);
    float factor = (animationTime - (float)pNodeAnim->mRotationKeys[rotationIndex].mTime) / deltaTime;
    const aiQuaternion& startRotationQ = pNodeAnim->mRotationKeys[rotationIndex].mValue;
    const aiQuaternion& endRotationQ = pNodeAnim->mRotationKeys[nextRotationIndex].mValue;
    aiQuaternion::Interpolate(out, startRotationQ, endRotationQ, factor);
    out = out.Normalize();
}

void SkinnedMesh::CalcInterpolatedPosition(aiVector3D& out, float animationTime, const aiNodeAnim* pNodeAnim) {
    if (pNodeAnim->mNumPositionKeys == 1) {
        out = pNodeAnim->mPositionKeys[0].mValue;
        return;
    }

    uint32_t positionIndex = FindPosition(animationTime, pNodeAnim);
    uint32_t nextPositionIndex = (positionIndex + 1);
    assert(nextPositionIndex < pNodeAnim->mNumPositionKeys);
    float deltaTime = (float)(pNodeAnim->mPositionKeys[nextPositionIndex].mTime - pNodeAnim->mPositionKeys[positionIndex].mTime);
    float factor = (animationTime - (float)pNodeAnim->mPositionKeys[positionIndex].mTime) / deltaTime;
    const aiVector3D& start = pNodeAnim->mPositionKeys[positionIndex].mValue;
    const aiVector3D& end = pNodeAnim->mPositionKeys[nextPositionIndex].mValue;
    out = start + factor * (end - start);
}
查找关键帧索引
实现查找关键帧索引的函数。

cpp
复制
uint32_t SkinnedMesh::FindScaling(float animationTime, const aiNodeAnim* pNodeAnim) {
    for (uint32_t i = 0; i < pNodeAnim->mNumScalingKeys - 1; i++) {
        if (animationTime < (float)pNodeAnim->mScalingKeys[i + 1].mTime) {
            return i;
        }
    }
    assert(0);
    return 0;
}

uint32_t SkinnedMesh::FindRotation(float animationTime, const aiNodeAnim* pNodeAnim) {
    for (uint32_t i = 0; i < pNodeAnim->mNumRotationKeys - 1; i++) {
        if (animationTime < (float)pNodeAnim->mRotationKeys[i + 1].mTime) {
            return i;
        }
    }
    assert(0);
    return 0;
}

uint32_t SkinnedMesh::FindPosition(float animationTime, const aiNodeAnim* pNodeAnim) {
    for (uint32_t i = 0; i < pNodeAnim->mNumPositionKeys - 1; i++) {
        if (animationTime < (float)pNodeAnim->mPositionKeys[i + 1].mTime) {
            return i;
        }
    }
    assert(0);
    return 0;
}
网络支持
为了实现网络支持,我们可以使用一个简单的网络库,如ENet或Boost.Asio。这里我们将使用ENet来实现一个基本的客户端-服务器架构。

安装ENet
首先,确保你已经安装了ENet库,并在项目中包含了相关头文件和库文件。

服务器端
实现一个简单的服务器端。

cpp
复制
#include <enet/enet.h>
#include <iostream>

int main(int argc, char** argv) {
    if (enet_initialize() != 0) {
        std::cerr << "An error occurred while initializing ENet.\n";
        return EXIT_FAILURE;
    }
    atexit(enet_deinitialize);

    ENetAddress address;
    ENetHost* server;
    ENetEvent event;

    address.host = ENET_HOST_ANY; // Bind the server to the default localhost.
    address.port = 1234; // The port to listen on.

    server = enet_host_create(&address /* the address to bind the server host to */,
                              32      /* allow up to 32 clients and/or outgoing connections */,
                              2       /* allow up to 2 channels to be used, 0 and 1 */,
                              0       /* assume any amount of incoming bandwidth */,
                              0       /* assume any amount of outgoing bandwidth */);

    if (server == nullptr) {
        std::cerr << "An error occurred while trying to create an ENet server host.\n";
        return EXIT_FAILURE;
    }

    std::cout << "Server started on port " << address.port << "\n";

    while (true) {
        while (enet_host_service(server, &event, 1000) > 0) {
            switch (event.type) {
                case ENET_EVENT_TYPE_CONNECT:
                    std::cout << "A new client connected from "
                              << event.peer->address.host << ":" << event.peer->address.port << "\n";
                    event.peer->data = (void*)"Client information";
                    break;
                case ENET_EVENT_TYPE_RECEIVE:
                    std::cout << "A packet of length " << event.packet->dataLength
                              << " containing " << event.packet->data
                              << " was received from " << (char*)event.peer->data
                              << " on channel " << event.channelID << ".\n";
                    enet_packet_destroy(event.packet);
                    break;
                case ENET_EVENT_TYPE_DISCONNECT:
                    std::cout << (char*)event.peer->data << " disconnected.\n";
                    event.peer->data = nullptr;
                    break;
                default:
                    break;
            }
        }
    }

    enet_host_destroy(server);
    return EXIT_SUCCESS;
}

客户端
实现一个简单的客户端。

cpp
复制
#include <enet/enet.h>
#include <iostream>

int main(int argc, char** argv) {
    if (enet_initialize() != 0) {
        std::cerr << "An error occurred while initializing ENet.\n";
        return EXIT_FAILURE;
    }
    atexit(enet_deinitialize);

    ENetHost* client;
    ENetAddress address;
    ENetEvent event;
    ENetPeer* peer;

    client = enet_host_create(nullptr /* create a client host */,
                              1      /* only allow 1 outgoing connection */,
                              2      /* allow up to 2 channels to be used, 0 and 1 */,
                              0      /* assume any amount of incoming bandwidth */,
                              0      /* assume any amount of outgoing bandwidth */);

    if (client == nullptr) {
        std::cerr << "An error occurred while trying to create an ENet client host.\n";
        return EXIT_FAILURE;
    }

    enet_address_set_host(&address, "localhost");
    address.port = 1234;

    peer = enet_host_connect(client, &address, 2, 0);
    if (peer == nullptr) {
        std::cerr << "No available peers for initiating an ENet connection.\n";
        return EXIT_FAILURE;
    }

    if (enet_host_service(client, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
        std::cout << "Connection to localhost:1234 succeeded.\n";
    } else {
        enet_peer_reset(peer);
        std::cerr << "Connection to localhost:1234 failed.\n";
        return EXIT_FAILURE;
    }

    ENetPacket* packet = enet_packet_create("Hello, server!",
                                            strlen("Hello, server!") + 1,
                                            ENET_PACKET_FLAG_RELIABLE);
    enet_peer_send(peer, 0, packet);
    enet_host_flush(client);

    while (enet_host_service(client, &event, 5000) > 0) {
        switch (event.type) {
            case ENET_EVENT_TYPE_RECEIVE:
                std::cout << "A packet of length " << event.packet->dataLength
                          << " containing " << event.packet->data
                          << " was received from server.\n";
                enet_packet_destroy(event.packet);
                break;
            case ENET_EVENT_TYPE_DISCONNECT:
                std::cout << "Disconnected from server.\n";
                return EXIT_SUCCESS;
            default:
                break;
        }
    }

    enet_peer_disconnect(peer, 0);
    while (enet_host_service(client, &event, 3000) > 0) {
               switch (event.type) {
            case ENET_EVENT_TYPE_RECEIVE:
                enet_packet_destroy(event.packet);
                break;
            case ENET_EVENT_TYPE_DISCONNECT:
                std::cout << "Disconnection succeeded.\n";
                return EXIT_SUCCESS;
            default:
                break;
        }
    }

    enet_peer_reset(peer);
    enet_host_destroy(client);
    return EXIT_SUCCESS;
}

运行服务器和客户端
编译和运行服务器:

编译服务器代码。
运行服务器程序。
编译和运行客户端:

编译客户端代码。
运行客户端程序。
服务器和客户端的交互
当客户端连接到服务器时,服务器会输出连接信息。客户端发送消息后,服务器会接收到并输出消息内容。服务器可以处理多个客户端的连接和消息。

结合骨骼动画系统和网络支持
为了将骨骼动画系统与网络支持结合起来,我们可以在客户端发送骨骼动画数据到服务器,服务器接收并处理这些数据。

客户端发送骨骼动画数据
在客户端代码中,修改发送数据的部分,将骨骼动画数据打包并发送。

cpp
复制
// 假设我们有一个函数来获取骨骼动画数据
std::vector<float> GetBoneAnimationData();

std::vector<float> boneData = GetBoneAnimationData();
ENetPacket* packet = enet_packet_create(boneData.data(),
                                        boneData.size() * sizeof(float),
                                        ENET_PACKET_FLAG_RELIABLE);
enet_peer_send(peer, 0, packet);
enet_host_flush(client);
服务器接收骨骼动画数据
在服务器代码中,修改接收数据的部分,解析并处理骨骼动画数据。

cpp
复制
case ENET_EVENT_TYPE_RECEIVE:
    std::cout << "A packet of length " << event.packet->dataLength
              << " was received from " << (char*)event.peer->data
              << " on channel " << event.channelID << ".\n";

    // 解析骨骼动画数据
    std::vector<float> boneData(event.packet->dataLength / sizeof(float));
    std::memcpy(boneData.data(), event.packet->data, event.packet->dataLength);

    // 处理骨骼动画数据
    ProcessBoneAnimationData(boneData);

    enet_packet_destroy(event.packet);
    break;
总结
我们已经实现了一个基本的骨骼动画系统,并通过ENet库实现了网络支持。客户端可以发送骨骼动画数据到服务器,服务器接收并处理这些数据。这个基础可以扩展到更复杂的网络游戏或分布式动画系统中。

如果你有任何进一步的问题或需要更详细的实现,请随时告诉我!

好的,我们继续完善骨骼动画系统与网络支持的结合。我们将详细讨论如何获取骨骼动画数据、打包数据、发送数据以及在服务器端解析和处理数据。

### 获取骨骼动画数据

假设我们有一个骨骼动画系统,能够在每一帧计算出骨骼的变换矩阵。我们需要将这些矩阵打包成一个数据包,发送到服务器。

#### 获取骨骼变换矩阵

```cpp
std::vector<aiMatrix4x4> SkinnedMesh::GetBoneTransforms(float animationTime) {
    std::vector<aiMatrix4x4> transforms(m_NumBones);

    // 计算每个骨骼的变换矩阵
    for (uint32_t i = 0; i < m_NumBones; i++) {
        aiMatrix4x4 boneTransform = CalculateBoneTransform(i, animationTime);
        transforms[i] = boneTransform;
    }

    return transforms;
}
打包骨骼变换矩阵

将骨骼变换矩阵打包成一个数据包,以便通过网络发送。

std::vector<float> PackBoneTransforms(const std::vector<aiMatrix4x4>& transforms) {
    std::vector<float> packedData;
    packedData.reserve(transforms.size() * 16); // 每个矩阵有16个float

    for (const auto& transform : transforms) {
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                packedData.push_back(transform[i][j]);
            }
        }
    }

    return packedData;
}

客户端发送骨骼动画数据

在客户端代码中,获取骨骼动画数据并发送到服务器。

#include <vector>
#include <enet/enet.h>
#include <iostream>

// 假设我们有一个SkinnedMesh对象
SkinnedMesh skinnedMesh;

int main(int argc, char** argv) {
    if (enet_initialize() != 0) {
        std::cerr << "An error occurred while initializing ENet.\n";
        return EXIT_FAILURE;
    }
    atexit(enet_deinitialize);

    ENetHost* client;
    ENetAddress address;
    ENetEvent event;
    ENetPeer* peer;

    client = enet_host_create(nullptr /* create a client host */,
                              1      /* only allow 1 outgoing connection */,
                              2      /* allow up to 2 channels to be used, 0 and 1 */,
                              0      /* assume any amount of incoming bandwidth */,
                              0      /* assume any amount of outgoing bandwidth */);

    if (client == nullptr) {
        std::cerr << "An error occurred while trying to create an ENet client host.\n";
        return EXIT_FAILURE;
    }

    enet_address_set_host(&address, "localhost");
    address.port = 1234;

    peer = enet_host_connect(client, &address, 2, 0);
    if (peer == nullptr) {
        std::cerr << "No available peers for initiating an ENet connection.\n";
        return EXIT_FAILURE;
    }

    if (enet_host_service(client, &event, 5000) > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
        std::cout << "Connection to localhost:1234 succeeded.\n";
    } else {
        enet_peer_reset(peer);
        std::cerr << "Connection to localhost:1234 failed.\n";
        return EXIT_FAILURE;
    }

    // 获取骨骼动画数据
    float animationTime = 0.0f; // 假设我们有一个动画时间
    std::vector<aiMatrix4x4> boneTransforms = skinnedMesh.GetBoneTransforms(animationTime);
    std::vector<float> packedData = PackBoneTransforms(boneTransforms);

    // 发送骨骼动画数据
    ENetPacket* packet = enet_packet_create(packedData.data(),
                                            packedData.size() * sizeof(float),
                                            ENET_PACKET_FLAG_RELIABLE);
    enet_peer_send(peer, 0, packet);
    enet_host_flush(client);

    while (enet_host_service(client, &event, 5000) > 0) {
        switch (event.type) {
            case ENET_EVENT_TYPE_RECEIVE:
                std::cout << "A packet of length " << event.packet->dataLength
                          << " containing " << event.packet->data
                          << " was received from server.\n";
                enet_packet_destroy(event.packet);
                break;
            case ENET_EVENT_TYPE_DISCONNECT:
                           std::cout << "Disconnected from server.\n";
                return EXIT_SUCCESS;
            default:
                break;
        }
    }

    enet_peer_disconnect(peer, 0);
    while (enet_host_service(client, &event, 3000) > 0) {
        switch (event.type) {
            case ENET_EVENT_TYPE_RECEIVE:
                enet_packet_destroy(event.packet);
                break;
            case ENET_EVENT_TYPE_DISCONNECT:
                std::cout << "Disconnection succeeded.\n";
                return EXIT_SUCCESS;
            default:
                break;
        }
    }

    enet_peer_reset(peer);
    enet_host_destroy(client);
    return EXIT_SUCCESS;
}

服务器接收骨骼动画数据
在服务器端,我们需要解析接收到的数据包,并将其转换回骨骼变换矩阵。

解析骨骼变换矩阵
cpp
复制
std::vector<aiMatrix4x4> UnpackBoneTransforms(const std::vector<float>& packedData) {
    std::vector<aiMatrix4x4> transforms(packedData.size() / 16);

    for (size_t i = 0; i < transforms.size(); ++i) {
        aiMatrix4x4& transform = transforms[i];
        for (int j = 0; j < 4; ++j) {
            for (int k = 0; k < 4; ++k) {
                transform[j][k] = packedData[i * 16 + j * 4 + k];
            }
        }
    }

    return transforms;
}
服务器端接收和处理数据
在服务器代码中,修改接收数据的部分,解析并处理骨骼动画数据。

cpp
复制
#include <enet/enet.h>
#include <iostream>
#include <vector>
#include <cstring>

// 假设我们有一个函数来处理骨骼动画数据
void ProcessBoneAnimationData(const std::vector<aiMatrix4x4>& boneTransforms);

int main(int argc, char** argv) {
    if (enet_initialize() != 0) {
        std::cerr << "An error occurred while initializing ENet.\n";
        return EXIT_FAILURE;
    }
    atexit(enet_deinitialize);

    ENetAddress address;
    ENetHost* server;
    ENetEvent event;

    address.host = ENET_HOST_ANY;
    address.port = 1234;

    server = enet_host_create(&address /* the address to bind the server host to */,
                              32      /* allow up to 32 clients and/or outgoing connections */,
                              2       /* allow up to 2 channels to be used, 0 and 1 */,
                              0       /* assume any amount of incoming bandwidth */,
                              0       /* assume any amount of outgoing bandwidth */);

    if (server == nullptr) {
        std::cerr << "An error occurred while trying to create an ENet server host.\n";
        return EXIT_FAILURE;
    }

    std::cout << "Server started on port " << address.port << "\n";

    while (true) {
        while (enet_host_service(server, &event, 1000) > 0) {
            switch (event.type) {
                case ENET_EVENT_TYPE_CONNECT:
                    std::cout << "A new client connected from "
                              << event.peer->address.host << ":" << event.peer->address.port << "\n";
                    event.peer->data = (void*)"Client information";
                    break;
                case ENET_EVENT_TYPE_RECEIVE:
                    std::cout << "A packet of length " << event.packet->dataLength
                              << " was received from " << (char*)event.peer->data
                              << " on channel " << event.channelID << ".\n";

                    // 解析骨骼动画数据
                    std::vector<float> packedData(event.packet->dataLength / sizeof(float));
                    std::memcpy(packedData.data(), event.packet->data, event.packet->dataLength);
                    std::vector<aiMatrix4x4> boneTransforms = UnpackBoneTransforms(packedData);

                    // 处理骨骼动画数据
                    ProcessBoneAnimationData(boneTransforms);

                    enet_packet_destroy(event.packet);
                    break;
                case ENET_EVENT_TYPE_DISCONNECT:
                    std::cout << (char*)event.peer->data << " disconnected.\n";
                    event.peer->data = nullptr;
                    break;
                default:
                    break;
            }
        }
    }

    enet_host_destroy(server);
    return EXIT_SUCCESS;
}

好的,我们继续完成服务器端的实现,并详细讨论如何处理接收到的骨骼动画数据。

### 处理骨骼动画数据

在服务器端,我们需要定义一个函数 `ProcessBoneAnimationData` 来处理接收到的骨骼动画数据。这个函数可以将骨骼动画数据应用到服务器端的模型上,或者将其转发给其他客户端。

#### 处理骨骼动画数据

```cpp
void ProcessBoneAnimationData(const std::vector<aiMatrix4x4>& boneTransforms) {
    // 这里可以将骨骼动画数据应用到服务器端的模型上
    // 或者将其转发给其他客户端
    for (const auto& transform : boneTransforms) {
        // 打印骨骼变换矩阵
        std::cout << "Bone Transform:\n";
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                std::cout << transform[i][j] << " ";
            }
            std::cout << "\n";
        }
        std::cout << "\n";
    }
}

完整的服务器代码

将所有部分整合在一起,形成完整的服务器代码。

#include <enet/enet.h>
#include <iostream>
#include <vector>
#include <cstring>
#include <assimp/matrix4x4.h>

// 假设我们有一个函数来处理骨骼动画数据
void ProcessBoneAnimationData(const std::vector<aiMatrix4x4>& boneTransforms);

std::vector<aiMatrix4x4> UnpackBoneTransforms(const std::vector<float>& packedData) {
    std::vector<aiMatrix4x4> transforms(packedData.size() / 16);

    for (size_t i = 0; i < transforms.size(); ++i) {
        aiMatrix4x4& transform = transforms[i];
        for (int j = 0; j < 4; ++j) {
            for (int k = 0; k < 4; ++k) {
                transform[j][k] = packedData[i * 16 + j * 4 + k];
            }
        }
    }

    return transforms;
}

int main(int argc, char** argv) {
    if (enet_initialize() != 0) {
        std::cerr << "An error occurred while initializing ENet.\n";
        return EXIT_FAILURE;
    }
    atexit(enet_deinitialize);

    ENetAddress address;
    ENetHost* server;
    ENetEvent event;

    address.host = ENET_HOST_ANY;
    address.port = 1234;

    server = enet_host_create(&address /* the address to bind the server host to */,
                              32      /* allow up to 32 clients and/or outgoing connections */,
                              2       /* allow up to 2 channels to be used, 0 and 1 */,
                              0       /* assume any amount of incoming bandwidth */,
                              0       /* assume any amount of outgoing bandwidth */);

    if (server == nullptr) {
        std::cerr << "An error occurred while trying to create an ENet server host.\n";
        return EXIT_FAILURE;
    }

    std::cout << "Server started on port " << address.port << "\n";

    while (true) {
        while (enet_host_service(server, &event, 1000) > 0) {
            switch (event.type) {
                case ENET_EVENT_TYPE_CONNECT:
                    std::cout << "A new client connected from "
                              << event.peer->address.host << ":" << event.peer->address.port << "\n";
                    event.peer->data = (void*)"Client information";
                    break;
                case ENET_EVENT_TYPE_RECEIVE:
                    std::cout << "A packet of length " << event.packet->dataLength
                              << " was received from " << (char*)event.peer->data
                              << " on channel " << event.channelID << ".\n";

                    // 解析骨骼动画数据
                    std::vector<float> packedData(event.packet->dataLength / sizeof(float));
                    std::memcpy(packedData.data(), event.packet->data, event.packet->dataLength);
                    std::vector<aiMatrix4x4> boneTransforms = UnpackBoneTransforms(packedData);

                    // 处理骨骼动画数据
                    ProcessBoneAnimationData(boneTransforms);

                    enet_packet_destroy(event.packet);
                    break;
                case ENET_EVENT_TYPE_DISCONNECT:
                    std::cout << (char*)event.peer->data << " disconnected.\n";
                    event.peer->data = nullptr;
                    break;
                           default:
                    break;
            }
        }
    }

    enet_host_destroy(server);
    return EXIT_SUCCESS;
}

void ProcessBoneAnimationData(const std::vector<aiMatrix4x4>& boneTransforms) {
    // 这里可以将骨骼动画数据应用到服务器端的模型上
    // 或者将其转发给其他客户端
    for (const auto& transform : boneTransforms) {
        // 打印骨骼变换矩阵
        std::cout << "Bone Transform:\n";
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                std::cout << transform[i][j] << " ";
            }
            std::cout << "\n";
        }
        std::cout << "\n";
    }
}
服务器端的改进
为了更好地管理客户端连接和数据处理,我们可以对服务器端进行一些改进,例如:

管理客户端连接:使用一个数据结构来存储和管理所有连接的客户端。
转发数据:将接收到的骨骼动画数据转发给其他客户端,以实现多客户端同步。
管理客户端连接
我们可以使用一个 std::unordered_map 来存储和管理所有连接的客户端。

cpp
复制
#include <unordered_map>

std::unordered_map<ENetPeer*, std::string> clients;

void OnClientConnect(ENetPeer* peer) {
    std::string clientInfo = "Client " + std::to_string(peer->address.host) + ":" + std::to_string(peer->address.port);
    clients[peer] = clientInfo;
    std::cout << clientInfo << " connected.\n";
}

void OnClientDisconnect(ENetPeer* peer) {
    std::cout << clients[peer] << " disconnected.\n";
    clients.erase(peer);
}
转发数据
将接收到的骨骼动画数据转发给其他客户端。

cpp
复制
void ForwardBoneAnimationData(ENetHost* server, ENetPeer* sender, const std::vector<float>& packedData) {
    for (auto& client : clients) {
        if (client.first != sender) {
            ENetPacket* packet = enet_packet_create(packedData.data(),
                                                    packedData.size() * sizeof(float),
                                                    ENET_PACKET_FLAG_RELIABLE);
            enet_peer_send(client.first, 0, packet);
        }
    }
    enet_host_flush(server);
}
完整的改进后的服务器代码
将所有部分整合在一起,形成改进后的服务器代码。

cpp
复制
#include <enet/enet.h>
#include <iostream>
#include <vector>
#include <unordered_map>
#include <cstring>
#include <assimp/matrix4x4.h>

// 假设我们有一个函数来处理骨骼动画数据
void ProcessBoneAnimationData(const std::vector<aiMatrix4x4>& boneTransforms);

std::vector<aiMatrix4x4> UnpackBoneTransforms(const std::vector<float>& packedData) {
    std::vector<aiMatrix4x4> transforms(packedData.size() / 16);

    for (size_t i = 0; i < transforms.size(); ++i) {
        aiMatrix4x4& transform = transforms[i];
        for (int j = 0; j < 4; ++j) {
            for (int k = 0; k < 4; ++k) {
                transform[j][k] = packedData[i * 16 + j * 4 + k];
            }
        }
    }

    return transforms;
}

std::unordered_map<ENetPeer*, std::string> clients;

void OnClientConnect(ENetPeer* peer) {
    std::string clientInfo = "Client " + std::to_string(peer->address.host) + ":" + std::to_string(peer->address.port);
    clients[peer] = clientInfo;
    std::cout << clientInfo << " connected.\n";
}

void OnClientDisconnect(ENetPeer* peer) {
    std::cout << clients[peer] << " disconnected.\n";
    clients.erase(peer);
}

void ForwardBoneAnimationData(ENetHost* server, ENetPeer* sender, const std::vector<float>& packedData) {
    for (auto& client : clients) {
        if (client.first != sender) {
            ENetPacket* packet = enet_packet_create(packedData.data(),
                                                    packedData.size                                                      * sizeof(float),
                                                    ENET_PACKET_FLAG_RELIABLE);
            enet_peer_send(client.first, 0, packet);
        }
    }
    enet_host_flush(server);
}

int main(int argc, char** argv) {
    if (enet_initialize() != 0) {
        std::cerr << "An error occurred while initializing ENet.\n";
        return EXIT_FAILURE;
    }
    atexit(enet_deinitialize);

    ENetAddress address;
    ENetHost* server;
    ENetEvent event;

    address.host = ENET_HOST_ANY;
    address.port = 1234;

    server = enet_host_create(&address /* the address to bind the server host to */,
                              32      /* allow up to 32 clients and/or outgoing connections */,
                              2       /* allow up to 2 channels to be used, 0 and 1 */,
                              0       /* assume any amount of incoming bandwidth */,
                              0       /* assume any amount of outgoing bandwidth */);

    if (server == nullptr) {
        std::cerr << "An error occurred while trying to create an ENet server host.\n";
        return EXIT_FAILURE;
    }

    std::cout << "Server started on port " << address.port << "\n";

    while (true) {
        while (enet_host_service(server, &event, 1000) > 0) {
            switch (event.type) {
                case ENET_EVENT_TYPE_CONNECT:
                    OnClientConnect(event.peer);
                    break;
                case ENET_EVENT_TYPE_RECEIVE:
                    std::cout << "A packet of length " << event.packet->dataLength
                              << " was received from " << clients[event.peer]
                              << " on channel " << event.channelID << ".\n";

                    // 解析骨骼动画数据
                    std::vector<float> packedData(event.packet->dataLength / sizeof(float));
                    std::memcpy(packedData.data(), event.packet->data, event.packet->dataLength);
                    std::vector<aiMatrix4x4> boneTransforms = UnpackBoneTransforms(packedData);

                    // 处理骨骼动画数据
                    ProcessBoneAnimationData(boneTransforms);

                    // 转发骨骼动画数据给其他客户端
                    ForwardBoneAnimationData(server, event.peer, packedData);

                    enet_packet_destroy(event.packet);
                    break;
                case ENET_EVENT_TYPE_DISCONNECT:
                    OnClientDisconnect(event.peer);
                    break;
                default:
                    break;
            }
        }
    }

    enet_host_destroy(server);
    return EXIT_SUCCESS;
}

void ProcessBoneAnimationData(const std::vector<aiMatrix4x4>& boneTransforms) {
    // 这里可以将骨骼动画数据应用到服务器端的模型上
    // 或者将其转发给其他客户端
    for (const auto& transform : boneTransforms) {
        // 打印骨骼变换矩阵
        std::cout << "Bone Transform:\n";
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                std::cout << transform[i][j] << " ";
            }
            std::cout << "\n";
        }
        std::cout << "\n";
    }
}        

代码说明
客户端管理:使用 std::unordered_map<ENetPeer*, std::string> 来存储和管理所有连接的客户端。每当有客户端连接或断开时,更新该数据结构。
数据转发:在 ForwardBoneAnimationData 函数中,将接收到的骨骼动画数据转发给其他所有客户端。
数据处理:在 ProcessBoneAnimationData 函数中,可以对接收到的骨骼动画数据进行处理,例如应用到服务器端的模型上,或者进行其他处理。
总结
通过以上步骤,我们实现了一个简单的客户端-服务器架构,客户端可以发送骨骼动画数据到服务器,服务器可以解析并处理这些数据,并将其转发给其他客户端。这样可以实现多客户端之间的骨骼动画同步。

如果你有任何进一步的问题或需要更多的功能实现,请随时告诉我! 

好的,我们可以进一步扩展和优化服务器端的实现,以提高其功能性和可靠性。以下是一些可以考虑的改进和扩展:

### 1. 增加日志记录

为了更好地调试和监控服务器的运行状态,可以增加日志记录功能。可以使用标准库中的 `std::ofstream` 来将日志写入文件。

```cpp
#include <fstream>

std::ofstream logFile("server_log.txt");

void Log(const std::string& message) {
    std::cout << message << std::endl;
    logFile << message << std::endl;
}

在代码中将所有的 std::cout 替换为 Log 函数调用。

2. 增加心跳机制

为了检测客户端是否仍然在线,可以增加心跳机制。服务器定期向客户端发送心跳包,客户端需要回复。如果在一定时间内没有收到回复,则认为客户端已断开。

void SendHeartbeat(ENetHost* server) {
    for (auto& client : clients) {
        ENetPacket* packet = enet_packet_create("heartbeat", strlen("heartbeat") + 1, ENET_PACKET_FLAG_RELIABLE);
        enet_peer_send(client.first, 1, packet);
    }
    enet_host_flush(server);
}

在主循环中定期调用 SendHeartbeat 函数。

3. 增加命令处理

可以增加命令处理功能,允许客户端发送特定命令来执行某些操作。例如,客户端可以发送一个命令来请求服务器上的某些数据。

void ProcessCommand(const std::string& command, ENetPeer* peer) {
    if (command == "get_status") {
        std::string status = "Server is running";
        ENetPacket* packet = enet_packet_create(status.c_str(), status.size() + 1, ENET_PACKET_FLAG_RELIABLE);
        enet_peer_send(peer, 0, packet);
    }
}

在接收数据时,检查是否是命令并进行处理。

4. 增加错误处理

增加错误处理以提高服务器的稳定性。例如,处理网络错误、数据解析错误等。

try {
    // 解析骨骼动画数据
    std::vector<float> packedData(event.packet->dataLength / sizeof(float));
    std::memcpy(packedData.data(), event.packet->data, event.packet->dataLength);
    std::vector<aiMatrix4x4> boneTransforms = UnpackBoneTransforms(packedData);

    // 处理骨骼动画数据
    ProcessBoneAnimationData(boneTransforms);

    // 转发骨骼动画数据给其他客户端
    ForwardBoneAnimationData(server, event.peer, packedData);
} catch (const std::exception& e) {
    Log("Error processing packet: " + std::string(e.what()));
}

完整的改进后的服务器代码

将所有改进整合在一起,形成完整的改进后的服务器代码。

#include <enet/enet.h>
#include <iostream>
#include <vector>
#include <unordered_map>
#include <cstring>
#include <fstream>
#include <assimp/matrix4x4.h>

std::ofstream logFile("server_log.txt");

void Log(const std::string& message) {
    std::cout << message << std::endl;
    logFile << message << std::endl;
}

void ProcessBoneAnimationData(const std::vector<aiMatrix4x4>& boneTransforms);

std::vector<aiMatrix4x4> UnpackBoneTransforms(const std::vector<float>& packedData) {
    std::vector<aiMatrix4x4> transforms(packedData.size() / 16);

    for (size_t i = 0; i < transforms.size(); ++i) {
        aiMatrix4x4& transform = transforms[i];
        for (int j = 0; j < 4; ++j) {
            for (int k = 0; k < 4; ++k) {
                transform[j][k] = packedData[i * 16 + j * 4 + k];
            }
        }
    }

    return transforms;
}

std::unordered_map<ENetPeer*, std::string> clients;

void OnClientConnect(ENetPeer* peer) {
    std::string clientInfo = "Client " + std::to_string(peer->address.host) + ":" + std::to_string(peer->address.port);
    clients[peer] = clientInfo;
    Log(clientInfo + " connected.");
}

void OnClientDisconnect(ENetPeer* peer) {
     Log(clients[peer] + " disconnected.");
    clients.erase(peer);
}

void ForwardBoneAnimationData(ENetHost* server, ENetPeer* sender, const std::vector<float>& packedData) {
    for (auto& client : clients) {
        if (client.first != sender) {
            ENetPacket* packet = enet_packet_create(packedData.data(),
                                                    packedData.size() * sizeof(float),
                                                    ENET_PACKET_FLAG_RELIABLE);
            enet_peer_send(client.first, 0, packet);
        }
    }
    enet_host_flush(server);
}

void SendHeartbeat(ENetHost* server) {
    for (auto& client : clients) {
        ENetPacket* packet = enet_packet_create("heartbeat", strlen("heartbeat") + 1, ENET_PACKET_FLAG_RELIABLE);
        enet_peer_send(client.first, 1, packet);
    }
    enet_host_flush(server);
}

void ProcessCommand(const std::string& command, ENetPeer* peer) {
    if (command == "get_status") {
        std::string status = "Server is running";
        ENetPacket* packet = enet_packet_create(status.c_str(), status.size() + 1, ENET_PACKET_FLAG_RELIABLE);
        enet_peer_send(peer, 0, packet);
    }
}

int main(int argc, char** argv) {
    if (enet_initialize() != 0) {
        Log("An error occurred while initializing ENet.");
        return EXIT_FAILURE;
    }
    atexit(enet_deinitialize);

    ENetAddress address;
    ENetHost* server;
    ENetEvent event;

    address.host = ENET_HOST_ANY;
    address.port = 1234;

    server = enet_host_create(&address /* the address to bind the server host to */,
                              32      /* allow up to 32 clients and/or outgoing connections */,
                              2       /* allow up to 2 channels to be used, 0 and 1 */,
                              0       /* assume any amount of incoming bandwidth */,
                              0       /* assume any amount of outgoing bandwidth */);

    if (server == nullptr) {
        Log("An error occurred while trying to create an ENet server host.");
        return EXIT_FAILURE;
    }

    Log("Server started on port " + std::to_string(address.port));

    while (true) {
        while (enet_host_service(server, &event, 1000) > 0) {
            switch (event.type) {
                case ENET_EVENT_TYPE_CONNECT:
                    OnClientConnect(event.peer);
                    break;
                case ENET_EVENT_TYPE_RECEIVE:
                    Log("A packet of length " + std::to_string(event.packet->dataLength) +
                        " was received from " + clients[event.peer] +
                        " on channel " + std::to_string(event.channelID) + ".");

                    try {
                        // 解析骨骼动画数据
                        std::vector<float> packedData(event.packet->dataLength / sizeof(float));
                        std::memcpy(packedData.data(), event.packet->data, event.packet->dataLength);
                        std::vector<aiMatrix4x4> boneTransforms = UnpackBoneTransforms(packedData);

                        // 处理骨骼动画数据
                        ProcessBoneAnimationData(boneTransforms);

                        // 转发骨骼动画数据给其他客户端
                        ForwardBoneAnimationData(server, event.peer, packedData);
                    } catch (const std::exception& e) {
                        Log("Error processing packet: " + std::string(e.what()));
                    }

                    enet_packet_destroy(event.packet);
                    break;
                case ENET_EVENT_TYPE_DISCONNECT:
                    OnClientDisconnect(event.peer);
                    break;
                default:
                    break;
            }
        }

        // 定期发送心跳包
        SendHeartbeat(server);
    }

    enet_host_destroy(server);
    return EXIT_SUCCESS;
}

void ProcessBoneAnimationData(const std::vector<aiMatrix4x4>& boneTransforms) {
    // 这里可以将骨骼动画数据应用到服务器端的模型上
    // 或者将其转发给其他客户端
    for (const auto& transform : boneTransforms) {
        // 打印骨骼变换矩阵
        Log("Bone Transform:");
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                std::cout << transform[i][j] << " ";
            }
            std::cout << "\n";
        }
        std::cout << "\n";
    }
}

好的,我们继续完成和优化服务器代码。

### 完整的改进后的服务器代码(续)

```cpp
void ProcessBoneAnimationData(const std::vector<aiMatrix4x4>& boneTransforms) {
    // 这里可以将骨骼动画数据应用到服务器端的模型上
    // 或者将其转发给其他客户端
    for (const auto& transform : boneTransforms) {
        // 打印骨骼变换矩阵
        Log("Bone Transform:");
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                std::cout << transform[i][j] << " ";
            }
            std::cout << "\n";
        }
        std::cout << "\n";
    }
}

代码说明

  1. 日志记录:使用 std::ofstream 将日志写入文件,并在控制台输出。通过 Log 函数统一管理日志记录。
  2. 心跳机制:在 SendHeartbeat 函数中,服务器定期向所有客户端发送心跳包,以检测客户端是否仍然在线。
  3. 命令处理:在 ProcessCommand 函数中,处理客户端发送的命令。例如,客户端可以发送 get_status 命令来请求服务器状态。
  4. 错误处理:在接收数据时,使用 try-catch 块捕获并处理可能的异常,确保服务器的稳定性。

进一步优化

  1. 多线程处理:可以使用多线程来处理客户端连接和数据接收,以提高服务器的并发处理能力。
  2. 配置文件:将服务器配置(如端口号、最大客户端数等)放入配置文件中,方便管理和修改。
  3. 安全性:增加数据加密和身份验证机制,确保数据传输的安全性。

完整的改进后的服务器代码

将所有改进整合在一起,形成完整的改进后的服务器代码。

#include <enet/enet.h>
#include <iostream>
#include <vector>
#include <unordered_map>
#include <cstring>
#include <fstream>
#include <assimp/matrix4x4.h>

std::ofstream logFile("server_log.txt");

void Log(const std::string& message) {
    std::cout << message << std::endl;
    logFile << message << std::endl;
}

void ProcessBoneAnimationData(const std::vector<aiMatrix4x4>& boneTransforms);

std::vector<aiMatrix4x4> UnpackBoneTransforms(const std::vector<float>& packedData) {
    std::vector<aiMatrix4x4> transforms(packedData.size() / 16);

    for (size_t i = 0; i < transforms.size(); ++i) {
        aiMatrix4x4& transform = transforms[i];
        for (int j = 0; j < 4; ++j) {
            for (int k = 0; k < 4; ++k) {
                transform[j][k] = packedData[i * 16 + j * 4 + k];
            }
        }
    }

    return transforms;
}

std::unordered_map<ENetPeer*, std::string> clients;

void OnClientConnect(ENetPeer* peer) {
    std::string clientInfo = "Client " + std::to_string(peer->address.host) + ":" + std::to_string(peer->address.port);
    clients[peer] = clientInfo;
    Log(clientInfo + " connected.");
}

void OnClientDisconnect(ENetPeer* peer) {
    Log(clients[peer] + " disconnected.");
    clients.erase(peer);
}

void ForwardBoneAnimationData(ENetHost* server, ENetPeer* sender, const std::vector<float>& packedData) {
    for (auto& client : clients) {
        if (client.first != sender) {
            ENetPacket* packet = enet_packet_create(packedData.data(),
                                                    packedData.size() * sizeof(float),
                                                    ENET_PACKET_FLAG_RELIABLE);
            enet_peer_send(client.first, 0, packet);
        }
    }
    enet_host_flush(server);
}

void SendHeartbeat(ENetHost* server) {
    for (auto& client : clients) {
        ENetPacket* packet = enet_packet_create("heartbeat", strlen("heartbeat") + 1, ENET_PACKET_FLAG_RELIABLE);
        enet_peer_send(client.first, 1, packet);
    }
    enet_host_flush(server);
}

void ProcessCommand(const std::string& command, ENetPeer* peer) {
    if (command == "get_status") {
        std::string status = "Server is running";
        ENetPacket* packet = enet_packet_create(status.c_str(), status.size() + 1, ENET_PACKET_FLAG_RELIABLE);
        enet_peer_send(peer, 0, packet);
    }
}

int main(int argc, char** argv) {
    if (enet_initialize() != 0) {
        Log("An error occurred while initializing ENet.");
        return EXIT_FAILURE;
    }
    atexit(enet_deinitialize);

    ENetAddress address;
    ENetHost* server;
    ENetEvent event;

    address.host = ENET_HOST_ANY;
    address.port = 1234;

    server = enet_host_create(&address /* the address to bind the server host to */,
                              32      /* allow up to 32 clients and/or outgoing connections */,
                              2       /* allow up to 2 channels to be used, 0 and 1 */,
                              0       /* assume any amount of incoming bandwidth */,
                              0       /* assume any amount of outgoing bandwidth */);

    if (server == nullptr) {
        Log("An error occurred while trying to create an ENet server host.");
        return EXIT_FAILURE;
    }

    Log("Server started on port " + std::to_string(address.port));

    while (true) {
        while (enet_host_service(server, &event, 1000) > 0) {
            switch (event.type) {
                case ENET_EVENT_TYPE_CONNECT:
                    OnClientConnect(event.peer);
                    break;
                case ENET_EVENT_TYPE_RECEIVE:
                    Log("A packet of length " + std::to_string(event.packet->dataLength) +
                        " was received from " + clients[event.peer] +
                        " on channel " + std::to_string(event.channelID) + ".");

                    try {
                        // 解析骨骼动画数据
                        std::vector<float> packedData(event.packet->dataLength / sizeof(float));
                        std::memcpy(packedData.data(), event.packet->data, event.packet->dataLength);

                        // 检查是否是命令
                        std::string receivedData(reinterpret_cast<char*>(event.packet->data), event.packet->dataLength);
                        if (receivedData == "get_status") {
                            ProcessCommand(receivedData, event.peer);
                        } else {
                            std::vector<aiMatrix4x4> boneTransforms = UnpackBoneTransforms(packedData);

                            // 处理骨骼动画数据
                            ProcessBoneAnimationData(boneTransforms);

                            // 转发骨骼动画数据给其他客户端
                            ForwardBoneAnimationData(server, event.peer, packedData);
                        }
                    } catch (const std::exception& e) {
                        Log("Error processing packet: " + std::string(e.what()));
                    }

                    enet_packet_destroy(event.packet);
                    break;
                case ENET_EVENT_TYPE_DISCONNECT:
                    OnClientDisconnect(event.peer);
                    break;
                default:
                    break;
            }
        }

        // 定期发送心跳包
        SendHeartbeat(server);
    }

    enet_host_destroy(server);
    return EXIT_SUCCESS;
}

void ProcessBoneAnimationData(const std::vector<aiMatrix4x4>& boneTransforms) {
    // 这里可以将骨骼动画数据应用到服务器端的模型上
    // 或者将其转发给其他客户端
    for (const auto& transform : boneTransforms) {
        // 打印骨骼变换矩阵
        Log("Bone Transform:");
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                std::cout << transform[i][j] << " ";
            }
            std::cout << "\n";
        }
        std::cout << "\n";
    }
}
代码说明
命令处理:在接收数据时,首先检查是否是命令。如果是命令,则调用 ProcessCommand 函数进行处理;否则,解析骨骼动画数据并进行处理。
心跳机制:在主循环中定期调用 SendHeartbeat 函数,向所有客户端发送心跳包。
错误处理:在接收数据时,使用 try-catch 块捕获并处理可能的异常,确保服务器的稳定性。
进一步优化
多线程处理:可以使用多线程来处理客户端连接和数据接

好的,我们继续完成和优化服务器代码。

### 完整的改进后的服务器代码(续)

```cpp
1. **多线程处理**:可以使用多线程来处理客户端连接和数据接收,以提高服务器的并发处理能力。
2. **配置文件**:将服务器配置(如端口号、最大客户端数等)放入配置文件中,方便管理和修改。
3. **安全性**:增加数据加密和身份验证机制,确保数据传输的安全性。

### 完整的改进后的服务器代码

将所有改进整合在一起,形成完整的改进后的服务器代码。

```cpp
#include <enet/enet.h>
#include <iostream>
#include <vector>
#include <unordered_map>
#include <cstring>
#include <fstream>
#include <assimp/matrix4x4.h>
#include <thread>
#include <mutex>

std::ofstream logFile("server_log.txt");
std::mutex logMutex;

void Log(const std::string& message) {
    std::lock_guard<std::mutex> lock(logMutex);
    std::cout << message << std::endl;
    logFile << message << std::endl;
}

void ProcessBoneAnimationData(const std::vector<aiMatrix4x4>& boneTransforms);

std::vector<aiMatrix4x4> UnpackBoneTransforms(const std::vector<float>& packedData) {
    std::vector<aiMatrix4x4> transforms(packedData.size() / 16);

    for (size_t i = 0; i < transforms.size(); ++i) {
        aiMatrix4x4& transform = transforms[i];
        for (int j = 0; j < 4; ++j) {
            for (int k = 0; k < 4; ++k) {
                transform[j][k] = packedData[i * 16 + j * 4 + k];
            }
        }
    }

    return transforms;
}

std::unordered_map<ENetPeer*, std::string> clients;
std::mutex clientsMutex;

void OnClientConnect(ENetPeer* peer) {
    std::lock_guard<std::mutex> lock(clientsMutex);
    std::string clientInfo = "Client " + std::to_string(peer->address.host) + ":" + std::to_string(peer->address.port);
    clients[peer] = clientInfo;
    Log(clientInfo + " connected.");
}

void OnClientDisconnect(ENetPeer* peer) {
    std::lock_guard<std::mutex> lock(clientsMutex);
    Log(clients[peer] + " disconnected.");
    clients.erase(peer);
}

void ForwardBoneAnimationData(ENetHost* server, ENetPeer* sender, const std::vector<float>& packedData) {
    std::lock_guard<std::mutex> lock(clientsMutex);
    for (auto& client : clients) {
        if (client.first != sender) {
            ENetPacket* packet = enet_packet_create(packedData.data(),
                                                    packedData.size() * sizeof(float),
                                                    ENET_PACKET_FLAG_RELIABLE);
            enet_peer_send(client.first, 0, packet);
        }
    }
    enet_host_flush(server);
}

void SendHeartbeat(ENetHost* server) {
    std::lock_guard<std::mutex> lock(clientsMutex);
    for (auto& client : clients) {
        ENetPacket* packet = enet_packet_create("heartbeat", strlen("heartbeat") + 1, ENET_PACKET_FLAG_RELIABLE);
        enet_peer_send(client.first, 1, packet);
    }
    enet_host_flush(server);
}

void ProcessCommand(const std::string& command, ENetPeer* peer) {
    if (command == "get_status") {
        std::string status = "Server is running";
        ENetPacket* packet = enet_packet_create(status.c_str(), status.size() + 1, ENET_PACKET_FLAG_RELIABLE);
        enet_peer_send(peer, 0, packet);
    }
}

void HandleEvent(ENetHost* server, ENetEvent& event) {
    switch (event.type) {
        case ENET_EVENT_TYPE_CONNECT:
            OnClientConnect(event.peer);
            break;
        case ENET_EVENT_TYPE_RECEIVE:
            Log("A packet of length " + std::to_string(event.packet->dataLength) +
                " was received from " + clients[event.peer] +
                " on channel " + std::to_string(event.channelID) + ".");

            try {
                // 解析骨骼动画数据
                std::vector<float> packedData(event.packet->dataLength / sizeof(float));
                std::memcpy(packedData.data(), event.packet->data, event.packet->dataLength);

                              // 检查是否是命令
                std::string receivedData(reinterpret_cast<char*>(event.packet->data), event.packet->dataLength);
                if (receivedData == "get_status") {
                    ProcessCommand(receivedData, event.peer);
                } else {
                    std::vector<aiMatrix4x4> boneTransforms = UnpackBoneTransforms(packedData);

                    // 处理骨骼动画数据
                    ProcessBoneAnimationData(boneTransforms);

                    // 转发骨骼动画数据给其他客户端
                    ForwardBoneAnimationData(server, event.peer, packedData);
                }
            } catch (const std::exception& e) {
                Log("Error processing packet: " + std::string(e.what()));
            }

            enet_packet_destroy(event.packet);
            break;
        case ENET_EVENT_TYPE_DISCONNECT:
            OnClientDisconnect(event.peer);
            break;
        default:
            break;
    }
}

void ServerLoop(ENetHost* server) {
    ENetEvent event;
    while (true) {
        while (enet_host_service(server, &event, 1000) > 0) {
            HandleEvent(server, event);
        }

        // 定期发送心跳包
        SendHeartbeat(server);
    }
}

int main(int argc, char** argv) {
    if (enet_initialize() != 0) {
        Log("An error occurred while initializing ENet.");
        return EXIT_FAILURE;
    }
    atexit(enet_deinitialize);

    ENetAddress address;
    ENetHost* server;

    address.host = ENET_HOST_ANY;
    address.port = 1234;

    server = enet_host_create(&address /* the address to bind the server host to */,
                              32      /* allow up to 32 clients and/or outgoing connections */,
                              2       /* allow up to 2 channels to be used, 0 and 1 */,
                              0       /* assume any amount of incoming bandwidth */,
                              0       /* assume any amount of outgoing bandwidth */);

    if (server == nullptr) {
        Log("An error occurred while trying to create an ENet server host.");
        return EXIT_FAILURE;
    }

    Log("Server started on port " + std::to_string(address.port));

    std::thread serverThread(ServerLoop, server);
    serverThread.join();

    enet_host_destroy(server);
    return EXIT_SUCCESS;
}

void ProcessBoneAnimationData(const std::vector<aiMatrix4x4>& boneTransforms) {
    // 这里可以将骨骼动画数据应用到服务器端的模型上
    // 或者将其转发给其他客户端
    for (const auto& transform : boneTransforms) {
        // 打印骨骼变换矩阵
        Log("Bone Transform:");
        for (int i = 0; i < 4; ++i) {
            for (int j = 0; j < 4; ++j) {
                std::cout << transform[i][j] << " ";
            }
            std::cout << "\n";
        }
        std::cout << "\n";
    }
}

代码说明
多线程处理:使用 std::thread 创建一个新的线程来运行服务器主循环 ServerLoop,以提高并发处理能力。
互斥锁:使用 std::mutex 和 std::lock_guard 确保日志记录和客户端列表的线程安全。
事件处理:将事件处理逻辑封装在 HandleEvent 函数中,使代码结构更加清晰。
进一步优化
配置文件:将服务器配置(如端口号、最大客户端数等)放入配置文件中,方便管理和修改。
安全性:增加数据加密和身份验证机制,确保数据传输的安全性。
配置文件示例
可以使用 JSON 格式的配置文件来存储服务器配置,例如 config.json:

json
复制
{
    "port": 1234,
    "max_clients": 32,
    "heartbeat_interval": 1000
}
读取配置文件
可以使用一个 JSON 解析库(如 nlohmann/json)来读取配置文件:

cpp
复制
#include <nlohmann/json.hpp>
#include <fstream>

struct ServerConfig {
    uint16_t port;
    size_t maxClients;
    uint32_t heartbeatInterval;
};

ServerConfig LoadConfig(const std::string& filename) {
    std::ifstream file(filename);
    nlohmann::json json;
    file >> json;

    ServerConfig config
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你一身傲骨怎能输

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值