You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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.

First: Ditch Multiple JFrames (They're More Trouble Than They're Worth)

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");
    }
}
Implementing Tile-Based Levels (Like Mario)

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);
    }
}
Player Movement & Collision Detection

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) {}
}
Adding Enemy Collision

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.

Quick Next Steps
  • Sprites: Replace fillRect calls with ImageIO.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 08:38:12