如何让我的两个 类 一起工作?
How do I make my two classes function together?
我正在开发一个基于文本的视频游戏并为其创建了一个 GUI,但是,我在让我的两个 classes 一起工作时遇到了问题。我的 GUI class 中有一个文本字段,它接收来自用户的输入,我想将其发送到我的播放器 class 以便它可以比较它并执行适当的方法。我对编程还是很陌生,所以我希望你们中的一些人能够帮助我。请原谅可怕的代码。
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class GUI extends Player {
String input;
JLabel message;
public GUI() {
JFrame frame = new JFrame("SPACE GAME");
ImageIcon image = new ImageIcon("rocket.png");
frame.setIconImage(image.getImage());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1200, 800);
frame.setLocationRelativeTo(null);
frame.setFocusable(true);
frame.getContentPane();
JPanel panel = new JPanel();
JTextField commandLine = new JTextField(30);
JLabel message = new JLabel();
frame.add(panel);
panel.setBackground(Color.black);
panel.setLayout(null);
panel.setBorder(BorderFactory.createEmptyBorder(1000, 1000 ,1000 ,1000));
commandLine.setBackground(Color.WHITE);
commandLine.setBounds(5, 730, 300, 30);
commandLine.setBorder(BorderFactory.createLineBorder(Color.GRAY, 3));
commandLine.setFont(new Font("Zig", Font.PLAIN, 18));
commandLine.setForeground(Color.GREEN);
commandLine.setBackground(Color.BLACK);
commandLine.setCaretColor(Color.GREEN);
commandLine.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String input = commandLine.getText();
}
});
commandLine.setVisible(false);
message.setForeground(Color.GREEN);
message.setText("Welcome to SPACE GAME! Press any key to start.");
message.setBounds(5,665, 1000, 100);
message.setFont(new Font("Courier", Font.PLAIN, 18));
panel.add(message);
panel.add(commandLine);
frame.setVisible(true);
frame.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
commandLine.setVisible(true);
commandLine.requestFocusInWindow();
message.setText("Type \"help\" for help");
}
@Override
public void keyReleased(KeyEvent e) {
}
});
}
public JLabel getMessage(JLabel message) {
return message;
}
}
import javax.swing.*;
import java.util.Scanner;
public class Player {
//Attributes
private Room currentRoom;
Player player;
JLabel message;
// Handles player movement and commands
public void move() {
player = new Player();
Map map = new Map();
GUI gui = new GUI();
player.currentRoom = map.room1;
Scanner input = new Scanner(System.in);
System.out.println("You are in " + player.currentRoom.getName() + ". " + player.currentRoom.getRoomDescription());
System.out.println("Type \"help\" to get help");
//Commands
boolean isGameRunning = true;
while (isGameRunning) {
String goMessage = input.nextLine();
goMessage = goMessage.toLowerCase();
switch (goMessage) {
case "go north", "north", "go n", "n": goNorth(); break;
case "go east", "east", "go e", "e": goEast(); break;
case "go south", "south", "go s", "s": goSouth(); break;
case "go west", "west", "go w", "w": goWest(); break;
case "exit": isGameRunning = false; break;
case "look":
System.out.println(player.currentRoom.getRoomDescription());
break;
case "help":
System.out.println("""
"go (north, south, east, west)" to choose a direction to go.
"look" gives you a description of the room.
"exit" stops the game.""");
break;
default:
System.out.println("Unknown command");
break;
}
}
}
public void goNorth() {
if (player.currentRoom.getNorthRoom() != null) {
player.currentRoom = player.currentRoom.getNorthRoom();
System.out.println("You are in " + player.currentRoom.getName() + ". " + player.currentRoom.getRoomDescription());
} else {
System.out.println("You cannot go that way");
}
}
public void goEast() {
if (player.currentRoom.getEastRoom() != null) {
player.currentRoom = player.currentRoom.getEastRoom();
System.out.println("You are in " + player.currentRoom.getName() + ". " + player.currentRoom.getRoomDescription());
} else {
System.out.println("You cannot go that way");
}
}
public void goSouth() {
if (player.currentRoom.getSouthRoom() != null) {
player.currentRoom = player.currentRoom.getSouthRoom();
System.out.println("You are in " + player.currentRoom.getName() + ". " + player.currentRoom.getRoomDescription());
} else {
System.out.println("You cannot go that way");
}
}
public void goWest() {
if (player.currentRoom.getWestRoom() != null) {
player.currentRoom = player.currentRoom.getWestRoom();
System.out.println("You are in " + player.currentRoom.getName() + ". " + player.currentRoom.getRoomDescription());
} else {
System.out.println("You cannot go that way");
}
}
}
由于您已经使 GUI 扩展了播放器,您需要做的就是从输入字段上的 ActionListener 调用所需的函数。
commandLine.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String input = commandLine.getText();
if (input == "go north") {
GoNorth();
}
// .....
}
});
然而,事情相当混乱。对于初学者来说,没有真正的理由让 GUI 扩展播放器。我建议您将 GUI class 与播放器分开。这样,您就不需要使用扫描仪或打印,而是在 GUI class.
中收集所有 GUI 功能
例如:
public void goNorth() {
if (player.currentRoom.getNorthRoom() != null) {
player.currentRoom = player.currentRoom.getNorthRoom();
gui.print("You are in " + player.currentRoom.getName() + ". " + player.currentRoom.getRoomDescription())
} else {
gui.print("You cannot go that way");
}
}
您需要在 GUI class 中实现一个打印功能,它会向消息框添加一行文本,但我相信您一开始就计划这样做。
在 GUI class 中,您将获得对播放器的引用,因此读取输入字段将如下所示:
commandLine.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String input = commandLine.getText();
switch(input) {
case "go north":
player.goNorth();
break;
...
}
}
});
您的代码中有一些好东西以及几个可能妨碍您将 GUI 与当前的控制台程序代码混搭的主要问题,这些问题包括:
- 您确实将您的程序逻辑分离到单独的 class 中,例如播放器、地图和房间 class 中,这是一件非常好的事情,但您仍然可以做更多的逻辑分离,这样做可以简化您的代码,使其更容易调试和增强。
- Player class 包含一个 Player 实例,我不确定为什么。外层主 class 可能应该从
Player
重命名为 Game
class,而玩家 class 应该是一个完全独立的 class,它保存 单个 玩家实例的状态,并且不应像您正在做的那样创建自己的实例。关键是要努力“oop-ify”你的代码,尤其是代码的“模型”部分,即持有非用户界面程序逻辑的代码,使其更面向对象,因为这将使它变得更更容易让您将 GUI 或任何其他接口连接到您的模型。
- 您的模型(再次命名为 Player)包含用户界面 (UI) 代码:其中包含 Scanner 输入代码和 println 语句。这是有问题的,尤其是现在您要增强程序并使其与 GUI 一起工作。首先,您必须做的关键事情是在模型 class 之外分离用户界面代码(主要是从用户那里获取输入并向用户显示输出的代码),也就是说,再次, class 保存程序逻辑。
- 您的播放器 class 有很多非播放器代码使其混乱。一个基本的 OOPs 原则是 class 应该有单一职责(称为“单一职责规则”),所以你的 Player class 应该只有封装 Player 状态所需的代码(例如名称、房间,也许还有其他属性,例如力量、武器、生命值、智力……)和玩家行为(允许玩家与其他 classes 交互的方法),或多或少都没有。
- 按照同样的思路,似乎所有程序数据和行为似乎都硬连接到驱动程序的这个单一主播放器 class 中。这是非常有限的,并且会妨碍您创建包含许多房间、房间中的物品以及诸如此类的东西的丰富游戏环境的能力。分而治之——从主程序中获取该信息并将其放入符合 OOP 的 classes,包括一个 Room class,它具有它可能包含的项目的字段,它有自己的知识与其他房间的连接。
- 您的 GUI 程序扩展了 Player 对象。这是一个主要问题,因为这个程序结构没有通过继承的基本测试,“is-a”测试:逻辑上 GUI 是一种更具体的播放器类型(很像狗是更具体的Animal 类型,一个通过“is-a”测试的结构)?不,它不是,虽然这种区别看起来悬而未决,但由于这种继承,您可以尝试在 GUI class 中使用 Player 字段,但如果您尝试这样做,代码将无法正常工作。删除此继承,而是尝试通过“组合”连接 classes,其中一个 class 持有另一个的实例,而不是扩展另一个。因此,例如,GUI 可能包含一个 Player 变量而不是从 Player.
扩展
底线:
在尝试添加 GUI
之前, 您需要完全“OOP-ify”您的代码
为了使程序能够与 GUI 很好地结合在一起,我建议使用 class 都具有 单一责任,class 很容易可测试 隔离(远离 GUI 或任何用户界面)。从第一原则开始,程序将更容易构建。
因此,例如,class可以考虑用于冒险游戏的元素包括:
- 方向 class,或者更好的方向 enum.
这将封装“方向”的概念,并将用于游戏中的对象以了解它们的前进方向,并能够使用常量而不是字符串将其传达给其他对象。枚举的用户将允许编译器检查方向是否被正确使用,因为如果你使用字符串,比如“West”,编译器不会自动知道你是否输入错误的字符串并改为使用“Best”。
像这样简单的东西,例如:
public enum Direction {
NORTH, EAST, SOUTH, WEST
}
- 一个游戏室class
这将保存信息,告诉它它在房间网格中的位置、名称和描述属性的字符串,以及游戏玩家字段或 List
游戏玩家(如果允许超过一个的话),也许包含如下代码的内容:....
public class GameRoom {
// map with connections to other game rooms
private Map<Direction, GameRoom> connections = new HashMap<Direction, GameRoom>();
// location information
private int x;
private int y;
// identifying information
private String roomName;
private String description;
// holds any player objects that may be in the room
private List<GamePlayer> playersInRoom = new ArrayList<>();
class 将有适当的构造函数以及 getter 和 setter 方法,还有:
允许游戏将玩家添加到房间的addPlayer(...)
方法:
public void addPlayer(GamePlayer gamePlayer) {
playersInRoom.add(gamePlayer);
gamePlayer.setCurrentRoom(this); // we'll get to this later
}
一种public boolean move(...)
方法,可以将房间内的玩家移动到相连的房间。它首先检查房间是否确实包含被移动的玩家,然后检查房间是否与请求方向上的另一个房间有连接。如果任一个为 false,则方法 returns false 让调用代码表明尝试的移动失败。否则,如果允许移动,则 returns true:
public boolean move(GamePlayer gamePlayer, Direction direction) {
// if the room doesn't currently hold this player
if (!playersInRoom.contains(gamePlayer)) {
return false; // invalid move request
}
// if the room doesn't have a connecting room in the requested direction
if (!connections.containsKey(direction)) {
return false; // invalid move request
}
// otherwise, we're good
playersInRoom.remove(gamePlayer);
connections.get(direction).addPlayer(gamePlayer);
return true;
}
- 一个游戏玩家class
这个 class 将包含多个属性,例如名称的字符串和当前房间的 GameRoom 字段、构造函数、getter 和 setter 方法。它的声明和字段可能类似于:
public class GamePlayer {
private String name;
private GameRoom currentRoom;
//.... other properties
它也应该有一个移动方法调用 currentRoom GameRoom 的移动方法和 returns 相同的布尔值,一个结果告诉调用代码移动是否有效且成功:
public boolean move(Direction direction) {
return currentRoom.move(this, direction);
}
- 一个游戏模型class:
这将保存当前游戏的状态,玩家字段,保存所有房间的数据结构。它可以有自己的移动方法.......
只有在创建符合 OOP 的 classes 之后,您才应该进入下一步:创建 GUI 或“视图”class/classes。这个 class 可以包含一个 GameModel 实例,并将负责 1) 显示游戏的 state(房间及其所包含物品的视觉表示),和 2) 从用户那里获取输入,并将该输入传递给游戏模型进行处理。
我喜欢用M-V-C的程序结构,代表“Model-View-Controller”,程序逻辑和GUI尽量分开,可能会有controllerclass 或 class 有助于将模型和视图联系在一起的元素。我尝试遵循的另一个原则是让 GUI 尽可能“哑”。所谓哑巴,我的意思是它应该从用户那里获得输入,也许进行最基本的输入验证,并且应该显示模型的状态,仅此而已。程序的几乎所有“大脑”都应该由模型本身控制,而不是视图。
概念验证示例:
我上面描述的内容的不完整但 运行宁“概念验证”示例如下所示。您应该复制整个程序,将其粘贴到您的 IDE 中一个名为 VideoGame.java
的文件中,然后应该能够 运行 它。它使用了一些您可能还不熟悉的概念,包括使用键绑定从 GUI 获取用户输入,以及使用 PropertyChangeListeners 和 PropertyChangeSupport 对象允许对象之间进行干净的通信(通知侦听器如果其中一个模型对象的 state 已更改)。该程序应该响应箭头键的按下:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
public class VideoGame {
private static final int[][] ROOM_GRID_KEY = {
{ 1, 0, 0, 2, 0, 0, 3, 4 },
{ 5, 6, 7, 8, 9, 10, 11, 0 },
{ 0, 12, 0, 13, 0, 0, 0, 0 },
{ 0, 14, 0, 0, 0, 0, 15, 16 },
{ 17, 18, 0, 19, 0, 0, 20, 0 },
{ 21, 22, 23, 24, 25, 26, 27, 28 }
};
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
GameModel gameModel = new GameModel(ROOM_GRID_KEY);
GameView gameView = new GameView();
new GameController(gameModel, gameView);
JFrame frame = new JFrame("Video Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(gameView);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
class GameController {
private GameModel gameModel;
private GameView gameView;
public GameController(GameModel gameModel, GameView gameView) {
this.gameModel = gameModel;
this.gameView = gameView;
ModelListener modelListener = new ModelListener();
gameView.setModel(gameModel);
gameModel.addPropertyChangeListener(modelListener);
}
private class ModelListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
gameView.modelChange(evt);
}
}
}
@SuppressWarnings("serial")
class DisplayPanel extends JPanel {
private JPanel[][] panelGrid;
private int gridCellSize;
private GameModel gameModel;
private GameRoom[][] roomGrid;
public DisplayPanel(int gridCellSize) {
this.gridCellSize = gridCellSize;
}
public void setModel(GameModel gameModel) {
this.gameModel = gameModel;
this.roomGrid = gameModel.getRoomGrid();
int rows = roomGrid.length;
int cols = roomGrid[0].length;
setBackground(Color.BLACK);
setLayout(new GridLayout(rows, cols, 1, 1));
setBorder(BorderFactory.createLineBorder(Color.BLACK));
panelGrid = new JPanel[rows][cols];
for (int r = 0; r < panelGrid.length; r++) {
for (int c = 0; c < panelGrid[r].length; c++) {
JPanel panel = new JPanel(new GridBagLayout());
panelGrid[r][c] = panel;
panel.setPreferredSize(new Dimension(gridCellSize, gridCellSize));
if (roomGrid[r][c] == null) {
panel.setBackground(Color.BLACK);
} else {
panel.setBackground(Color.PINK);
if (roomGrid[r][c].getPlayersInRoom().size() > 0) {
GamePlayer gamePlayer = roomGrid[r][c].getPlayersInRoom().get(0);
String name = gamePlayer.getName();
JLabel label = new JLabel(name);
panel.add(label);
}
}
add(panel);
}
}
// key bindings code
addBindings(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), Direction.SOUTH);
addBindings(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), Direction.NORTH);
addBindings(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), Direction.WEST);
addBindings(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), Direction.EAST);
}
private void addBindings(KeyStroke keyStroke, Direction direction) {
int condition = WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = getInputMap(condition);
ActionMap actionMap = getActionMap();
Action action = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
gameModel.move(direction);
}
};
inputMap.put(keyStroke, keyStroke.toString());
actionMap.put(keyStroke.toString(), action);
}
public void modelChange(PropertyChangeEvent evt) {
for (int r = 0; r < panelGrid.length; r++) {
for (int c = 0; c < panelGrid[r].length; c++) {
JPanel panel = panelGrid[r][c];
if (roomGrid[r][c] != null) {
if (roomGrid[r][c].getPlayersInRoom().size() > 0) {
GamePlayer gamePlayer = roomGrid[r][c].getPlayersInRoom().get(0);
String name = gamePlayer.getName();
JLabel label = new JLabel(name);
panel.add(label);
} else {
panel.removeAll();
}
}
}
}
revalidate();
repaint();
}
public GameModel getGameModel() {
return gameModel;
}
}
class GameView extends JPanel {
private static final int CELL_SIZE = 80;
private DisplayPanel displayPanel = new DisplayPanel(CELL_SIZE);
private GameModel gameModel;
// private JTextField textField = new JTextField();
public GameView() {
setLayout(new BorderLayout());
add(displayPanel);
// add(textField, BorderLayout.PAGE_END);
}
public void setModel(GameModel gameModel) {
this.gameModel = gameModel;
displayPanel.setModel(gameModel);
}
public void modelChange(PropertyChangeEvent evt) {
displayPanel.modelChange(evt);
}
public GameModel getGameModel() {
return gameModel;
}
}
class GameModel {
public static final String GAME_MODEL = "game model";
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
private GameRoom[][] roomGrid;
private GamePlayer player = new GamePlayer("Fred");
public GameModel(int[][] roomGridKey) {
roomGrid = new GameRoom[roomGridKey.length][roomGridKey[0].length];
// fill room grid with rooms if 1 in grid key array, with null if 0
for (int y = 0; y < roomGridKey.length; y++) {
for (int x = 0; x < roomGridKey[0].length; x++) {
roomGrid[y][x] = roomGridKey[y][x] != 0 ? new GameRoom("Some Room", "Some Description", x, y) : null;
}
}
// make room connections:
for (int y = 0; y < roomGrid.length; y++) {
for (int x = 0; x < roomGrid[0].length; x++) {
GameRoom thisRoom = roomGrid[y][x];
// if no room present, don't
if (thisRoom == null) {
continue;
}
if (x > 0) {
GameRoom otherGameRoom = roomGrid[y][x - 1];
if (otherGameRoom != null) {
thisRoom.putConnection(Direction.WEST, otherGameRoom);
}
}
if (x < roomGrid[0].length - 1) {
GameRoom otherGameRoom = roomGrid[y][x + 1];
if (otherGameRoom != null) {
thisRoom.putConnection(Direction.EAST, otherGameRoom);
}
}
if (y > 0) {
GameRoom otherGameRoom = roomGrid[y - 1][x];
if (otherGameRoom != null) {
thisRoom.putConnection(Direction.NORTH, otherGameRoom);
}
}
if (y < roomGrid.length - 1) {
GameRoom otherGameRoom = roomGrid[y + 1][x];
if (otherGameRoom != null) {
thisRoom.putConnection(Direction.SOUTH, otherGameRoom);
}
}
}
}
// put player in top left room
GameRoom currentRoom = roomGrid[0][0];
if (currentRoom == null) {
// some big error occurred
System.err.println("Current room at 0, 0 is null. Exiting");
System.exit(-1);
}
player.setCurrentRoom(currentRoom);
currentRoom.addPlayer(player);
player.addPropertyChangeListener(pce -> pcSupport.firePropertyChange(GAME_MODEL, null, player));
}
public boolean move(Direction direction) {
boolean success = player.move(direction);
return success;
}
public GamePlayer getPlayer() {
return player;
}
public GameRoom[][] getRoomGrid() {
return roomGrid;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(GAME_MODEL, listener);
}
}
class GamePlayer {
public static final String GAME_PLAYER = "game player";
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
private String name;
private GameRoom currentRoom;
public GamePlayer(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public boolean move(Direction direction) {
boolean success = currentRoom.move(this, direction);
return success;
}
public void setCurrentRoom(GameRoom currentRoom) {
GameRoom oldValue = this.currentRoom;
GameRoom newValue = currentRoom;
this.currentRoom = currentRoom;
pcSupport.firePropertyChange(GAME_PLAYER, oldValue, newValue);
}
public GameRoom getCurrentRoom() {
return currentRoom;
}
@Override
public String toString() {
return "GamePlayer [name=" + name + ", currentRoom=" + currentRoom + "]";
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(GAME_PLAYER, listener);
}
}
class GameRoom {
// map with connections to other game rooms
private Map<Direction, GameRoom> connections = new HashMap<Direction, GameRoom>();
// location information
private int x;
private int y;
// identifying information
private String roomName;
private String description;
// holds any player objects that may be in the room
private List<GamePlayer> playersInRoom = new ArrayList<>();
public GameRoom(String roomName, String description, int x, int y) {
this.roomName = roomName;
this.description = description;
this.x = x;
this.y = y;
}
public boolean move(GamePlayer gamePlayer, Direction direction) {
// if the room doesn't currently hold this player
if (!playersInRoom.contains(gamePlayer)) {
return false; // invalid move request
}
// if the room doesn't have a connecting room in the requested direction
if (!connections.containsKey(direction)) {
return false; // invalid move request
}
// otherwise, we're good
playersInRoom.remove(gamePlayer);
connections.get(direction).addPlayer(gamePlayer);
return true;
}
public void addPlayer(GamePlayer gamePlayer) {
playersInRoom.add(gamePlayer);
gamePlayer.setCurrentRoom(this);
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public String getRoomName() {
return roomName;
}
public String getDescription() {
return description;
}
public List<GamePlayer> getPlayersInRoom() {
return playersInRoom;
}
public Map<Direction, GameRoom> getConnections() {
return connections;
}
public void putConnection(Direction direction, GameRoom otherGameRoom) {
connections.put(direction, otherGameRoom);
}
@Override
public String toString() {
return "GameRoom [x=" + x + ", y=" + y + "]";
}
}
enum Direction {
NORTH, EAST, SOUTH, WEST
}
再次编译并运行此代码,并使用箭头键在网格中移动“Fred”。
一个更完整的程序会将数据完全从代码中分离出来,并允许文件 I/O 将房间网格读入程序,可能是通过创建一个或多个 CSV 文件来保存房间信息,或者如果预计数据会增长并变得更加复杂,则使用关系数据库,例如使用 SQL.
之一的关系数据库
我正在开发一个基于文本的视频游戏并为其创建了一个 GUI,但是,我在让我的两个 classes 一起工作时遇到了问题。我的 GUI class 中有一个文本字段,它接收来自用户的输入,我想将其发送到我的播放器 class 以便它可以比较它并执行适当的方法。我对编程还是很陌生,所以我希望你们中的一些人能够帮助我。请原谅可怕的代码。
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class GUI extends Player {
String input;
JLabel message;
public GUI() {
JFrame frame = new JFrame("SPACE GAME");
ImageIcon image = new ImageIcon("rocket.png");
frame.setIconImage(image.getImage());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1200, 800);
frame.setLocationRelativeTo(null);
frame.setFocusable(true);
frame.getContentPane();
JPanel panel = new JPanel();
JTextField commandLine = new JTextField(30);
JLabel message = new JLabel();
frame.add(panel);
panel.setBackground(Color.black);
panel.setLayout(null);
panel.setBorder(BorderFactory.createEmptyBorder(1000, 1000 ,1000 ,1000));
commandLine.setBackground(Color.WHITE);
commandLine.setBounds(5, 730, 300, 30);
commandLine.setBorder(BorderFactory.createLineBorder(Color.GRAY, 3));
commandLine.setFont(new Font("Zig", Font.PLAIN, 18));
commandLine.setForeground(Color.GREEN);
commandLine.setBackground(Color.BLACK);
commandLine.setCaretColor(Color.GREEN);
commandLine.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String input = commandLine.getText();
}
});
commandLine.setVisible(false);
message.setForeground(Color.GREEN);
message.setText("Welcome to SPACE GAME! Press any key to start.");
message.setBounds(5,665, 1000, 100);
message.setFont(new Font("Courier", Font.PLAIN, 18));
panel.add(message);
panel.add(commandLine);
frame.setVisible(true);
frame.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
commandLine.setVisible(true);
commandLine.requestFocusInWindow();
message.setText("Type \"help\" for help");
}
@Override
public void keyReleased(KeyEvent e) {
}
});
}
public JLabel getMessage(JLabel message) {
return message;
}
}
import javax.swing.*;
import java.util.Scanner;
public class Player {
//Attributes
private Room currentRoom;
Player player;
JLabel message;
// Handles player movement and commands
public void move() {
player = new Player();
Map map = new Map();
GUI gui = new GUI();
player.currentRoom = map.room1;
Scanner input = new Scanner(System.in);
System.out.println("You are in " + player.currentRoom.getName() + ". " + player.currentRoom.getRoomDescription());
System.out.println("Type \"help\" to get help");
//Commands
boolean isGameRunning = true;
while (isGameRunning) {
String goMessage = input.nextLine();
goMessage = goMessage.toLowerCase();
switch (goMessage) {
case "go north", "north", "go n", "n": goNorth(); break;
case "go east", "east", "go e", "e": goEast(); break;
case "go south", "south", "go s", "s": goSouth(); break;
case "go west", "west", "go w", "w": goWest(); break;
case "exit": isGameRunning = false; break;
case "look":
System.out.println(player.currentRoom.getRoomDescription());
break;
case "help":
System.out.println("""
"go (north, south, east, west)" to choose a direction to go.
"look" gives you a description of the room.
"exit" stops the game.""");
break;
default:
System.out.println("Unknown command");
break;
}
}
}
public void goNorth() {
if (player.currentRoom.getNorthRoom() != null) {
player.currentRoom = player.currentRoom.getNorthRoom();
System.out.println("You are in " + player.currentRoom.getName() + ". " + player.currentRoom.getRoomDescription());
} else {
System.out.println("You cannot go that way");
}
}
public void goEast() {
if (player.currentRoom.getEastRoom() != null) {
player.currentRoom = player.currentRoom.getEastRoom();
System.out.println("You are in " + player.currentRoom.getName() + ". " + player.currentRoom.getRoomDescription());
} else {
System.out.println("You cannot go that way");
}
}
public void goSouth() {
if (player.currentRoom.getSouthRoom() != null) {
player.currentRoom = player.currentRoom.getSouthRoom();
System.out.println("You are in " + player.currentRoom.getName() + ". " + player.currentRoom.getRoomDescription());
} else {
System.out.println("You cannot go that way");
}
}
public void goWest() {
if (player.currentRoom.getWestRoom() != null) {
player.currentRoom = player.currentRoom.getWestRoom();
System.out.println("You are in " + player.currentRoom.getName() + ". " + player.currentRoom.getRoomDescription());
} else {
System.out.println("You cannot go that way");
}
}
}
由于您已经使 GUI 扩展了播放器,您需要做的就是从输入字段上的 ActionListener 调用所需的函数。
commandLine.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String input = commandLine.getText();
if (input == "go north") {
GoNorth();
}
// .....
}
});
然而,事情相当混乱。对于初学者来说,没有真正的理由让 GUI 扩展播放器。我建议您将 GUI class 与播放器分开。这样,您就不需要使用扫描仪或打印,而是在 GUI class.
中收集所有 GUI 功能例如:
public void goNorth() {
if (player.currentRoom.getNorthRoom() != null) {
player.currentRoom = player.currentRoom.getNorthRoom();
gui.print("You are in " + player.currentRoom.getName() + ". " + player.currentRoom.getRoomDescription())
} else {
gui.print("You cannot go that way");
}
}
您需要在 GUI class 中实现一个打印功能,它会向消息框添加一行文本,但我相信您一开始就计划这样做。
在 GUI class 中,您将获得对播放器的引用,因此读取输入字段将如下所示:
commandLine.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String input = commandLine.getText();
switch(input) {
case "go north":
player.goNorth();
break;
...
}
}
});
您的代码中有一些好东西以及几个可能妨碍您将 GUI 与当前的控制台程序代码混搭的主要问题,这些问题包括:
- 您确实将您的程序逻辑分离到单独的 class 中,例如播放器、地图和房间 class 中,这是一件非常好的事情,但您仍然可以做更多的逻辑分离,这样做可以简化您的代码,使其更容易调试和增强。
- Player class 包含一个 Player 实例,我不确定为什么。外层主 class 可能应该从
Player
重命名为Game
class,而玩家 class 应该是一个完全独立的 class,它保存 单个 玩家实例的状态,并且不应像您正在做的那样创建自己的实例。关键是要努力“oop-ify”你的代码,尤其是代码的“模型”部分,即持有非用户界面程序逻辑的代码,使其更面向对象,因为这将使它变得更更容易让您将 GUI 或任何其他接口连接到您的模型。 - 您的模型(再次命名为 Player)包含用户界面 (UI) 代码:其中包含 Scanner 输入代码和 println 语句。这是有问题的,尤其是现在您要增强程序并使其与 GUI 一起工作。首先,您必须做的关键事情是在模型 class 之外分离用户界面代码(主要是从用户那里获取输入并向用户显示输出的代码),也就是说,再次, class 保存程序逻辑。
- 您的播放器 class 有很多非播放器代码使其混乱。一个基本的 OOPs 原则是 class 应该有单一职责(称为“单一职责规则”),所以你的 Player class 应该只有封装 Player 状态所需的代码(例如名称、房间,也许还有其他属性,例如力量、武器、生命值、智力……)和玩家行为(允许玩家与其他 classes 交互的方法),或多或少都没有。
- 按照同样的思路,似乎所有程序数据和行为似乎都硬连接到驱动程序的这个单一主播放器 class 中。这是非常有限的,并且会妨碍您创建包含许多房间、房间中的物品以及诸如此类的东西的丰富游戏环境的能力。分而治之——从主程序中获取该信息并将其放入符合 OOP 的 classes,包括一个 Room class,它具有它可能包含的项目的字段,它有自己的知识与其他房间的连接。
- 您的 GUI 程序扩展了 Player 对象。这是一个主要问题,因为这个程序结构没有通过继承的基本测试,“is-a”测试:逻辑上 GUI 是一种更具体的播放器类型(很像狗是更具体的Animal 类型,一个通过“is-a”测试的结构)?不,它不是,虽然这种区别看起来悬而未决,但由于这种继承,您可以尝试在 GUI class 中使用 Player 字段,但如果您尝试这样做,代码将无法正常工作。删除此继承,而是尝试通过“组合”连接 classes,其中一个 class 持有另一个的实例,而不是扩展另一个。因此,例如,GUI 可能包含一个 Player 变量而不是从 Player. 扩展
底线: 在尝试添加 GUI
之前, 您需要完全“OOP-ify”您的代码为了使程序能够与 GUI 很好地结合在一起,我建议使用 class 都具有 单一责任,class 很容易可测试 隔离(远离 GUI 或任何用户界面)。从第一原则开始,程序将更容易构建。
因此,例如,class可以考虑用于冒险游戏的元素包括:
- 方向 class,或者更好的方向 enum.
这将封装“方向”的概念,并将用于游戏中的对象以了解它们的前进方向,并能够使用常量而不是字符串将其传达给其他对象。枚举的用户将允许编译器检查方向是否被正确使用,因为如果你使用字符串,比如“West”,编译器不会自动知道你是否输入错误的字符串并改为使用“Best”。
像这样简单的东西,例如:
public enum Direction {
NORTH, EAST, SOUTH, WEST
}
- 一个游戏室class
这将保存信息,告诉它它在房间网格中的位置、名称和描述属性的字符串,以及游戏玩家字段或 List
游戏玩家(如果允许超过一个的话),也许包含如下代码的内容:....
public class GameRoom {
// map with connections to other game rooms
private Map<Direction, GameRoom> connections = new HashMap<Direction, GameRoom>();
// location information
private int x;
private int y;
// identifying information
private String roomName;
private String description;
// holds any player objects that may be in the room
private List<GamePlayer> playersInRoom = new ArrayList<>();
class 将有适当的构造函数以及 getter 和 setter 方法,还有:
允许游戏将玩家添加到房间的addPlayer(...)
方法:
public void addPlayer(GamePlayer gamePlayer) {
playersInRoom.add(gamePlayer);
gamePlayer.setCurrentRoom(this); // we'll get to this later
}
一种public boolean move(...)
方法,可以将房间内的玩家移动到相连的房间。它首先检查房间是否确实包含被移动的玩家,然后检查房间是否与请求方向上的另一个房间有连接。如果任一个为 false,则方法 returns false 让调用代码表明尝试的移动失败。否则,如果允许移动,则 returns true:
public boolean move(GamePlayer gamePlayer, Direction direction) {
// if the room doesn't currently hold this player
if (!playersInRoom.contains(gamePlayer)) {
return false; // invalid move request
}
// if the room doesn't have a connecting room in the requested direction
if (!connections.containsKey(direction)) {
return false; // invalid move request
}
// otherwise, we're good
playersInRoom.remove(gamePlayer);
connections.get(direction).addPlayer(gamePlayer);
return true;
}
- 一个游戏玩家class
这个 class 将包含多个属性,例如名称的字符串和当前房间的 GameRoom 字段、构造函数、getter 和 setter 方法。它的声明和字段可能类似于:
public class GamePlayer {
private String name;
private GameRoom currentRoom;
//.... other properties
它也应该有一个移动方法调用 currentRoom GameRoom 的移动方法和 returns 相同的布尔值,一个结果告诉调用代码移动是否有效且成功:
public boolean move(Direction direction) {
return currentRoom.move(this, direction);
}
- 一个游戏模型class:
这将保存当前游戏的状态,玩家字段,保存所有房间的数据结构。它可以有自己的移动方法.......
只有在创建符合 OOP 的 classes 之后,您才应该进入下一步:创建 GUI 或“视图”class/classes。这个 class 可以包含一个 GameModel 实例,并将负责 1) 显示游戏的 state(房间及其所包含物品的视觉表示),和 2) 从用户那里获取输入,并将该输入传递给游戏模型进行处理。
我喜欢用M-V-C的程序结构,代表“Model-View-Controller”,程序逻辑和GUI尽量分开,可能会有controllerclass 或 class 有助于将模型和视图联系在一起的元素。我尝试遵循的另一个原则是让 GUI 尽可能“哑”。所谓哑巴,我的意思是它应该从用户那里获得输入,也许进行最基本的输入验证,并且应该显示模型的状态,仅此而已。程序的几乎所有“大脑”都应该由模型本身控制,而不是视图。
概念验证示例:
我上面描述的内容的不完整但 运行宁“概念验证”示例如下所示。您应该复制整个程序,将其粘贴到您的 IDE 中一个名为 VideoGame.java
的文件中,然后应该能够 运行 它。它使用了一些您可能还不熟悉的概念,包括使用键绑定从 GUI 获取用户输入,以及使用 PropertyChangeListeners 和 PropertyChangeSupport 对象允许对象之间进行干净的通信(通知侦听器如果其中一个模型对象的 state 已更改)。该程序应该响应箭头键的按下:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
public class VideoGame {
private static final int[][] ROOM_GRID_KEY = {
{ 1, 0, 0, 2, 0, 0, 3, 4 },
{ 5, 6, 7, 8, 9, 10, 11, 0 },
{ 0, 12, 0, 13, 0, 0, 0, 0 },
{ 0, 14, 0, 0, 0, 0, 15, 16 },
{ 17, 18, 0, 19, 0, 0, 20, 0 },
{ 21, 22, 23, 24, 25, 26, 27, 28 }
};
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
GameModel gameModel = new GameModel(ROOM_GRID_KEY);
GameView gameView = new GameView();
new GameController(gameModel, gameView);
JFrame frame = new JFrame("Video Game");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(gameView);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
class GameController {
private GameModel gameModel;
private GameView gameView;
public GameController(GameModel gameModel, GameView gameView) {
this.gameModel = gameModel;
this.gameView = gameView;
ModelListener modelListener = new ModelListener();
gameView.setModel(gameModel);
gameModel.addPropertyChangeListener(modelListener);
}
private class ModelListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
gameView.modelChange(evt);
}
}
}
@SuppressWarnings("serial")
class DisplayPanel extends JPanel {
private JPanel[][] panelGrid;
private int gridCellSize;
private GameModel gameModel;
private GameRoom[][] roomGrid;
public DisplayPanel(int gridCellSize) {
this.gridCellSize = gridCellSize;
}
public void setModel(GameModel gameModel) {
this.gameModel = gameModel;
this.roomGrid = gameModel.getRoomGrid();
int rows = roomGrid.length;
int cols = roomGrid[0].length;
setBackground(Color.BLACK);
setLayout(new GridLayout(rows, cols, 1, 1));
setBorder(BorderFactory.createLineBorder(Color.BLACK));
panelGrid = new JPanel[rows][cols];
for (int r = 0; r < panelGrid.length; r++) {
for (int c = 0; c < panelGrid[r].length; c++) {
JPanel panel = new JPanel(new GridBagLayout());
panelGrid[r][c] = panel;
panel.setPreferredSize(new Dimension(gridCellSize, gridCellSize));
if (roomGrid[r][c] == null) {
panel.setBackground(Color.BLACK);
} else {
panel.setBackground(Color.PINK);
if (roomGrid[r][c].getPlayersInRoom().size() > 0) {
GamePlayer gamePlayer = roomGrid[r][c].getPlayersInRoom().get(0);
String name = gamePlayer.getName();
JLabel label = new JLabel(name);
panel.add(label);
}
}
add(panel);
}
}
// key bindings code
addBindings(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), Direction.SOUTH);
addBindings(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), Direction.NORTH);
addBindings(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), Direction.WEST);
addBindings(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), Direction.EAST);
}
private void addBindings(KeyStroke keyStroke, Direction direction) {
int condition = WHEN_IN_FOCUSED_WINDOW;
InputMap inputMap = getInputMap(condition);
ActionMap actionMap = getActionMap();
Action action = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
gameModel.move(direction);
}
};
inputMap.put(keyStroke, keyStroke.toString());
actionMap.put(keyStroke.toString(), action);
}
public void modelChange(PropertyChangeEvent evt) {
for (int r = 0; r < panelGrid.length; r++) {
for (int c = 0; c < panelGrid[r].length; c++) {
JPanel panel = panelGrid[r][c];
if (roomGrid[r][c] != null) {
if (roomGrid[r][c].getPlayersInRoom().size() > 0) {
GamePlayer gamePlayer = roomGrid[r][c].getPlayersInRoom().get(0);
String name = gamePlayer.getName();
JLabel label = new JLabel(name);
panel.add(label);
} else {
panel.removeAll();
}
}
}
}
revalidate();
repaint();
}
public GameModel getGameModel() {
return gameModel;
}
}
class GameView extends JPanel {
private static final int CELL_SIZE = 80;
private DisplayPanel displayPanel = new DisplayPanel(CELL_SIZE);
private GameModel gameModel;
// private JTextField textField = new JTextField();
public GameView() {
setLayout(new BorderLayout());
add(displayPanel);
// add(textField, BorderLayout.PAGE_END);
}
public void setModel(GameModel gameModel) {
this.gameModel = gameModel;
displayPanel.setModel(gameModel);
}
public void modelChange(PropertyChangeEvent evt) {
displayPanel.modelChange(evt);
}
public GameModel getGameModel() {
return gameModel;
}
}
class GameModel {
public static final String GAME_MODEL = "game model";
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
private GameRoom[][] roomGrid;
private GamePlayer player = new GamePlayer("Fred");
public GameModel(int[][] roomGridKey) {
roomGrid = new GameRoom[roomGridKey.length][roomGridKey[0].length];
// fill room grid with rooms if 1 in grid key array, with null if 0
for (int y = 0; y < roomGridKey.length; y++) {
for (int x = 0; x < roomGridKey[0].length; x++) {
roomGrid[y][x] = roomGridKey[y][x] != 0 ? new GameRoom("Some Room", "Some Description", x, y) : null;
}
}
// make room connections:
for (int y = 0; y < roomGrid.length; y++) {
for (int x = 0; x < roomGrid[0].length; x++) {
GameRoom thisRoom = roomGrid[y][x];
// if no room present, don't
if (thisRoom == null) {
continue;
}
if (x > 0) {
GameRoom otherGameRoom = roomGrid[y][x - 1];
if (otherGameRoom != null) {
thisRoom.putConnection(Direction.WEST, otherGameRoom);
}
}
if (x < roomGrid[0].length - 1) {
GameRoom otherGameRoom = roomGrid[y][x + 1];
if (otherGameRoom != null) {
thisRoom.putConnection(Direction.EAST, otherGameRoom);
}
}
if (y > 0) {
GameRoom otherGameRoom = roomGrid[y - 1][x];
if (otherGameRoom != null) {
thisRoom.putConnection(Direction.NORTH, otherGameRoom);
}
}
if (y < roomGrid.length - 1) {
GameRoom otherGameRoom = roomGrid[y + 1][x];
if (otherGameRoom != null) {
thisRoom.putConnection(Direction.SOUTH, otherGameRoom);
}
}
}
}
// put player in top left room
GameRoom currentRoom = roomGrid[0][0];
if (currentRoom == null) {
// some big error occurred
System.err.println("Current room at 0, 0 is null. Exiting");
System.exit(-1);
}
player.setCurrentRoom(currentRoom);
currentRoom.addPlayer(player);
player.addPropertyChangeListener(pce -> pcSupport.firePropertyChange(GAME_MODEL, null, player));
}
public boolean move(Direction direction) {
boolean success = player.move(direction);
return success;
}
public GamePlayer getPlayer() {
return player;
}
public GameRoom[][] getRoomGrid() {
return roomGrid;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(GAME_MODEL, listener);
}
}
class GamePlayer {
public static final String GAME_PLAYER = "game player";
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
private String name;
private GameRoom currentRoom;
public GamePlayer(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public boolean move(Direction direction) {
boolean success = currentRoom.move(this, direction);
return success;
}
public void setCurrentRoom(GameRoom currentRoom) {
GameRoom oldValue = this.currentRoom;
GameRoom newValue = currentRoom;
this.currentRoom = currentRoom;
pcSupport.firePropertyChange(GAME_PLAYER, oldValue, newValue);
}
public GameRoom getCurrentRoom() {
return currentRoom;
}
@Override
public String toString() {
return "GamePlayer [name=" + name + ", currentRoom=" + currentRoom + "]";
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(GAME_PLAYER, listener);
}
}
class GameRoom {
// map with connections to other game rooms
private Map<Direction, GameRoom> connections = new HashMap<Direction, GameRoom>();
// location information
private int x;
private int y;
// identifying information
private String roomName;
private String description;
// holds any player objects that may be in the room
private List<GamePlayer> playersInRoom = new ArrayList<>();
public GameRoom(String roomName, String description, int x, int y) {
this.roomName = roomName;
this.description = description;
this.x = x;
this.y = y;
}
public boolean move(GamePlayer gamePlayer, Direction direction) {
// if the room doesn't currently hold this player
if (!playersInRoom.contains(gamePlayer)) {
return false; // invalid move request
}
// if the room doesn't have a connecting room in the requested direction
if (!connections.containsKey(direction)) {
return false; // invalid move request
}
// otherwise, we're good
playersInRoom.remove(gamePlayer);
connections.get(direction).addPlayer(gamePlayer);
return true;
}
public void addPlayer(GamePlayer gamePlayer) {
playersInRoom.add(gamePlayer);
gamePlayer.setCurrentRoom(this);
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public String getRoomName() {
return roomName;
}
public String getDescription() {
return description;
}
public List<GamePlayer> getPlayersInRoom() {
return playersInRoom;
}
public Map<Direction, GameRoom> getConnections() {
return connections;
}
public void putConnection(Direction direction, GameRoom otherGameRoom) {
connections.put(direction, otherGameRoom);
}
@Override
public String toString() {
return "GameRoom [x=" + x + ", y=" + y + "]";
}
}
enum Direction {
NORTH, EAST, SOUTH, WEST
}
再次编译并运行此代码,并使用箭头键在网格中移动“Fred”。
一个更完整的程序会将数据完全从代码中分离出来,并允许文件 I/O 将房间网格读入程序,可能是通过创建一个或多个 CSV 文件来保存房间信息,或者如果预计数据会增长并变得更加复杂,则使用关系数据库,例如使用 SQL.
之一的关系数据库