Edit:我将主板缩小到相关功能,以使代码较短。如果需要阅读任何其他代码以帮助理解,请说
我当前正在制作多人俄罗斯方Tetris游戏,该游戏启动了一个使用按钮的开始窗口,该按钮将创建两个窗口,该窗口都运行了一个Tetris的实例。EAFEAST具有自己的输入(箭头键和WASD)。两个实例单独工作并仅响应其各自的密钥输入,但是我必须在它们之间单击,以切换一个接收输入的人 - 我想修改程序以在两个阶段在两个阶段中动态切换基于收到密钥输入的两个阶段。 /**
* Defines actions to take for each input.
* @param tetr, the current Tetromino.
*/
public void moveOnKeyPress() {
if(scene != null) {
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
public void handle(KeyEvent event) {
if (player == 0) {
switch(event.getCode()) {
case UP:
tetr.rotate(MESH);
break;
case LEFT:
tetr.move(false, MESH);
break;
case RIGHT:
tetr.move(true, MESH);
break;
}
} else {
switch(event.getCode()) {
case W:
tetr.rotate(MESH);
break;
case A:
tetr.move(false, MESH);
break;
case D:
tetr.move(true, MESH);
break;
}
}
}
});
}
}
public final class StartBoard extends Application {
public static final int SIZE = 25;
public static final int XMAX = SIZE * 12;
public static final int YMAX = SIZE * 24;
private static Pane pane = new Pane();
private static Scene scene = new Scene(pane, XMAX, YMAX);
public static int highScore = 0;
private Stage stage;
private Button startButton = new Button("Start");
public static List<Stage> stageList = new ArrayList<Stage>();
public static List<MainBoard> gameList = new ArrayList<MainBoard>();
private AtomicInteger playerVal = new AtomicInteger(0);
static Text highScoreText = new Text("Highscore: " + highScore);
/**
* Launches the program and calls the start function.
* @param args
*/
public static void main(String[] args) {
launch(args);
}
/**
* Overridden function that's automatically called by the launch function.
* Sets up main stage and assigns an event to the start button.
*/
@Override
public void start(Stage stage) throws Exception {
this.stage = stage;
highScoreText.setStyle("-fx-font-size: 20; -fx-font-family: Arial;");
highScoreText.setY(50);
pane.getChildren().addAll(startButton, highScoreText);
stage.setScene(scene);
stage.setTitle("T E T R I S");
stage.show();
startButton.setOnAction(new EventHandler <ActionEvent>()
{
public void handle(ActionEvent event)
{
playerVal.set(0);
startTask();
}
});
}
/**
* Overwrites the global high score in the main window.
* @param score, higher score to overwrite current high score.
*/
public static void updateHighScore(int score) {
highScore = score;
highScoreText.setText("Highscore: " + highScore);
}
/**
* Creates a runnable task and threads to run it.
*/
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);
stageList.add(inner);
inner.show();
MainBoard localMainBoard = new MainBoard(val);
gameList.add(localMainBoard);
try {
localMainBoard.start(inner);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
}
直接回答您的基本问题:是的,可以根据按键按下来切换哪个窗口的重点。如果您只想在应用程序的窗口中至少有一个焦点时,则只想处理钥匙按下,这是相对简单的。一种选项是将密钥压制过滤器添加到该过程中涉及的每个窗口。例如:
,但是,如果您希望能够在应用程序的窗口都集中精力时根据按键按下窗口,那么它会变得更加复杂。您将不得不挂接平台的关键事件,以添加某种全球处理程序。一个可能对此有所帮助的库是
jnativehook。或者,您可以使用java本机接口(JNI)
,JAVA本机访问(JNA)或foreign函数&内存API(FFM)(java 22中添加了API)。
为何改变方式会引起问题,请参阅Stage#initModality(Modality)
throws:
IllegalStateException
-如果在舞台上可以看到此属性,则设置了该属性。
IllegalStateException
-如果这个阶段是主要阶段。换句话说,您无法通过Javafx框架将阶段PASSE的模式设置为
,也无法更改以后的任何阶段的方式。据我所知,这没有解决方案。您可能只需要接受任何窗口即可是模态。 注意,您可以将此开关对方的按压行为抽象为自己的类。例如:
Application#start(Stage)
COCUSSWITCHER.JAVA
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javafx.beans.Observable;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.event.WeakEventHandler;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.stage.Window;
/**
* A class that manages a list of registered windows and switches their focus based on specified key
* combinations.
*/
public class FocusSwitcher {
private final ObservableList<Window> registeredWindows =
FXCollections.observableArrayList(w -> new Observable[] {w.focusedProperty()});
private ObservableList<Window> unmodifiableRegisteredWindows;
private final Map<Window, Set<KeyCombination>> winToCombos = new HashMap<>();
private final Map<KeyCombination, Window> comboToWin = new HashMap<>();
private final EventHandler<KeyEvent> keyHandler = this::processKeyEvent;
private final WeakEventHandler<KeyEvent> weakKeyHandler = new WeakEventHandler<>(keyHandler);
public FocusSwitcher() {
registeredWindows.addListener(this::onRegisteredWindowsChanged);
}
/**
* Register the given window and associate it with the given key combinations. If the window is
* already registered, then the given key combinations will be added to the already associated key
* combinations. Duplicate key combinations are ignored.
*
* @param window the window to register
* @param combos the key combinations to associate with {@code window}
* @throws IllegalStateException if any key combination in {@code combos} is already associated
* with a different window
* @throws NullPointerException if {@code window}, {@code combos}, or any element of
* {@code combos} is {@code null}
*/
public void register(Window window, KeyCombination... combos) {
Objects.requireNonNull(window, "window");
Objects.requireNonNull(combos, "combos");
register(window, Arrays.asList(combos));
}
/**
* Register the given window and associate it with the given key combinations. If the window is
* already registered, then the given key combinations will be added to the already associated key
* combinations. Duplicate key combinations are ignored.
*
* @param window the window to register
* @param combos the key combinations to associate with {@code window}
* @throws IllegalStateException if any key combination in {@code combos} is already associated
* with a different window
* @throws NullPointerException if {@code window}, {@code combos}, or any element of
* {@code combos} is {@code null}
*/
public void register(Window window, Iterable<? extends KeyCombination> combos) {
Objects.requireNonNull(window, "window");
Objects.requireNonNull(combos, "combos");
for (var combo : combos) {
Objects.requireNonNull(combo, "'combos' contains null elements");
Window w;
if ((w = comboToWin.get(combo)) != null && w != window) {
throw new IllegalStateException(
"key combination already associated with different window: " + combo);
}
comboToWin.put(combo, window);
}
var comboSet = winToCombos.get(window);
if (comboSet == null) {
comboSet = new HashSet<>();
winToCombos.put(window, comboSet);
registeredWindows.add(window);
}
if (combos instanceof Collection<? extends KeyCombination> col) {
comboSet.addAll(col);
} else {
combos.forEach(comboSet::add);
}
}
/**
* Unregister the given window.
*
* @param window the window to unregister
*/
public void unregister(Window window) {
Objects.requireNonNull(window);
var comboSet = winToCombos.remove(window);
if (comboSet != null) {
comboSet.forEach(comboToWin::remove);
registeredWindows.remove(window);
}
}
private void processKeyEvent(KeyEvent event) {
var eventType = event.getEventType();
if (eventType == KeyEvent.KEY_PRESSED || eventType == KeyEvent.KEY_TYPED) {
comboToWin.entrySet().stream()
.filter(entry -> entry.getKey().match(event))
.findFirst()
.map(Map.Entry::getValue)
.filter(not(Window::isFocused))
.ifPresent(window -> {
event.consume();
window.requestFocus();
});
}
}
/**
* Returns the observable list of windows registered with this "focus switcher". The returned list
* will be unmodifiable.
*
* @return the unmodifiable observable list of registered windows
*/
public ObservableList<Window> getRegisteredWindows() {
if (unmodifiableRegisteredWindows == null) {
unmodifiableRegisteredWindows = FXCollections.unmodifiableObservableList(registeredWindows);
}
return unmodifiableRegisteredWindows;
}
private void onRegisteredWindowsChanged(ListChangeListener.Change<? extends Window> c) {
while (c.next()) {
if (c.wasUpdated()) {
var focused = getFocusedWindow();
for (var updated : c.getList().subList(c.getFrom(), c.getTo())) {
if (updated.isFocused()) {
focused = updated;
break;
}
}
if (focused != null && focused.isFocused()) {
setFocusedWindow(focused);
} else {
setFocusedWindow(null);
}
} else if (c.wasRemoved()) {
for (var removed : c.getRemoved()) {
removed.removeEventFilter(KeyEvent.ANY, weakKeyHandler);
if (getFocusedWindow() == removed) {
setFocusedWindow(null);
}
}
} else if (c.wasAdded()) {
for (var added : c.getAddedSubList()) {
added.addEventFilter(KeyEvent.ANY, weakKeyHandler);
if (added.isFocused()) {
setFocusedWindow(added);
}
}
}
}
}
/* **************************************************************************
* *
* Properties *
* *
****************************************************************************/
// -- focusedWindow property
/**
* The currently focused window from the registered windows. If none of the registered windows are
* focused then this property will contain {@code null}.
*/
private final ReadOnlyObjectWrapper<Window> focusedWindow =
new ReadOnlyObjectWrapper<>(this, "focusedWindow");
private void setFocusedWindow(Window focusedWindow) {
this.focusedWindow.set(focusedWindow);
}
public final Window getFocusedWindow() {
return focusedWindow.get();
}
public final ReadOnlyObjectProperty<Window> focusedWindowProperty() {
return focusedWindow.getReadOnlyProperty();
}
}
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class Main extends Application {
private final FocusSwitcher switcher = new FocusSwitcher();
@Override
public void start(Stage stageA) {
var labelA = new Label();
stageA.setScene(new Scene(new StackPane(labelA), 300, 150));
stageA.setTitle("Window A");
var labelB = new Label();
var stageB = new Stage();
stageB.setScene(new Scene(new StackPane(labelB), 300, 150));
stageB.setTitle("Window B");
switcher.register(stageA, KeyCombination.valueOf("shortcut+a"));
switcher.register(stageB, KeyCombination.valueOf("shortcut+b"));
switcher.focusedWindowProperty().subscribe(win -> {
String text;
if (win == null) {
text = "No window has focus!";
} else if (win instanceof Stage s) {
text = "\"" + s.getTitle() + "\" has focus!";
} else {
text = "\"" + win + "\" has focus!";
}
labelA.setText(text);
labelB.setText(text);
});
stageA.show();
stageB.show();
// place windows side-by-side
stageA.setX(stageA.getX() - (stageA.getWidth() / 2) + 5);
stageB.setX(stageB.getX() + (stageB.getWidth() / 2) + 5);
}
}
除了一个,您似乎在并发方面遇到了一些麻烦。请注意,您不得不从除javafx应用程序线程
以外的任何线程中修改实时场景图。您似乎知道,由于您将代码包装在Platform::runlater
中,但是您的设置使线程毫无意义。他们所做的就是安排使用
工作。另外,您在
runTask
如果您需要游戏循环,请考虑使用AnimationTimer
或
Timeline
。有关更多信息,请参见javafx周期背景任务。