ผลต่างระหว่างรุ่นของ "Oop lab/maze game"

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา
แถว 204: แถว 204:
  
 
== ตัว Pacman และการเคลื่อนที่ ==
 
== ตัว Pacman และการเคลื่อนที่ ==
 +
=== แสดง Pacman ===
 +
=== ทิศทางการวิ่ง และการรับการกดแป้น ===
 +
=== หยุดวิ่งเมื่อปล่อยปุ่ม ===
 +
=== การวิ่งให้ตรงช่อง (แต่ยังทะลุกำแพง) ===
 +
=== เก็บกวาดข้อมูลทิศทาง ===
 +
=== จัดการวิ่งให้อยู่ในกำแพง ===
  
 
== จุดและการกิน ==
 
== จุดและการกิน ==
  
 
== คะแนน ==
 
== คะแนน ==

รุ่นแก้ไขเมื่อ 09:14, 4 ตุลาคม 2557

หน้านี้เป็นส่วนหนึ่งของ oop lab
เนื้อหาส่วนนี้ ถ้าต้องการดูเป็นภาษา JavaScript กรุณาดูที่ 01219245/cocos2d/Maze

ในส่วนนี้เราจะพิจารณาการเขียนเกมที่เป็นตารางและผู้เล่นเครื่องที่ไปมาในตาราง เกมที่เป็นตัวอย่างคลาสสิกของเกมตระกูลนี้คือ PacMan หน้าตาของเกมนี้แสดงดังด้านล่าง

Java-mazegame.png

ขั้นตอน

เราจะค่อย ๆ เขียนโปรแกรมไปทีละขั้น ๆ ดังนี้

  • แสดงแผนที่
  • แสดงตัวผู้เล่นและขยับตัวผู้เล่น แบ่งเป็นขั้นย่อย ๆ หลายขั้น
    • แสดงตัวผู้เล่น
    • ขยับตัวผู้เล่นตามการกดปุ่ม โดยไม่สนใจแผนที่
    • ขยับตัวผู้เล่นให้ตรงช่องแผนที่ แต่อาจวิ่งทะลุกำแพง
    • ขยับตัวผู้เล่นให้ตรงแผนที่และไม่วิ่งทะลุกำแพง
  • แสดงจุด และให้ผู้เล่นกินจุดได้

ในหลาย ๆ ขั้นตอนสามารถเขียนได้หลายแบบ โดยมีข้อดีและข้อเสียแตกต่างกันไป ดังนั้นในการเขียนจริง ผู้เขียนอาจจะเลือกเขียนไม่เหมือนในเอกสารนี้ก็ได้

โค้ดทั้งหมดอยู่ที่: https://github.com/jittat/slick2d-mazegame

แสดงแผนที่

หน้าจอเปล่า/คลาส MazeGame

เช่นเคย เราจะเริ่มโดยสร้างโปรแกรมที่แสดงหน้าจอเปล่า

public class MazeGame extends BasicGame {

  public static final int GAME_WIDTH = 640;
  public static final int GAME_HEIGHT = 480;

  public MazeGame(String title) {
    super(title);
  }
  
  @Override
  public void init(GameContainer container) throws SlickException {
  }

  @Override
  public void render(GameContainer container, Graphics g) throws SlickException {
  }

  @Override
  public void update(GameContainer container, int delta) throws SlickException {
  }
  
  public static void main(String[] args) {
    try { 
      MazeGame game = new MazeGame("Maze Game");
      AppGameContainer container = new AppGameContainer(game);
      container.setDisplayMode(GAME_WIDTH, GAME_HEIGHT, false);
      container.setMinimumLogicUpdateInterval(1000 / 60);
      container.start();
    } catch (SlickException e) {
      e.printStackTrace();
    }
  }
}

คลาส Maze: แสดงหนึ่งช่อง

เราจะสร้างคลาส Maze ที่แสดงแผนที่ โดยในการแสดงแผนที่เราจะแสดงโดยใช้รูปเล็ก ๆ ขนาด 40 x 40 มาประกอบกันเพื่อแสดงเป็นแผนที่ ดังนั้นในขั้นแรกให้สร้างไฟล์ wall.png ที่เป็นรูปกำแพงขนาด 40 x 40 และเก็บไว้ในไดเร็กทอรี res

ก่อนที่เราจะจัดการเรื่องการเก็บข้อมูลต่าง ๆ เรามาทำคลาส Maze ให้แสดงรูปนี้ให้ได้ก่อน คลาส Maze จะมี constructor เราจะสร้าง Image ของช่องที่จะเป็นผนัง

public class Maze {
  private Image wallImage = null;

  public Maze() {
    try {
      wallImage = new Image("res/wall.png");
    } catch (SlickException e) {
      e.printStackTrace();
    }
  }

  // ...
}

เราจะสร้างเมท็อด render เพื่อให้คลาส MazeGame มาเรียกให้ Maze แสดงผล เราจะแสดงผลแบบง่าย ๆ ก่อน ดังนี้

  public void render() {
    wallImage.draw(100, 100);
  }

จากนั้นในคลาส MazeGame เราก็ไปเพิ่มการสร้าง maze และการสั่งให้ maze แสดงผล

ด้านล่างแสดงโค้ดในคลาส MazeGame ที่เราแก้ไข

class MazeGame extends BasicGame {
  private Maze maze;
  // ...
  
  @Override
  public void init(GameContainer container) throws SlickException {
    maze = new Maze();
  }

  @Override
  public void render(GameContainer container, Graphics g) throws SlickException {
    maze.render();
  }
  // ...
}

การเก็บและจัดการแผนที่

สำหรับเกมนี้ เพื่อความง่าย เราจะระบุขนาดของแผนที่ให้เหมาะสมกับหน้าจอของเกมของเราไปเลย สังเกตว่าหน้าจอของเราขนาด 640 x 480 ถ้าช่องของเราขนาด 40 x 40 เราจะแสดง maze ได้ 12 แถว x 16 คอลัมน์ เราจะเว้นแถวบนกับแถวล่างไว้เพื่อใช้แสดงข้อมูลอื่น ๆ ดังนั้นเราจะได้ว่าเราจะใช้แผนที่ขนาด 16 คอลัมน์ x 10 แถว

เราจะเพิ่มค่าเหล่านี้เป็นค่าคงที่ของคลาส ดังนี้

public class Maze {
  static public int ROWS = 10;
  static public int COLS = 16;
  static public int BLOCK_SIZE = 40;

  // ...
}

เกม maze จำนวนมากมายสามารถเปลี่ยนแผนที่ได้ สำหรับเกมนี้แม้เรายังไม่ได้พัฒนาไประดับนั้น แต่เราก็จะเก็บแผนที่แยกไว้เป็นค่าคงที่ของคลาส ถ้าในอนาคตต้องการเปลี่ยนแผนที่ก็น่าจะทำได้โดยง่าย ในคลาส Maze ประกาศค่าคงที่ของคลาสเพิ่มเติมเป็นแผนที่ โดยเราจะเก็บเป็นอาร์เรย์ของสตริง ดังนี้

  static private String[] MAP = {
    "################",
    "#..............#",
    "#.#.###..###.#.#",
    "#...#......#...#",
    "#.#...#..#...#.#",
    "#.#...#..#...#.#",
    "#...#......#...#",
    "#.#.###..###.#.#",
    "#..............#",
    "################"
  };

ในการเก็บข้อมูลดังกล่าว ถ้าต้องการทราบข้อมูลของแผนที่แถวที่ r คอลัมน์ที่ c (นับดัชนีเริ่มที่ 0) เราสามารถสั่งได้ดังนี้

MAP[r].charAt(c)

ด้วยวิธีดังกล่าว เมท็อด render ของคลาส Maze แก้ใหม่ได้เป็นดังนี้

  public void render() {
    for (int r = 0; r < ROWS; r++) {
      for (int c = 0; c < COLS; c++) {
        if (MAP[r].charAt(c) == '#') {
          wallImage.draw(leftX + (c * BLOCK_SIZE), 
              topY + (r * BLOCK_SIZE));
        }
      }
    }
  }

เก็บกวาดโค้ด

ก่อนที่เราจะไปต่อเราจะเก็บกวาดโค้ดให้อ่านง่ายขึ้น

เราจะมีการอ่านค่าจาก MAP บ่อยมาก เราจะเขียนเมท็อด mapAt เพื่ออ่านค่าจากอาร์เรย์ MAP

  private char mapAt(int r, int c) {
    return MAP[r].charAt(c);
  }

นอกจากนี้การคำนวณคำแหน่งที่การวาดช่องนั้น จะเป็นการคำนวณที่เราทำบ่อยมาก ดังนั้นเราจะเพิ่มเมท็อดสองเมท็อด ดังนี้

  public int getCellX(int r, int c) {
    return leftX + c * BLOCK_SIZE;
  }
  
  public int getCellY(int r, int c) {
    return topY + r * BLOCK_SIZE;
  }

ด้วยเมท็อดที่เราเพิ่มขึ้น เมท็อด render จะถูกแก้ให้อ่านง่ายขึ้นเป็นดังนี้

  public void render() {
    for (int r = 0; r < ROWS; r++) {
      for (int c = 0; c < COLS; c++) {
        if (mapAt(r,c) == '#') {
          wallImage.draw(getCellX(r,c), getCellY(r,c));
        }
      }
    }
  }

ตัว Pacman และการเคลื่อนที่

แสดง Pacman

ทิศทางการวิ่ง และการรับการกดแป้น

หยุดวิ่งเมื่อปล่อยปุ่ม

การวิ่งให้ตรงช่อง (แต่ยังทะลุกำแพง)

เก็บกวาดข้อมูลทิศทาง

จัดการวิ่งให้อยู่ในกำแพง

จุดและการกิน

คะแนน