Bài báo cáo môn lập trình Java không chỉ là một cơ hội để áp dụng những kiến thức đã học mà còn là một thử thách để phát triển kỹ năng lập trình và giải quyết vấn đề.. Kế hoạch – phân cô
Giới thiệu
Mục tiêu của báo cáo
Mục tiêu chính của báo cáo là đưa ra một hướng dẫn chi tiết từng bước trong việc phát triển game bắn máy bay, từ việc thiết kế cấu trúc dữ liệu đến triển khai các tính năng chính như di chuyển, bắn đạn và phát hiện va chạm Ngoài ra, báo cáo cũng sẽ giới thiệu về môi trường phát triển Java và các công nghệ liên quan, đồng thời nhấn mạnh vào các kỹ thuật và thủ thuật để giải quyết những thách thức thường gặp trong quá trình phát triển game.
Môi trường phát triển
Trong quá trình phát triển game bắn máy bay sử dụng ngôn ngữ lập trình Java, chúng tôi đã sử dụng một số công cụ và môi trường phát triển phù hợp để giúp tối ưu hóa quá trình lập trình và đảm bảo tính năng và hiệu suất của sản phẩm cuối cùng Dưới đây là một số chi tiết về môi trường phát triển mà chúng tôi đã áp dụng:
Ngôn ngữ lập trình Java: Java được lựa chọn làm ngôn ngữ chính để phát triển game bắn máy bay của chúng tôi Lý do chính là sự phổ biến của Java trong việc phát triển ứng dụng đa nền tảng và khả năng quản lý bộ nhớ tốt, điều này rất quan trọng đối với game yêu cầu xử lý nhiều đối tượng đồ họa và logic phức tạp.
Môi trường lập trình (IDE): Chúng tôi đã sử dụng IntelliJ IDEA làm môi trường phát triển tích hợp (IDE) chính cho dự án IntelliJ cung cấp môi trường lập trình mạnh mẽ với các tính năng hỗ trợ nâng cao, giúp chúng tôi dễ dàng quản lý mã nguồn, thực hiện debug và phát triển các tính năng mới một cách hiệu quả.
Phân tích chức năng
Bắt đầu game
Khởi tạo các thành phần của game như người chơi, đạn, kẻ địch, âm thanh, hình ảnh, và kết nối đến server.
Thiết lập các sự kiện của Scene (như sự kiện phím nhấn và thả).
Bắt đầu một AnimationTimer để cập nhật và vẽ game
Hiện thực: player1ship = new Image(getClass().getResource("/SShip.png").toString()); player2ship = new Image(getClass().getResource("/SS.png").toString()); player1 = new Player(400, 500, player1ship); player2 = new Player(400, 100, player2ship); enemy1Image = new Image(getClass().getResource("/enemy1.png").toString()); enemy2Image = new Image(getClass().getResource("/enemy2.png").toString());
String playerEnv = System.getenv("PLAYER"); isPlayer1 = "1".equals(playerEnv); bullets = new ArrayList(); explosions = new ArrayList(); enemies = new ArrayList(); score1 = ;0 score2 = ;0 lastShotTime = ;0 gameOver = false; winner = ""; remainingTime = GAME_DURATION; shootSound = new AudioClip(getClass().getResource("/shoot.wav").toString()); explosionSound = new
AudioClip(getClass().getResource("/explo.wav").toString()); backgroundImage = new
Image(getClass().getResource("/background.png").toString());
Canvas canvas = new Canvas(WIDTH, HEIGHT);
GraphicsContext gc = canvas.getGraphicsContext2D(); connectToServer(); sendMessage("PLAYER_READY");
StackPane root = new StackPane(); root.getChildren().add(canvas);
Scene scene = new Scene(root); scene.setOnKeyPressed(this::handleKeyPressed); scene.setOnKeyReleased(this::handleKeyReleased); primaryStage.setTitle("Space Shooter"); primaryStage.setScene(scene); primaryStage.show(); gameStartTime = System.currentTimeMillis(); new AnimationTimer() {
@Override public void handle(long now) { if (!gameStarted) { renderWaitingScreen(gc);
} else if (!gameOver) { long currentTime = System.currentTimeMillis(); if (currentTime - serverStartTime >= GAME_START_DELAY) { updateGame(now); remainingTime = GAME_DURATION - (currentTime -
(serverStartTime + GAME_START_DELAY)); if (remainingTime 0, hiển thị số giây còn lại.
Nếu seconds ) {0 gc.fillText(String.valueOf(seconds), WIDTH / 2 - 25, HEIGHT / );2 } else { gc.fillText("Game Start", WIDTH / 2 - 100, HEIGHT / );2
Xử lý sự kiện khi có phím được nhấn và thả
Điều khiển di chuyển và bắn đạn của người chơi 1 hoặc người chơi 2 tùy theo người chơi nào đang hoạt động.
Dừng di chuyển hoặc dừng việc bắn đạn của người chơi 1 hoặc người chơi 2.
Hiện thực: private void handleKeyPressed(KeyEvent event) { if (isPlayer1) { if (event.getCode() == KeyCode.LEFT) { player1.startMovingLeft();
} else if (event.getCode() == KeyCode.RIGHT) { player1.startMovingRight();
} else if (event.getCode() == KeyCode.SPACE) { player1Shoot = true;
} else { if (event.getCode() == KeyCode ) {A player2.startMovingLeft();
} else if (event.getCode() == KeyCode ) {D player2.startMovingRight();
} else if (event.getCode() == KeyCode.SHIFT) { player2Shoot = true;
} private void handleKeyReleased(KeyEvent event) { if (isPlayer1) { if (event.getCode() == KeyCode.LEFT || event.getCode() =KeyCode.RIGHT) { player1.stopMoving();
} else if (event.getCode() == KeyCode.SPACE) { player1Shoot = false;
} else { if (event.getCode() == KeyCode.A || event.getCode() == KeyCode ) {D player2.stopMoving();
} else if (event.getCode() == KeyCode.SHIFT) { player2Shoot = false;
Cập nhật trạng thái của game
Điều khiển di chuyển người chơi, bắn đạn, sinh kẻ địch, và xử lý va chạm giữa các đối tượng.
Hiện thực: private void updateGame(long now) { if (gameOver) { return;
} updateLocalPlayerPosition(); long currentTime = System.currentTimeMillis(); if (currentTime - lastEnemySpawnTime > ENEMY_SPAWN_INTERVAL) { requestSpawnEnemies(); lastEnemySpawnTime = currentTime;
} if (player1Shoot && now - lastShotTime > 500_000_000 && isPlayer1) { double bulletX = player1.getX() + player1.getWidth() / ;2 double bulletY = player1.getY(); bullets.add(new Bullet(bulletX, bulletY, Color.YELLOW, true)); shootSound.play(); lastShotTime = now; player1Shoot = false; sendMessage("FIRE1," + bulletX + "," + bulletY);
} if (player2Shoot && now - lastShotTime > 500_000_000 && !isPlayer1) { double bulletX = player2.getX() + player2.getWidth() / ;2 double bulletY = player2.getY() + player2.getHeight(); bullets.add(new Bullet(bulletX, bulletY, Color.ORANGE, false)); shootSound.play(); lastShotTime = now; player2Shoot = false; sendMessage("FIRE2," + bulletX + "," + bulletY);
} bullets.forEach(Bullet::update); bullets.removeIf(Bullet::isOffScreen); updateEnemies(); checkCollisions(); explosions.forEach(Explosion::update); explosions.removeIf(Explosion::isFinished);
Cập nhật trạng thái của kẻ địch
Loại bỏ kẻ địch ra khỏi danh sách khi chúng đi ra khỏi màn hình.
Hiện thực: private void updateEnemies() {
Iterator iterator = enemies.iterator(); while (iterator.hasNext()) {
Enemy enemy = iterator.next(); enemy.update(); if (enemy.isOffScreen()) { iterator.remove(); int escapedSide = enemy.isMovingDown() ? 2 : ;1 sendMessage("ENEMY_ESCAPED," + escapedSide);
Xử lý va chạm
Kiểm tra và xử lý va chạm giữa đạn và người chơi, cũng như giữa đạn và kẻ địchLoại bỏ các đạn và kẻ địch khi chúng va chạm và không còn hữu ích nữa.Hiện thực: private void checkCollisions() {
List bulletsToRemove = new ArrayList();
List enemiesToRemove = new ArrayList(); for (Bullet bullet : bullets) { boolean bulletRemoved = false;
// Xử lý va chạm với người chơi
Player otherPlayer = bullet.isPlayer1() ? player2 : player1; if (checkPlayerHit(bullet, otherPlayer) && bullet.isPlayer1() ! isPlayer1) { bulletsToRemove.add(bullet); sendMessage("PLAYER_HIT," + (bullet.isPlayer1() ? "2" : "1")); addExplosion(bullet.getX(), bullet.getY()); playExplosionSound(); bulletRemoved = true;
// Nếu đạn chưa bị xóa, kiểm tra va chạm với kẻ địch if (!bulletRemoved) { for (Enemy enemy : enemies) { if (checkEnemyHit(bullet, enemy) && isValidHit(bullet, enemy)) { bulletsToRemove.add(bullet); sendMessage("ENEMY_HIT," + (bullet.isPlayer1() ? "1" :
"2") + "," + enemies.indexOf(enemy)); addExplosion(bullet.getX(), bullet.getY()); playExplosionSound(); bulletRemoved = true; break; // Thoát khỏi vòng lặp sau khi xử lý va chạm }
} if (bulletRemoved) { break; // Dừng vòng lặp nếu đạn đã bị xóa
Hiệu ứng va chạm
Thêm hiệu ứng nổ khi một đối tượng bị phá hủy.
Phát âm thanh nổ khi có hiệu ứng nổ xảy ra.
Hiện thực: private void addExplosion(double x, double y) { explosions.add(new Explosion(x, y));
} private void playExplosionSound() { explosionSound.play();
Kiểm tra va chạm
Kiểm tra xem một viên đạn có thể bắn trúng một kẻ địch hay không, dựa trên hướng di chuyển của kẻ địch.
Kiểm tra xem một viên đạn có bắn trúng người chơi hay không.
Kiểm tra xem một viên đạn có bắn trúng kẻ địch hay không.
Hiện thực: private boolean isValidHit(Bullet bullet, Enemy enemy) {
// Player 1 chỉ có thể bắn trúng enemy đi xuống
// Player 2 chỉ có thể bắn trúng enemy đi lên return (bullet.isPlayer1() && enemy.isMovingDown()) || (! bullet.isPlayer1() && !enemy.isMovingDown());
} private boolean checkPlayerHit(Bullet bullet, Player player) { return bullet.getX() < player.getX() + player.getWidth() && bullet.getX() + bullet.getWidth() > player.getX() && bullet.getY() < player.getY() + player.getHeight() && bullet.getY() + bullet.getHeight() > player.getY();
} private boolean checkEnemyHit(Bullet bullet, Enemy enemy) { return bullet.getX() < enemy.getX() + enemy.getWidth() && bullet.getX() + bullet.getWidth() > enemy.getX() && bullet.getY() < enemy.getY() + enemy.getHeight() && bullet.getY() + bullet.getHeight() > enemy.getY();
Vẽ giao diện
Vẽ trạng thái hiện tại của game lên GraphicsContext.
Bao gồm các đối tượng như người chơi, đạn, kẻ địch, hiệu ứng nổ, và các thông tin như điểm số và thời gian còn lại.
Hiện thực: private void render(GraphicsContext gc) { gc.drawImage(backgroundImage, , , 0 0 WIDTH, HEIGHT); player1.draw(gc); player2.draw(gc); for (Bullet bullet : bullets) { bullet.draw(gc);
} for (Enemy enemy : enemies) { enemy.draw(gc);
} for (Explosion explosion : explosions) { explosion.draw(gc);
// Hiển thị điểm số và thời gian gc.setFill(Color.WHITE); gc.setFont(Font.font(20)); gc.fillText("Player 1: " + score1, 10, 30); gc.fillText("Player 2: " + score2, WIDTH - 150, 30); gc.setFill(Color.YELLOW); gc.fillText("Time: " + remainingTime / 1000 + "s", WIDTH / 2 - 50, 30); if (gameOver) { gc.setFill(Color.RED); gc.setFont(Font.font(40)); gc.fillText("Game Over", WIDTH / 2 - 100, HEIGHT / 2 - 20); gc.fillText(winner + " Wins!", WIDTH / 2 - 100, HEIGHT / 2 + 40); }
Thiết lập kết nối
Thiết lập kết nối với server sử dụng socket.
Xử lý tin nhắn nhận được từ server bằng cách khởi động một luồng mới.
Hiện thực: private void connectToServer() { try { socket = new Socket("192.168.1.5", 5000); out = new PrintWriter(socket.getOutputStream(), true); in = new BufferedReader(new
InputStreamReader(socket.getInputStream())); new Thread(() -> { try {
String message; while ((message = in.readLine()) != null) { handleServerMessage(message);
Gửi, nhận và cập nhật một số trạng thái, đối tượng khi đã kết nối
Xử lý các tin nhắn nhận được từ server, bao gồm thông báo bắt đầu game, cập nhật điểm số, và các thông tin khác liên quan đến trạng thái game.
Nhận dữ liệu về các kẻ địch từ server và tạo mới các đối tượng kẻ địch dựa trên dữ liệu này.
Xử lý tin nhắn từ server khi có bắn đạn từ một trong hai người chơi.
Cập nhật vị trí của một trong hai người chơi dựa trên tin nhắn từ server.
Gửi trạng thái của người chơi (vị trí hiện tại) đến server.
Cập nhật vị trí của người chơi dựa trên sự kiện di chuyển của người chơi.
Gửi yêu cầu tạo kẻ địch đến server.
Gửi tin nhắn đến server.
Kết thúc trò chơi khi thời gian hết hoặc một người chơi chiến thắng.
Gửi thông báo kết thúc game và người chiến thắng đến server.
Hiện thực: private void handleServerMessage(String message) { if (message == null || message.isEmpty()) {
System.err.println("Nhận được tin nhắn trống từ server"); return;
String command = parts[ ];0 switch (command) { case "GAME_START": serverStartTime = Long.parseLong(parts[ ]);1 gameStarted = true; break; case "BOTH_PLAYERS_CONNECTED": bothPlayersConnected = true; break; case "SCORE_UPDATE": if (parts.length == ) {3 score1 = Integer.parseInt(parts[ ]);1 score2 = Integer.parseInt(parts[ ]);2
} break; case "PLAYER1": updatePlayerState(player1, message); break; case "PLAYER2": updatePlayerState(player2, message); break; case "FIRE1": handleFireMessage(message, true); break; case "FIRE2": handleFireMessage(message, false); break; case "GAME_OVER": if (parts.length == ) {2 gameOver = true; winner = parts[ ];1
} break; case "SPAWN_ENEMIES": spawnEnemiesFromServer(parts); break; case "ENEMY_DESTROYED": if (parts.length == ) {2 int enemyIndex = Integer.parseInt(parts[ ]);1 if (enemyIndex >= 0 && enemyIndex < enemies.size()) { Enemy destroyedEnemy = enemies.get(enemyIndex); addExplosion(destroyedEnemy.getX(), destroyedEnemy.getY()); playExplosionSound(); enemies.remove(enemyIndex);
} break; case "PLAYER_HIT": if (parts.length == ) {2 int playerHit = Integer.parseInt(parts[ ]);1
Player hitPlayer = (playerHit == ) ? 1 player1 : player2; hitPlayer.decrementHealth(); addExplosion(hitPlayer.getX() + hitPlayer.getWidth() / , 2 hitPlayer.getY() + hitPlayer.getHeight() / );2 playExplosionSound();
System.out.println("Nhận tin nhắn không xác định từ server: " + message); break;
} private void spawnEnemiesFromServer(String[] enemyData) { enemies.clear(); // Xóa tất cả kẻ địch hiện tại for int ( i = ; i < enemyData.1 length; i += ) {3 double x = Double.parseDouble(enemyData[i]); double y = Double.parseDouble(enemyData[i + ]);1 boolean movingDown = Boolean.parseBoolean(enemyData[i + ]);2 Image enemyImage = movingDown ? enemy1Image : enemy2Image; enemies.add(new Enemy(x, y, enemyImage, movingDown));
} private void handleFireMessage(String message, boolean isPlayer1) {
String[] parts = message.split(","); if (parts.length == ) {3 double x = Double.parseDouble(parts[ ]);1 double y = Double.parseDouble(parts[ ]);2 if ((isPlayer1 && !this.isPlayer1) || (!isPlayer1 && this.isPlayer1)) { bullets.add(new Bullet(x, y, isPlayer1 ? Color.YELLOW : Color.ORANGE, isPlayer1));
System.err.println("Invalid FIRE message received: " + message); }
} private void updatePlayerState(Player player, String message) {
String[] parts = message.split(","); if (parts.length >= ) {2 try { double x = Double.parseDouble(parts[ ]);1 player.setX(x);
System.err.println("Invalid x coordinate received: " + parts[ ]);1 }
System.err.println("Invalid player state message received: " + message);
String message; if (isPlayer1) { message = String.format("PLAYER1,%.2f", player1.getX()); } else { message = String.format("PLAYER2,%.2f", player2.getX()); } sendMessage(message);
} private void updateLocalPlayerPosition() { if (isPlayer1) { player1.update();
} private void requestSpawnEnemies() { sendMessage("REQUEST_SPAWN_ENEMIES");
} private void sendMessage(String message) { out.println(message);
} private void endGame() { gameOver = true; if (score1 > score2) { winner = "Player 1";
} else if (score2 > score1) { winner = "Player 2";
Lớp kẻ địch
public class Enemy { private double x, ;y private final double width = 50, height = 50; private double speed = ;1 private Image sprite; private boolean movingDown; public Enemy(double x, double y, Image sprite, boolean movingDown) { this.x = x; this.y = y; this.sprite = sprite; this.movingDown = movingDown;
} public void update() { if (movingDown) { y += speed;
} public boolean isOffScreen() { return (movingDown && y > HEIGHT) || (!movingDown && y < -height); } public void draw(GraphicsContext gc) { gc.drawImage(sprite, , , x y width, height);
// Getters public double getX() { return x; } public double getY() { return y; } public double getWidth() { return width; } public double getHeight() { return height; } public boolean isMovingDown() { return movingDown; }
Lớp người chơi
public static class Player { private double x, ;y private int width; private int height; private int health = ;3 private double velocity = ;0 private static final double SPEED = ;5 private boolean movingLeft = false; private boolean movingRight = false; private static final int MIN_X = ;0 private static final int MAX_X = WIDTH - 50; private Image sprite; public Player(double x, double y, Image sprite) { this.x = x; this.y = y; this.width = 100; this.height = 100; this.sprite = sprite;
} public void startMovingLeft() { movingLeft = true; movingRight = false;
} public void startMovingRight() { movingRight = true; movingLeft = false;
} public void stopMoving() { movingLeft = false; movingRight = false;
} public double getWidth() { return width;
} public double getHeight() { return height;
} public void setX(double x) { this.x = x;
} public void setY(double y) { this.y = y;
} public void moveLeft() { velocity = -SPEED;
} public void moveRight() { velocity = SPEED;
} public void update() { if (movingLeft) { x -= SPEED;
} public void draw(GraphicsContext gc) { gc.drawImage(sprite, , , x y width, height);
} public boolean isDead() { return health HEIGHT;
} public void draw(GraphicsContext gc) { gc.setFill(color); gc.fillRect( , , x y width, height);
Lớp vụ nổ
public static class Explosion { private double x, ;y private int lifetime; private final int maxLifetime = 20; private final double maxRadius = 30; public Explosion(double x, double y) { this.x = x; this.y = y; this.lifetime = ;0
} public void draw(GraphicsContext gc) { double radius = (double) lifetime / maxLifetime * maxRadius; double opacity = 1 - (double) lifetime / maxLifetime; gc.setGlobalAlpha(opacity); gc.setFill(Color.ORANGE); gc.fillOval(x - radius, y - radius, radius * , radius * );2 2 gc.setStroke(Color.RED); gc.strokeOval(x - radius, y - radius, radius * , radius * );2 2 gc.setGlobalAlpha(1.0);
} public boolean isFinished() { return lifetime > maxLifetime;
Khỏi tạo cho trò chơi
private Player player1; private Player player2; private List bullets = new ArrayList(); private List explosions = new ArrayList(); private List enemies = new ArrayList(); private boolean player1Shoot = false; private boolean player2Shoot = false; private boolean isPlayer1; private Socket socket; private PrintWriter out; private BufferedReader in; private long lastShotTime; private int score1, score2; private boolean gameOver; private String winner; private long gameStartTime; private long remainingTime; private static final int GAME_DURATION = 60000; // Thời gian trò chơi (60 giây) private static final int WIDTH = 1000; // Độ rộng màn hình game private static final int HEIGHT = 800; // Độ cao màn hình game private AudioClip shootSound; private AudioClip explosionSound; private Image backgroundImage; private Image player1ship; private Image player2ship; private Random random = new Random(); private static final int MIN_ENEMIES_PER_SPAWN = ;3 private static final int MAX_ENEMIES_PER_SPAWN = ;5 private long lastEnemySpawnTime = ;0 private Image enemy1Image; private Image enemy2Image; private static final long ENEMY_SPAWN_INTERVAL = 20000; // 20 giây private boolean gameStarted = false; private long serverStartTime = ;0 private static final long GAME_START_DELAY = 5000; // 5 giây đếm ngược private boolean bothPlayersConnected = false;
Giao diện
Hướng dẫn Chơi
Cách bắt đầu trò chơi
- Mở game và nhập địa chỉ IP của máy chủ, kết nối qua cổng 5000.
- Chờ đến khi máy chủ thông báo kết nối thành công.
- Khi đủ 2 người chơi, máy chủ sẽ đếm ngược 10 giây trước khi bắt đầu trò chơi.
Điều khiển
- Di chuyển: sử dụng các phím mũi tên để di chuyển máy bay. o →: Di chuyển qua phải o ←: Di chuyển qua trái
- Bắn: Nhấn phím Space để bắn đạn.
- Di chuyển: sử dụng phím A, D để di chuyển máy bay. o A: Di chuyển sang trái o D: Di chuyển sang phải
- Bắn: Nhấn phím Shift để bắn đạn
Luật chơi
- Quái vật sẽ xuất hiện từ cả trên và dưới màn hình.
- Quái vật sẽ di chuyển từ trên xuống dưới và từ dưới lên trên.
- Khi bắn trúng quái vật, người chơi cộng 5 điểm.
- Khi bị bắn trúng, đối thủ sẽ nhận 10 điểm.
- Nếu quái vật ra khỏi màn hình mà không bị tiêu diệt, người chơi ở phía đối diện bị trừ 5 điểm.