Java Swing新手求助:多JFrame结构下2D跑酷游戏关卡开发
Hey there! Let's break down how to tackle this problem step by step—since you're building a fun 2D platformer and hitting some roadblocks with JFrames and tile-based levels, I'll walk you through the fixes and core implementations.
Using separate JFrames for menus and game levels causes headaches with window focus, memory leaks, and inconsistent user behavior. Instead, use a single main JFrame that swaps out different JPanels (one for the menu, one for gameplay) with CardLayout. Here's how to adjust your existing code:
import java.awt.CardLayout; import java.awt.Dimension; import java.awt.Toolkit; import java.io.IOException; import javax.swing.JFrame; import javax.swing.JPanel; public class GameLauncher { public static void main(String[] args) throws IOException { JFrame mainFrame = new JFrame("2D Platformer"); mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Use your screen size logic Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); mainFrame.setSize(screenSize.width, screenSize.height); mainFrame.setExtendedState(JFrame.MAXIMIZED_BOTH); // Optional fullscreen // CardLayout lets us switch panels seamlessly CardLayout cardLayout = new CardLayout(); JPanel mainContainer = new JPanel(cardLayout); // Add your menu and game panels MainMenuPanel menuPanel = new MainMenuPanel(mainContainer, cardLayout); GamePanel gamePanel = new GamePanel(); mainContainer.add(menuPanel, "MENU"); mainContainer.add(gamePanel, "GAME"); mainFrame.add(mainContainer); mainFrame.setVisible(true); // Start on the menu cardLayout.show(mainContainer, "MENU"); } } // Turn your menu into a JPanel instead of a JFrame class MainMenuPanel extends JPanel { public MainMenuPanel(JPanel container, CardLayout layout) { // Add your existing menu buttons here // When "Start Game" is clicked, switch to the game: // layout.show(container, "GAME"); } }
Tile-based levels use a 2D array to represent your level (each number = a tile type: empty, platform, etc.). Let's build this out:
1. Create a Tile Class
First, make a simple class to hold tile properties (position, size, whether it's solid for collisions):
class Tile { private int x, y; private int width, height; private boolean isSolid; public Tile(int x, int y, int width, int height, boolean isSolid) { this.x = x; this.y = y; this.width = width; this.height = height; this.isSolid = isSolid; } // Getters for collision checks public int getX() { return x; } public int getY() { return y; } public int getWidth() { return width; } public int getHeight() { return height; } public boolean isSolid() { return isSolid; } }
2. Load the Level in Your GamePanel
In your GamePanel, initialize tiles from a level array and set up the game loop:
import java.awt.Graphics; import java.util.ArrayList; import javax.swing.JPanel; class GamePanel extends JPanel implements Runnable { private ArrayList<Tile> tiles = new ArrayList<>(); private Player player; private Thread gameThread; public GamePanel() { setFocusable(true); // Let the panel receive keyboard input initLevel(); initPlayer(); startGameLoop(); } private void initLevel() { // Example level: 0 = empty, 1 = solid platform int[][] levelMap = { {0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0}, {0,0,1,1,1,0,0,1,1,0}, {0,0,0,0,0,0,0,0,0,0}, {1,1,1,1,1,1,1,1,1,1} // Ground tile }; int tileSize = 64; // Match your sprite size for (int row = 0; row < levelMap.length; row++) { for (int col = 0; col < levelMap[row].length; col++) { if (levelMap[row][col] == 1) { tiles.add(new Tile(col * tileSize, row * tileSize, tileSize, tileSize, true)); } } } } private void initPlayer() { player = new Player(100, 400, 32, 64); // x, y, width, height addKeyListener(player); // Attach keyboard listener to player } // Game loop for movement and rendering (~60 FPS) private void startGameLoop() { gameThread = new Thread(this); gameThread.start(); } @Override public void run() { while (gameThread != null) { update(); repaint(); try { Thread.sleep(16); } catch (InterruptedException e) { e.printStackTrace(); } } } private void update() { player.update(tiles); // Pass tiles to player for collision checks } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); // Draw tiles first (so player is on top) for (Tile tile : tiles) { // Replace with sprite drawing later: g.drawImage(tileSprite, tile.getX(), tile.getY(), null); g.fillRect(tile.getX(), tile.getY(), tile.getWidth(), tile.getHeight()); } player.draw(g); } }
Now build the Player class with keyboard controls and collision logic for tiles:
import java.awt.Graphics; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.ArrayList; class Player implements KeyListener { private int x, y; private int width, height; private int xSpeed, ySpeed; private boolean leftPressed, rightPressed, jumping; private final int GRAVITY = 1; private final int JUMP_STRENGTH = -15; public Player(int x, int y, int width, int height) { this.x = x; this.y = y; this.width = width; this.height = height; } public void update(ArrayList<Tile> tiles) { // Apply gravity ySpeed += GRAVITY; y += ySpeed; checkVerticalCollision(tiles); // Horizontal movement xSpeed = leftPressed ? -5 : rightPressed ? 5 : 0; x += xSpeed; checkHorizontalCollision(tiles); } // Check collisions with tiles vertically (jumping/falling) private void checkVerticalCollision(ArrayList<Tile> tiles) { for (Tile tile : tiles) { if (tile.isSolid()) { // Land on top of a tile if (y + height > tile.getY() && y < tile.getY() + tile.getHeight() && x + width > tile.getX() && x < tile.getX() + tile.getWidth()) { y = tile.getY() - height; ySpeed = 0; jumping = false; } // Hit a ceiling tile from below else if (y < tile.getY() + tile.getHeight() && y + height > tile.getY() && x + width > tile.getX() && x < tile.getX() + tile.getWidth()) { y = tile.getY() + tile.getHeight(); ySpeed = 0; } } } } // Check horizontal collisions (left/right into walls) private void checkHorizontalCollision(ArrayList<Tile> tiles) { for (Tile tile : tiles) { if (tile.isSolid()) { // Hit right side of a tile if (x < tile.getX() + tile.getWidth() && x + width > tile.getX() + tile.getWidth() && y + height > tile.getY() && y < tile.getY() + tile.getHeight()) { x = tile.getX() + tile.getWidth(); xSpeed = 0; } // Hit left side of a tile if (x + width > tile.getX() && x < tile.getX() && y + height > tile.getY() && y < tile.getY() + tile.getHeight()) { x = tile.getX() - width; xSpeed = 0; } } } } public void draw(Graphics g) { // Replace with your player sprite later g.fillRect(x, y, width, height); } @Override public void keyPressed(KeyEvent e) { int key = e.getKeyCode(); if (key == KeyEvent.VK_LEFT) leftPressed = true; if (key == KeyEvent.VK_RIGHT) rightPressed = true; if (key == KeyEvent.VK_SPACE && !jumping) { ySpeed = JUMP_STRENGTH; jumping = true; } } @Override public void keyReleased(KeyEvent e) { int key = e.getKeyCode(); if (key == KeyEvent.VK_LEFT) leftPressed = false; if (key == KeyEvent.VK_RIGHT) rightPressed = false; } @Override public void keyTyped(KeyEvent e) {} }
For enemies, create an Enemy class with a hitbox, then add collision checks in the player's update method. If the player's hitbox overlaps with an enemy's, handle logic like losing a life or respawning.
- Sprites: Replace
fillRectcalls withImageIO.read()to load actual tile/player/enemy sprites. - Level Files: Instead of hardcoding level arrays, load levels from text files for easier editing.
- Performance: For large levels, only render tiles that are visible on screen to speed things up.
内容的提问来源于stack exchange,提问作者DennyS

