将游戏部署到服务器会导致奇怪的行为

Deploying game to server results in strange behaviour

我使用 HTML5 WebSockets 和 java 作为后端开发了一个类似突破的游戏,并且最近将我的游戏部署在 Glassfish 服务器上,该服务器是 运行 在 20$ Digitalocean droplet 上(3GB 内存,2 个 CPU)。

在开发游戏时,我与 IntelliJ 和一位同事一起使用 Netbeans,在我们 PC 上的 Glassfish 服务器 运行 上部署我们的 WAR 文件时,一切都按预期工作。但是当在 droplet 上部署完全相同的 WAR 文件时,球的移动速度似乎快了 3 倍。

我尝试通过在虚拟机上安装与 Droplet 相同的 Ubuntu 服务器并执行与安装 OpenJDK、Glassfish 等相同的步骤来重现该问题,但在 VM 上它是工作正常。

其他带 1 CPU 的 droplet(已尝试 ubuntu 和 centos)会产生相同的问题。我想知道我遗漏的这个问题的原因可能是什么?

下面是我用于 connection/game 的代码:

WebSocket:

@ServerEndpoint("/singleplayer")
public class SingleplayerSocket {

    private static final Set<Session> PLAYERS = Collections.synchronizedSet(new HashSet<Session>());

    private Session session;
    private Gson gson;
    private Game game;

    private void sendMessage(String message) {
        try {
            for (Session player: PLAYERS) {
                if (player == session) {
                    player.getBasicRemote().sendText(message);
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    private void gameStart() {
        game.start();
        sendMessage("Game started");
    }

    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        gson = new Gson();
        PLAYERS.add(session);

        sendMessage("Connection established");
    }

    @OnMessage
    public void onMessage(String message) {
        if (session != null && session.isOpen()) {
            String messageType = gson.fromJson(message, MessageType.class).getMessage();

            switch (messageType) {

                case "gameSetup":
                    gameSetup(message);
                    break;

                case "gameStart":
                    gameStart();
                    break;
            }
        }
    }

    @OnClose
    public void onClose(Session session) {
        PLAYERS.remove(session);
        this.session = null;
    }
}

游戏class 球的移动方法如下:

        public class Game implements Runnable {

        private final int TARGET_FPS = 60;
        private final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;

        private volatile boolean gameRunning;
        private volatile boolean gamePaused;

        private Session session;
        private Thread thread;
        private Gson gson;

        public Game(Session session, int width, int height, String difficulty) {
            this.session = session;
            this.WIDTH = width;
            this.HEIGHT = height;
            gson = new Gson();
            timer = new Timer();

            setup(difficulty);
        }

        private void setGameRunning(boolean gameRunning) {
            this.gameRunning = gameRunning;
        }

        private void update(double delta) {
            ball.move(delta);
            collisionDetectionWalls();
            collisionDetectionPaddle();
            collisionDetectionBricks();
        }

        public void start() {
            thread = new Thread(this);
            thread.start();
            setGameRunning(true);
        }

        public void stop() {
            setGameRunning(false);
        }

        private void end(boolean won) {
            updateScore();
            sendGameEnd(won);
            stop();
        }

        private void sendMessage(String message) {
            try {
                session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        private void sendGameUpdate() {
            GameUpdateData data = new GameUpdateData(paddle, ball, bricks);
            GameUpdateResponse response = new GameUpdateResponse("gameUpdate", data);
            sendMessage(gson.toJson(response));
        }

        @Override
        public void run() {
            long lastLoopTime = System.nanoTime();
            long lastFpsTime = 0;

            while (gameRunning) {
                long currentTime = System.nanoTime();
                long updateLength = currentTime - lastLoopTime;
                lastLoopTime = currentTime;
                double delta = updateLength / ((double) OPTIMAL_TIME);

                lastFpsTime += updateLength;
                if (lastFpsTime >= 1000000000) {
                    lastFpsTime = 0;
                }

                if (!gamePaused) {
                    update(delta);
                    sendGameUpdate();
                }

                try {
                    long sleepTime = (System.nanoTime() - lastLoopTime + OPTIMAL_TIME) / 1000000;
                    Thread.sleep(sleepTime);
                } catch (InterruptedException e) {
                }
            }
        }
    }

public class Ball {
    public void move(double delta) {
        if (isLaunched()){
            double trigoX = Math.cos(angle);
            double trigoY = Math.sin(angle);

            x += trigoX * velocity * delta;
            y += trigoY * velocity * delta;
        }
    }
}

也许你可以尝试 System.currentTimeMillis() 因为 System.nanoTime() 不是线程节省。

参考: Is System.nanoTime() consistent across threads?

在开发多人游戏时,我偶然发现了几个问题,因此决定在 Javascript 中使用 console.log() 检查几个变量。我注意到单击按钮时游戏启动了两次,并通过添加服务器端检查来解决问题,以防止单击 'Play' 按钮时多次启动游戏。

private void gameStart() {
    if (!game.isGameRunning()) {
        game.start();
        sendMessage("Game started");
    }
}

现在球速正常。