ผลต่างระหว่างรุ่นของ "Oop lab/maze game"
Jittat (คุย | มีส่วนร่วม) |
Jittat (คุย | มีส่วนร่วม) |
||
แถว 205: | แถว 205: | ||
== ตัว Pacman และการเคลื่อนที่ == | == ตัว Pacman และการเคลื่อนที่ == | ||
=== แสดง Pacman === | === แสดง Pacman === | ||
+ | เราจะสร้างคลาส Pacman เพื่อจัดการกับการควบคุมตำแหน่งของ Pacman แต่ในส่วนของการแสดงผล เราจะใช้รูปแบบเหมือนในแลบครั้งก่อน กล่าวคือ เราจะมี interface Renderable และมีคลาม PacmanImage ที่แสดงผล Pacman | ||
+ | |||
+ | อย่างไรก็ตาม เพื่อการเทสที่ค่อยเป็นค่อยไป เราจะสร้างคลาส Pacman ที่แสดงผลตัวเองก่อน | ||
+ | |||
+ | ขั้นแรก สร้างรูป pacman ขนาด 40x40 แล้วเก็บไว้ที่ res/pacman.png | ||
+ | |||
+ | คลาส Pacman ในตอนต้นจะมีตำแหน่งของ Pacman เราจะอ้างอิงตำแหน่งของ pacman จากจุดศูนย์กลางของตัว pacman ซึ่งจะทำให้ในการแสดงรูป เราต้องเลื่อนตำแหน่งที่จะแสดงรูป x,y ให้ตรงกับมุมบนซ้ายของ pacman | ||
+ | |||
+ | <syntaxhighlight lang="java"> | ||
+ | public class Pacman { | ||
+ | private int x; | ||
+ | private int y; | ||
+ | private Image image; | ||
+ | |||
+ | Pacman(int x, int y) { | ||
+ | this.x = x; | ||
+ | this.y = y; | ||
+ | try { | ||
+ | this.image = new Image("res/pacman.png"); | ||
+ | } catch (SlickException e) { | ||
+ | e.printStackTrace(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | public void render(Graphics g) { | ||
+ | image.draw(x - 20, y - 20); | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | <syntaxhighlight lang="java"> | ||
+ | public interface Renderable { | ||
+ | void render(Graphics g); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | |||
+ | ด้านล่างเป็น interface Renderable: | ||
+ | |||
+ | <syntaxhighlight lang="java"> | ||
+ | public interface Renderable { | ||
+ | void render(Graphics g); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | <syntaxhighlight lang="java"> | ||
+ | public class PacmanImage implements Renderable { | ||
+ | private Pacman pacman; | ||
+ | private Image image; | ||
+ | private int width; | ||
+ | private int height; | ||
+ | |||
+ | PacmanImage(Pacman pacman) { | ||
+ | this.pacman = pacman; | ||
+ | try { | ||
+ | this.image = new Image("res/pacman.png"); | ||
+ | } catch (SlickException e) { | ||
+ | e.printStackTrace(); | ||
+ | } | ||
+ | width = image.getWidth(); | ||
+ | height = image.getHeight(); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void render(Graphics g) { | ||
+ | image.draw(pacman.getX() - width/2, | ||
+ | pacman.getY() - height/2); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
=== ทิศทางการวิ่ง และการรับการกดแป้น === | === ทิศทางการวิ่ง และการรับการกดแป้น === | ||
=== หยุดวิ่งเมื่อปล่อยปุ่ม === | === หยุดวิ่งเมื่อปล่อยปุ่ม === |
รุ่นแก้ไขเมื่อ 16:24, 5 ตุลาคม 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));
}
}
}
}
ตัว Pacman และการเคลื่อนที่
แสดง Pacman
เราจะสร้างคลาส Pacman เพื่อจัดการกับการควบคุมตำแหน่งของ Pacman แต่ในส่วนของการแสดงผล เราจะใช้รูปแบบเหมือนในแลบครั้งก่อน กล่าวคือ เราจะมี interface Renderable และมีคลาม PacmanImage ที่แสดงผล Pacman
อย่างไรก็ตาม เพื่อการเทสที่ค่อยเป็นค่อยไป เราจะสร้างคลาส Pacman ที่แสดงผลตัวเองก่อน
ขั้นแรก สร้างรูป pacman ขนาด 40x40 แล้วเก็บไว้ที่ res/pacman.png
คลาส Pacman ในตอนต้นจะมีตำแหน่งของ Pacman เราจะอ้างอิงตำแหน่งของ pacman จากจุดศูนย์กลางของตัว pacman ซึ่งจะทำให้ในการแสดงรูป เราต้องเลื่อนตำแหน่งที่จะแสดงรูป x,y ให้ตรงกับมุมบนซ้ายของ pacman
public class Pacman {
private int x;
private int y;
private Image image;
Pacman(int x, int y) {
this.x = x;
this.y = y;
try {
this.image = new Image("res/pacman.png");
} catch (SlickException e) {
e.printStackTrace();
}
}
public void render(Graphics g) {
image.draw(x - 20, y - 20);
}
}
public interface Renderable {
void render(Graphics g);
}
ด้านล่างเป็น interface Renderable:
public interface Renderable {
void render(Graphics g);
}
public class PacmanImage implements Renderable {
private Pacman pacman;
private Image image;
private int width;
private int height;
PacmanImage(Pacman pacman) {
this.pacman = pacman;
try {
this.image = new Image("res/pacman.png");
} catch (SlickException e) {
e.printStackTrace();
}
width = image.getWidth();
height = image.getHeight();
}
@Override
public void render(Graphics g) {
image.draw(pacman.getX() - width/2,
pacman.getY() - height/2);
}
}