ผลต่างระหว่างรุ่นของ "Oop lab/maze game"
Jittat (คุย | มีส่วนร่วม) |
Jittat (คุย | มีส่วนร่วม) |
||
แถว 204: | แถว 204: | ||
== ตัว Pacman และการเคลื่อนที่ == | == ตัว Pacman และการเคลื่อนที่ == | ||
+ | === แสดง Pacman === | ||
+ | === ทิศทางการวิ่ง และการรับการกดแป้น === | ||
+ | === หยุดวิ่งเมื่อปล่อยปุ่ม === | ||
+ | === การวิ่งให้ตรงช่อง (แต่ยังทะลุกำแพง) === | ||
+ | === เก็บกวาดข้อมูลทิศทาง === | ||
+ | === จัดการวิ่งให้อยู่ในกำแพง === | ||
== จุดและการกิน == | == จุดและการกิน == | ||
== คะแนน == | == คะแนน == |
รุ่นแก้ไขเมื่อ 09:14, 4 ตุลาคม 2557
- หน้านี้เป็นส่วนหนึ่งของ oop lab
- เนื้อหาส่วนนี้ ถ้าต้องการดูเป็นภาษา JavaScript กรุณาดูที่ 01219245/cocos2d/Maze
ในส่วนนี้เราจะพิจารณาการเขียนเกมที่เป็นตารางและผู้เล่นเครื่องที่ไปมาในตาราง เกมที่เป็นตัวอย่างคลาสสิกของเกมตระกูลนี้คือ PacMan หน้าตาของเกมนี้แสดงดังด้านล่าง
ขั้นตอน
เราจะค่อย ๆ เขียนโปรแกรมไปทีละขั้น ๆ ดังนี้
- แสดงแผนที่
- แสดงตัวผู้เล่นและขยับตัวผู้เล่น แบ่งเป็นขั้นย่อย ๆ หลายขั้น
- แสดงตัวผู้เล่น
- ขยับตัวผู้เล่นตามการกดปุ่ม โดยไม่สนใจแผนที่
- ขยับตัวผู้เล่นให้ตรงช่องแผนที่ แต่อาจวิ่งทะลุกำแพง
- ขยับตัวผู้เล่นให้ตรงแผนที่และไม่วิ่งทะลุกำแพง
- แสดงจุด และให้ผู้เล่นกินจุดได้
ในหลาย ๆ ขั้นตอนสามารถเขียนได้หลายแบบ โดยมีข้อดีและข้อเสียแตกต่างกันไป ดังนั้นในการเขียนจริง ผู้เขียนอาจจะเลือกเขียนไม่เหมือนในเอกสารนี้ก็ได้
โค้ดทั้งหมดอยู่ที่: 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));
}
}
}
}