admin管理员组

文章数量:1355625

I'm currently working on a concurrent implementation of multiplayer Tetris in JavaFX where each game is run on a separate Stage. Each game has its own set of inputs so you can play simultaneously as focus is switched between the two stages whenever an appropriate input for one is received. For the most part, this works well - thanks to a very helpful answer on a previous question: Is there a way to switch the focus window upon a key press in JavaFX I was able to modify my input handling to include focus switching like I wanted. This is my current implementation:

public void handleInput(KeyEvent event) {
    switch(event.getCode()) {
        case UP:
            if (!StartBoard.stageList.get(0).isFocused()) {
                StartBoard.stageList.get(0).requestFocus();
                StartBoard.gameList.get(0).handleInput(event);
            } else {
                tetr.rotate(MESH);
            }
            break;
        case LEFT:
            if (!StartBoard.stageList.get(0).isFocused()) {
                StartBoard.stageList.get(0).requestFocus();
                StartBoard.gameList.get(0).handleInput(event);
            } else {
            tetr.move(false, MESH);
            }
            break;
        case RIGHT:
            if (!StartBoard.stageList.get(0).isFocused()) {
                StartBoard.stageList.get(0).requestFocus();
                StartBoard.gameList.get(0).handleInput(event);
            } else {
            tetr.move(true, MESH);
            }
            break;
        case DOWN:
            if (!StartBoard.stageList.get(0).isFocused()) {
                StartBoard.stageList.get(0).requestFocus();
                StartBoard.gameList.get(0).handleInput(event);
            } else {
            tetr.slam(MESH);
            }

With there being alternate cases for WASD that apply the same thing but for stageList.get(1) and gameList.get(1). Where the items in gameList are of type MainBoard - the class in which this method resides that act as the classes for running each individual game. Tetr is the Tetromino class which handles methods for the individual Tetris blocks. My issue is that the down input crashes the game every time, by this I mean it causes both windows to not respond for a few seconds before closing, leaving only the startboard open, and I'm unsure why. The method tetr.slam worked just fine before I implemented the dynamic focus switching and was instead manually switching focus by clicking. Additionally, the equivalent S input for the other MainBoard still works fine. I've included the method for tetr.slam below even if I don't believe that's where the issue lies:

public void slam(int MESH[][]) {
    while (!fall(MESH)) {
        fall(MESH);
    }
}

And the definitions for MESH and fall(MESH):

public int[][] MESH  = new int [StartBoard.XMAX/StartBoard.SIZE][StartBoard.YMAX/StartBoard.SIZE];

public boolean fall(int MESH[][]) {
    for (Rectangle i: rectList) {
        if (i.getY() + StartBoard.SIZE >= StartBoard.YMAX ||
                MESH[(int)i.getX()/StartBoard.SIZE][((int)i.getY() + StartBoard.SIZE)/StartBoard.SIZE] == 1) {
            return true;
        }
    }
    for (Rectangle i: rectList) {
        i.setY(i.getY() + StartBoard.SIZE);
    }
    return false;
}

Where MESH is a variable of MainBoard which it passes into its Tetromino. I'm really stumped on the issue here, if anyone has any ideas I would be very appreciative. Edit: I'm adding in the StartBoard code which creates the two threads on which the two MainBoards are created and run, in case concurrency is the issue.

startButton.setOnAction(new EventHandler <ActionEvent>() 
    {
        public void handle(ActionEvent event) 
        {
            playerVal.set(0);
            startTask();
        }
    });
 
}
public void startTask() {
    //Creates runnable
    Runnable task = new Runnable() {
        
        public void run() {
            runTask();
        }
        
    };
    
    //Run task in bgThread
    Thread bgThread = new Thread(task);
    Thread bgThread2 = new Thread(task);
    //Terminate running thread if application exists
    bgThread.setDaemon(true);
    bgThread2.setDaemon(true);
    //Start thread
    bgThread.start();
    bgThread2.start();
    
}

/**
 * Sets up new stages for a Tetris game.
 */
public void runTask() {
    Platform.runLater(new Runnable() 
    {
        @Override
        public void run() 
        {
            Pane localPane = new Pane();
            Scene localScene = new Scene(localPane, XMAX + 150, YMAX);
            Stage inner = new Stage(){{setScene(localScene); }};
            inner.initOwner(stage);
            //inner.initModality(Modality.WINDOW_MODAL);
            
            int val = playerVal.getAndIncrement();
            
            inner.setX((XMAX + 150) * val );
            inner.setY(YMAX / 2 - 150);
            stageList.add(inner);
            inner.show();
            
            MainBoard localMainBoard = new MainBoard(val);
            gameList.add(localMainBoard);
            System.out.println("*****START******");
            
            try {
                localMainBoard.start(inner);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        
       
    });
}      

}

I'm currently working on a concurrent implementation of multiplayer Tetris in JavaFX where each game is run on a separate Stage. Each game has its own set of inputs so you can play simultaneously as focus is switched between the two stages whenever an appropriate input for one is received. For the most part, this works well - thanks to a very helpful answer on a previous question: Is there a way to switch the focus window upon a key press in JavaFX I was able to modify my input handling to include focus switching like I wanted. This is my current implementation:

public void handleInput(KeyEvent event) {
    switch(event.getCode()) {
        case UP:
            if (!StartBoard.stageList.get(0).isFocused()) {
                StartBoard.stageList.get(0).requestFocus();
                StartBoard.gameList.get(0).handleInput(event);
            } else {
                tetr.rotate(MESH);
            }
            break;
        case LEFT:
            if (!StartBoard.stageList.get(0).isFocused()) {
                StartBoard.stageList.get(0).requestFocus();
                StartBoard.gameList.get(0).handleInput(event);
            } else {
            tetr.move(false, MESH);
            }
            break;
        case RIGHT:
            if (!StartBoard.stageList.get(0).isFocused()) {
                StartBoard.stageList.get(0).requestFocus();
                StartBoard.gameList.get(0).handleInput(event);
            } else {
            tetr.move(true, MESH);
            }
            break;
        case DOWN:
            if (!StartBoard.stageList.get(0).isFocused()) {
                StartBoard.stageList.get(0).requestFocus();
                StartBoard.gameList.get(0).handleInput(event);
            } else {
            tetr.slam(MESH);
            }

With there being alternate cases for WASD that apply the same thing but for stageList.get(1) and gameList.get(1). Where the items in gameList are of type MainBoard - the class in which this method resides that act as the classes for running each individual game. Tetr is the Tetromino class which handles methods for the individual Tetris blocks. My issue is that the down input crashes the game every time, by this I mean it causes both windows to not respond for a few seconds before closing, leaving only the startboard open, and I'm unsure why. The method tetr.slam worked just fine before I implemented the dynamic focus switching and was instead manually switching focus by clicking. Additionally, the equivalent S input for the other MainBoard still works fine. I've included the method for tetr.slam below even if I don't believe that's where the issue lies:

public void slam(int MESH[][]) {
    while (!fall(MESH)) {
        fall(MESH);
    }
}

And the definitions for MESH and fall(MESH):

public int[][] MESH  = new int [StartBoard.XMAX/StartBoard.SIZE][StartBoard.YMAX/StartBoard.SIZE];

public boolean fall(int MESH[][]) {
    for (Rectangle i: rectList) {
        if (i.getY() + StartBoard.SIZE >= StartBoard.YMAX ||
                MESH[(int)i.getX()/StartBoard.SIZE][((int)i.getY() + StartBoard.SIZE)/StartBoard.SIZE] == 1) {
            return true;
        }
    }
    for (Rectangle i: rectList) {
        i.setY(i.getY() + StartBoard.SIZE);
    }
    return false;
}

Where MESH is a variable of MainBoard which it passes into its Tetromino. I'm really stumped on the issue here, if anyone has any ideas I would be very appreciative. Edit: I'm adding in the StartBoard code which creates the two threads on which the two MainBoards are created and run, in case concurrency is the issue.

startButton.setOnAction(new EventHandler <ActionEvent>() 
    {
        public void handle(ActionEvent event) 
        {
            playerVal.set(0);
            startTask();
        }
    });
 
}
public void startTask() {
    //Creates runnable
    Runnable task = new Runnable() {
        
        public void run() {
            runTask();
        }
        
    };
    
    //Run task in bgThread
    Thread bgThread = new Thread(task);
    Thread bgThread2 = new Thread(task);
    //Terminate running thread if application exists
    bgThread.setDaemon(true);
    bgThread2.setDaemon(true);
    //Start thread
    bgThread.start();
    bgThread2.start();
    
}

/**
 * Sets up new stages for a Tetris game.
 */
public void runTask() {
    Platform.runLater(new Runnable() 
    {
        @Override
        public void run() 
        {
            Pane localPane = new Pane();
            Scene localScene = new Scene(localPane, XMAX + 150, YMAX);
            Stage inner = new Stage(){{setScene(localScene); }};
            inner.initOwner(stage);
            //inner.initModality(Modality.WINDOW_MODAL);
            
            int val = playerVal.getAndIncrement();
            
            inner.setX((XMAX + 150) * val );
            inner.setY(YMAX / 2 - 150);
            stageList.add(inner);
            inner.show();
            
            MainBoard localMainBoard = new MainBoard(val);
            gameList.add(localMainBoard);
            System.out.println("*****START******");
            
            try {
                localMainBoard.start(inner);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        
       
    });
}      

}

Share Improve this question edited Apr 1 at 18:11 Kyle Nickson asked Mar 28 at 10:19 Kyle NicksonKyle Nickson 11 silver badge1 bronze badge 7
  • 1 ”the down input crashes the game” -> what does this mean? What actually happens? Is there an exception? Does it just become unresponsive? – jewelsea Commented Mar 28 at 11:08
  • 2 My guess is that your slam method while (!fall(MESH)) goes into an infinite loop, but that is just a guess. – jewelsea Commented Mar 28 at 12:12
  • 2 You list java.util.concurrent as a tag, so threading could be an issue, but there is no concurrent code in your question. – jewelsea Commented Mar 28 at 12:13
  • 2 "Both mainBoards are run in their own threads by a StartBoard" -> JavaFX only has one user-accessible thread, the JavaFX application thread. If you want debugging help, provide a minimal reproducible example. That would be minimal code that only replicates the issue (not your whole app), and does so via copy and paste with no change or addition. Without that, you are unlikely to get further assistance. – jewelsea Commented Mar 28 at 17:32
  • 1 Why introduce any kind of concurrency and multiple stages into such an app? Only one player can input at a time, as this has been described, and only one Stage is ever active. So there is no theoretical advantage to introducing the complexity of two Stages and any kind of focus, or concurrency issues. – DaveB Commented Mar 30 at 12:40
 |  Show 2 more comments

1 Answer 1

Reset to default 0

I remember your original question, and I kinda questioned how you were using Threads. You appeared to create a thread to do background work, but in that thread, all of your code was in Platform.runLater. I would suggest avoiding threads. I think using something from the Animation API is better.

I created this example that hopefully can help you. This example uses two different TimeLines as game loops. It adds a listener to the stages to handle changing the focus from one stage to the other. After starting the two games, you can press CTRL +A to switch between both stages. You can also press the up and down arrows to speed up or slow down the ball, depending on which stage has focus.

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.security.Key;
import java.util.HashSet;
import java.util.Set;

public class App extends Application {

    @Override
    public void start(Stage primaryStage)
    {
        ObservableList<Screen> screens = Screen.getScreens();
        Rectangle2D bounds = screens.getFirst().getVisualBounds();

        VBox root1 = setUpScene();
        Scene scene1 = new Scene(root1, 800, 800);
        primaryStage.setX((bounds.getMaxX() / 2.0) - scene1.getWidth() - 10);
        primaryStage.setY(100);
        primaryStage.setScene(scene1);
        primaryStage.show();

        VBox root2 = setUpScene();
        var scene2 = new Scene(root2, 800, 800);
        Stage stage2 = new Stage();
        stage2.setX((bounds.getMaxX() / 2.0) + 10);
        stage2.setY(100);
        stage2.setScene(scene2);
        stage2.show();

        EventHandler<KeyEvent> handler = e -> {
            if (e.isControlDown()) {
                if (e.getCode() == KeyCode.A)
                {
                    if(primaryStage.isFocused())
                    {
                        stage2.requestFocus();
                        root2.setStyle("-fx-background-color: black;");
                        root2.requestFocus();

                        root1.setStyle(null);
                    }
                    else
                    {
                        primaryStage.requestFocus();
                        root1.setStyle("-fx-background-color: black;");
                        root1.requestFocus();

                        root2.setStyle(null);
                    }
                }
            }
        };
        // add key-pressed handler to all involved windows
        primaryStage.addEventFilter(KeyEvent.KEY_PRESSED, handler);
        stage2.addEventFilter(KeyEvent.KEY_PRESSED, handler);

        Platform.runLater(primaryStage::requestFocus);
    }

    public static void main(String[] args) {
        launch();
    }

    public void moveBall(Circle ball, DoubleProperty ballVelocity)
    {
        ball.setCenterX(ball.getCenterX() + ballVelocity.getValue());
    }

    public void checkWallCollision(Circle ball, DoubleProperty leftBound, DoubleProperty rightBound, DoubleProperty ballVelocity)
    {
        //If the ball hits the wall, change its direction
        if(ball.getCenterX() + ball.getRadius() >= rightBound.getValue() || ball.getCenterX() - ball.getRadius() <= leftBound.getValue())
        {
            ballVelocity.setValue(ballVelocity.getValue() * -1);
        }
    }


    public VBox setUpScene()
    {
        VBox root = new VBox();

        Circle ball = new Circle(100, 375, 10, Color.GREEN);

        Timeline gameLoop;

        DoubleProperty leftBound = new SimpleDoubleProperty(0);
        DoubleProperty rightBound = new SimpleDoubleProperty(750);
        DoubleProperty ballVelocity = new SimpleDoubleProperty(5);

        Pane middleRoot = new Pane(ball);
        rightBound.bind(middleRoot.widthProperty());
        middleRoot.setStyle("-fx-background-color: yellow;");
        middleRoot.setPrefSize(750, 750);

        gameLoop = new Timeline(
                new KeyFrame(Duration.millis(25), (ActionEvent event) -> {
                    System.out.println(ballVelocity);
                    moveBall(ball, ballVelocity);
                    checkWallCollision(ball, leftBound, rightBound, ballVelocity);
                }));
        gameLoop.setCycleCount(Timeline.INDEFINITE);


        Button btnStart = new Button("Start");
        btnStart.setOnAction((event) -> {
            if(gameLoop.getStatus() == Timeline.Status.STOPPED)
            {
                gameLoop.play();
            }
        });

        Button btnReset = new Button("Reset");
        btnReset.setOnAction((event) -> {
            if(gameLoop.getStatus() == Timeline.Status.RUNNING)
            {
                gameLoop.stop();
                ball.setCenterX(100);

            }
        });

        HBox bottomRoot = new HBox(btnStart, btnReset);

        root.getChildren().add(middleRoot);
        root.getChildren().add(bottomRoot);
        root.setOnKeyPressed(event -> {
            if (root.isFocused())
            {
                if(event.getCode() == KeyCode.UP && Math.abs(ballVelocity.getValue()) < 20)
                {
                    if(ballVelocity.getValue() > 0)
                    {
                        ballVelocity.setValue(ballVelocity.get() + 1);
                    }
                    else if(ballVelocity.getValue() < 0)
                    {
                        ballVelocity.setValue(ballVelocity.get() - 1);
                    }
                }
                else if(event.getCode() == KeyCode.DOWN && Math.abs(ballVelocity.getValue()) > 1)
                {
                    if(ballVelocity.getValue() > 0)
                    {
                        ballVelocity.setValue(ballVelocity.get() - 1);
                    }
                    else if(ballVelocity.getValue() < 0)
                    {
                        ballVelocity.setValue(ballVelocity.get() + 1);
                    }
                }
            }
        });

        return root;
    }
}

本文标签: javafxMethod Breaks After Implementing Focus SwitchingStack Overflow