正确的3D游戏客户端服务器结构

发布于 2025-02-04 03:41:17 字数 4159 浏览 2 评论 0原文

我正在制作一个简单的3D多人游戏。在服务器端,我正在根据播放器位置和查看方向为每个客户端/播放器进行所有计算(应从用户输入中更新两者),然后我想向客户端发送所有结果的三角形/多边形为了在每个帧中绘制屏幕。我希望客户端在每个帧中发送到服务器,按下键盘上的按钮(因此,如果固定W和空间,它们的标志将设置为true并以这种方式发送到服务器),以及鼠标位置的更改。基于该输入,我更改了播放器在服务器端和相机外观方向上的位置。我现在设置的方式是客户端和服务器都是无限循环。

服务器在循环的每个帧/迭代中进行类似的操作:

  1. 向客户端发送一条消息,该消息告诉其更新鼠标(以下有关此信息的详细信息),
  2. 则该帧的双播放器速度为帧速度
  3. 如果按下特定键是基于DX的更新方向, dy(鼠标自上次帧以来移动多少)
  4. 根据按下键
  5. 检测键来计算播放器的下一个位置,如果在对象或墙壁
  6. 项目中,所有场景都基于外观方向(摄像机矩阵),
  7. 将返回播放向客户端的一条消息,告诉它以清除列表中的所有三角形
  8. 发送每个2D项目的三角形(坐标和颜色)的数据

,并且客户端在每个帧中都会执行此操作:

  1. 发送当前按下键的键,哪些键
  2. > code> Repaint()方法:
@Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        
        ArrayList<Triangle> trianglesToDraw = new ArrayList<>(updatedTriangles);
        trianglesToDraw.removeIf(Objects::isNull);
        
        g.setColor(Color.black);
        g.fillRect(0, 0, width, height);
        
        for (Triangle t : trianglesToDraw)
        {
            g.setColor(t.color);
            
            int[] xValues = {(int) t.points[0].x, (int) t.points[1].x, (int) t.points[2].x};
            int[] yValues = {(int) t.points[0].y, (int) t.points[1].y, (int) t.points[2].y};
            
            g.fillPolygon(new Polygon(xValues, yValues, 3));
        }
    }

值得注意的是,双方都有一个单独的线程,该线程负责从另一个线程处理消息。

在服务器端:

public void handleClientMessage(String message)
    {
        if (message.startsWith("keys: "))
        {
            String[] keyInfo = message.replaceAll(",", "").split(" ");
            
            for (int i = 0; i < keysPressed.length; i++)
            {
                keysPressed[i] = Boolean.parseBoolean(keyInfo[i + 1]);
            }
        }
        else if (message.startsWith("mouse_update: "))
        {
            String[] mouseInfo = message.split(" ");
            
            try
            {
                mouseDX = Float.parseFloat(mouseInfo[1]);
                mouseDY = Float.parseFloat(mouseInfo[2]);
            }
            catch (NumberFormatException e)
            {
                mouseDX = 0;
                mouseDY = 0;
            }
        }

在客户端:

    public void handleMessageFromServer(String message) throws IOException
    {
        if (message.contains("Update Mouse"))
        {
            mousePosition = MouseInfo.getPointerInfo().getLocation();
            
            int centerX = screenWidth / 2;
            int centerY = screenHeight / 2;
            
            robot.mouseMove(centerX, centerY);
            
            float dx = mousePosition.x - centerX;
            float dy = mousePosition.y - centerY;
            
            sendMessage("mouse_update: " + dx + " " + dy);
        }
        else if (message.equals("clear triangles"))
        {
            updatedTriangles.clear();
        }
        else if (message.startsWith("triangle"))
        {
            String[] triangleInfo = message.split(" ");
            Triangle t = new Triangle(
                new Vector(Float.parseFloat(triangleInfo[1]), Float.parseFloat(triangleInfo[2]), Float.parseFloat(triangleInfo[3])),
                new Vector(Float.parseFloat(triangleInfo[4]), Float.parseFloat(triangleInfo[5]), Float.parseFloat(triangleInfo[6])),
                new Vector(Float.parseFloat(triangleInfo[7]), Float.parseFloat(triangleInfo[8]), Float.parseFloat(triangleInfo[9])),
                new Color(Integer.parseInt(triangleInfo[10]), Integer.parseInt(triangleInfo[11]), Integer.parseInt(triangleInfo[12]))
            );
            
            updatedTriangles.add(t);
        }
    }

现在我的布局真的很不好,原因很多:

  1. 我几乎无法移动相机新的外观方向计算(考虑到其发送给客户端的原始请求后,计算仅在2-3 o(1)计算中才真正不现实。实际发生的是,这两者几乎总是0.0,因为它不会及时更新。
  2. 屏幕一直在闪烁,因为客户端与服务器没有同步,因此它在不接收服务器的所有三角形并在之前清除屏幕之前更新屏幕。
  3. 所有重要的计算都是在服务器端执行的,这在我的脑海中是有意义的,但这也意味着,由于服务器的资源有限来计算所有内容,因此每个人都会运行的客户端越慢。

目前我可能想到的问题可能还有更多问题,但这些问题是主要问题。 无论如何,我需要帮助来找出一个更好的布局,至少可以解决其中一些问题并防止更多的问题。我只是没有足够的经验,所以我真的不知道服务器 - 客户节目编程中的最佳实践。

I am making a simple 3D multiplayer game. In the server side I'm making all the calculations for each client/player in-game based on player position and look direction (both should be updated from user input), and then I want to send all resulting triangles/polygons to the client in order to draw to the screen in each frame. I want the client to send to the server in each frame the buttons pressed on the keyboard (so if W and Space are held down, their flags are set to true and sent that way to the server), and also the change in mouse position. Based on that input I change the position of the player in server-side and the look direction of the camera. The way I have it set up now is that both the client and server are infinite loops without delay.

The server goes something like this in each frame/iteration of the loop:

  1. Send a message to client that tells it to update mouse (more about that below)
  2. Double player speed for the frame if a specific key is pressed
  3. Update look direction based of dx and dy (how much the mouse moved since last frame)
  4. Calculate next position of player based on which keys are pressed
  5. Collision detection, push back player if inside an object or wall
  6. Project all scene triangles into 2D based on look direction (camera matrix)
  7. Send a message to client that tells it to clear all triangles from list
  8. Send the data of each 2D-projected triangle (coordinates and color)

And the client does this in each frame:

  1. Send which keys are currently pressed and which are not
  2. Call repaint() method:
@Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        
        ArrayList<Triangle> trianglesToDraw = new ArrayList<>(updatedTriangles);
        trianglesToDraw.removeIf(Objects::isNull);
        
        g.setColor(Color.black);
        g.fillRect(0, 0, width, height);
        
        for (Triangle t : trianglesToDraw)
        {
            g.setColor(t.color);
            
            int[] xValues = {(int) t.points[0].x, (int) t.points[1].x, (int) t.points[2].x};
            int[] yValues = {(int) t.points[0].y, (int) t.points[1].y, (int) t.points[2].y};
            
            g.fillPolygon(new Polygon(xValues, yValues, 3));
        }
    }

It is worth noting that each side has a separate thread which is responsible for handling messages from the other.

In the server side:

public void handleClientMessage(String message)
    {
        if (message.startsWith("keys: "))
        {
            String[] keyInfo = message.replaceAll(",", "").split(" ");
            
            for (int i = 0; i < keysPressed.length; i++)
            {
                keysPressed[i] = Boolean.parseBoolean(keyInfo[i + 1]);
            }
        }
        else if (message.startsWith("mouse_update: "))
        {
            String[] mouseInfo = message.split(" ");
            
            try
            {
                mouseDX = Float.parseFloat(mouseInfo[1]);
                mouseDY = Float.parseFloat(mouseInfo[2]);
            }
            catch (NumberFormatException e)
            {
                mouseDX = 0;
                mouseDY = 0;
            }
        }

In the client side:

    public void handleMessageFromServer(String message) throws IOException
    {
        if (message.contains("Update Mouse"))
        {
            mousePosition = MouseInfo.getPointerInfo().getLocation();
            
            int centerX = screenWidth / 2;
            int centerY = screenHeight / 2;
            
            robot.mouseMove(centerX, centerY);
            
            float dx = mousePosition.x - centerX;
            float dy = mousePosition.y - centerY;
            
            sendMessage("mouse_update: " + dx + " " + dy);
        }
        else if (message.equals("clear triangles"))
        {
            updatedTriangles.clear();
        }
        else if (message.startsWith("triangle"))
        {
            String[] triangleInfo = message.split(" ");
            Triangle t = new Triangle(
                new Vector(Float.parseFloat(triangleInfo[1]), Float.parseFloat(triangleInfo[2]), Float.parseFloat(triangleInfo[3])),
                new Vector(Float.parseFloat(triangleInfo[4]), Float.parseFloat(triangleInfo[5]), Float.parseFloat(triangleInfo[6])),
                new Vector(Float.parseFloat(triangleInfo[7]), Float.parseFloat(triangleInfo[8]), Float.parseFloat(triangleInfo[9])),
                new Color(Integer.parseInt(triangleInfo[10]), Integer.parseInt(triangleInfo[11]), Integer.parseInt(triangleInfo[12]))
            );
            
            updatedTriangles.add(t);
        }
    }

Now my layout is really bad for many reasons:

  1. I can barely move the camera because I expect the client to send updated dx and dy and the server to receive it and update it before it makes the new look direction calculation (which is really unrealistic considering the calculation happends only 2-3 O(1) calculations after the original request it sends to the client). What actually happens is that it's almost always 0.0 for both because it won't update in time.
  2. The screen keeps flickering because the client isn't synced with the server so it updates the screen without receiving all triangles from the server and clears the screen before.
  3. All of the important calculations are performed in server-side, which is something that makes sense in my mind, but it also means that the more clients there will be the slower it will run for everyone since the server has limited resources to calculate everything.

There are probably more problems that I can't think about at the moment but these are the main ones.
Anyways, I need help to figure out a better layout that will at the very least solve some of these problems and prevent some more future ones. I just don't have enough experience with this so I don't really know the best practices in server-client programming.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文