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

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา
แถว 362: แถว 362:
  
 
จากนั้นในส่วนตรวจสอบการกดปุ่ม เราจะสั่งให้ pacman ขยับ ถ้าเราพิจารณาเมท็อด update เราจะพบโค้ดดังด้านล่าง
 
จากนั้นในส่วนตรวจสอบการกดปุ่ม เราจะสั่งให้ pacman ขยับ ถ้าเราพิจารณาเมท็อด update เราจะพบโค้ดดังด้านล่าง
 
  
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
แถว 369: แถว 368:
 
             x -= 10;
 
             x -= 10;
 
         }
 
         }
         // ... และอีก 4 ทิศทาง
+
         // ... และอีก 3 ทิศทาง
 
     }
 
     }
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
 +
: ''จะมาเขียนต่อ''
  
 
== โครงสร้างคลาส (ปลายทาง) ==
 
== โครงสร้างคลาส (ปลายทาง) ==
  
 
== แสดง Maze ==
 
== แสดง Maze ==

รุ่นแก้ไขเมื่อ 01:21, 3 ตุลาคม 2559

หน้านี้เป็นส่วนหนึ่งของ oop lab

ขั้นตอนคร่าว ๆ

  • สร้าง gradle project ของ GDX
  • (ขั้นตอนเพิ่มเติม) เขียน pacman เคลื่อนที่ไปมาบนหน้าจอ
  • เขียนส่วนแสดงแผนที่และจุด
  • บังคับ pacman เดินไปมาตามช่อง (วิ่งทะลุกำแพง)
  • บังคับ pacman เดินไปมาตามช่อง (ไม่ทะลุแล้ว)
  • กินจุด
  • แสดงคะแนน

สร้าง gradle project

เราจะสร้าง gradle project ของเกมด้วยโปรแกรม gdx-setup

ดาวนโหลด gdx-setup.jar

จากนั้นสั่ง

java -jar gdx-setup.jar

Gdx-gradle.png

ใส่ค่าให้ครบ เลือกเฉพาะ desktop เกม เพื่อความสะดวกในการทำ tutorial ให้ตั้งชื่อ game class ว่า PacmanGame

จากนั้นให้ import เข้าไปใน Eclipse หรือ NetBeans และทดลองรัน ถ้าเจอปัญหาว่าหารูป badlogic ไม่เจอ อย่าลืมแก้ไข Working Directory ที่ Run Configurations...

Gitmark.png พอเริ่มทำงานได้ อย่าลืม commit หมั่น commit เป็นระยะ ๆ ตามส่วนของงานย่อยที่ทำสำเร็จ

บังคับ pacman วิ่งไปมา

ในส่วนแรกเราจะทดลองเขียนโดยไม่แยกคลาสที่จัดการสถานะกับคลาสที่แสดงผล เพื่อให้เห็นภาพของ update loop ของเกมที่ชัดเจน จากนั้นเราจะค่อยแยกส่วนสถานะออกเป็นคลาสกลุ่ม World และส่วนแสดงผลออกเป็นคลาสกลุ่ม WorldRenderer ต่อไป

ปรับขนาดหน้าจอ, ลบรูป badlogic

เกมของเราจะเป็นเกมตาราง ช่องของตารางจะมีขนาด 40 x 40 จุด มีความกว้างของตาราง 20 ช่อง (กว้าง 800 จุด) เราจะปรับขนาดของหน้าจอเมื่อเราเริ่มสร้าง application โดยแก้ไขขนาดหน้าจอที่คลาส DesktopLauncher ในโปรเจ็คย่อย xxxx-desktop

public class DesktopLauncher {
    public static void main (String[] arg) {
        LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
        config.width = 800;
        config.height = 600;
        new LwjglApplication(new PacmanGame(), config);
    }
}

จากนั้นในโปรเจ็คย่อย xxxx-core ให้แก้ไขคลาส PacmanGame (เป็นคลาสหลักที่เรา generate มาจากขั้นตอนก่อน) โดยให้ลบส่วนที่เกี่ยวข้องกับ Texture img และแก้ส่วน render ให้เป็นดังด้านล่าง

public class PacmanGame extends ApplicationAdapter {
    SpriteBatch batch;
    
    @Override
    public void create () {
        batch = new SpriteBatch();
    }

    @Override
    public void render () {
        super.render();
    }
    
    @Override
    public void dispose () {
        batch.dispose();
    }
}

ทดลองเรียกให้ทำงาน (เรียก run ที่ DesktopLauncher) สังเกตว่าหน้าจอเป็นสีดำและมีขนาดตามที่เราระบุ

เราต้องการจะใช้ sprite batch อันเดียวที่สร้างใน PacmanGame ดังนั้นเราจะแก้ให้ทุกคลาสสามารถเข้าถึงได้ โดยเปลี่ยนประเภท field เป็น public

public class PacmanGame extends ApplicationAdapter {
    public SpriteBatch batch;

    //...
}

แยกคลาส GameScreen

คลาส PacmanGame ในปัจจุบันนั้น extends ApplicationAdapter ซึ่งเป็นคลาสที่ flexible มากในการเขียนเกม อย่างไรก็ตาม ในกรณีที่เราต้องการหลาย ๆ หน้าจอ (screen) เราสามารถเริ่มต้นเขียนโดย inherit มาจากคลาส Game จะสะดวกกว่า

เราจะปรับคลาสที่เป็นคลาสแม่ของ PacmanGame เป็นดังนี้ อย่าลืมกดให้ Eclipse/NetBeans import คลาสต่าง ๆ มาให้ครบด้วย

public class PacmanGame extends Game {

จากนั้นสร้างคลาส GameScreen จากนั้นแก้ให้คลาส extends ScreenAdapter

import com.badlogic.gdx.ScreenAdapter;

public class GameScreen extends ScreenAdapter {

}

หมายเหตุ: คลาสที่เป็น Adapter เป็นคลาสที่ implement method ของ interface บางอย่างจนครบด้วย method ว่าง ๆ ทำให้เวลาเราจะเขียนคลาสที่ implement บาง interface เราสามารถเขียนได้โดยไม่ต้องคอยไล่ implement ทุกเมท็อดเอง

เราจะสร้าง GameScreen โดยให้มีการอ้างถึง PacmanGame ได้ด้วย (เพื่อที่จะอ้างถึง SprintBatch batch) เราจะเพิ่ม constructor และ field ดังนี้

public class GameScreen extends ScreenAdapter {

    private PacmanGame pacmanGame;

    public GameScreen(PacmanGame pacmanGame) {
        this.pacmanGame = pacmanGame;
    }
}

จากนั้นในเมท็อด create ใน PacmanGame ให้สั่ง setScreen ด้วย instance ของ GameScreen

    public void create () {
        batch = new SpriteBatch();
        setScreen(new GameScreen(this));
    }

ทดลองสั่งให้โปรแกรมทำงาน อย่างไรก็ตาม เราจะยังไม่เห็นอะไรเลย

ให้ทดลองเขียนเมท็อด render ในคลาส GameScreen ให้พิมพ์ข้อความและ delta แล้วทดลองสั่งให้ทำงาน เมื่อมีหน้าจอเกมสีดำขึ้นมา ถ้าไปกดดู console จะเห็นข้อความพร้อมเลขขึ้นมาเรื่อย ๆ ตามที่มีการเรียก render

    @Override
    public void render(float delta) {
        System.out.println("Hello " + delta);
    }

ถ้าได้ผลตามที่ระบุนี้ แสดงว่าเราปรับแก้ให้โปรแกรมมาเรียกใช้งาน GameScreen ได้แล้ว ในขั้นตอนไปเราจะแก้ GameScreen ให้แสดงตัว pacman วิ่งไปมาบนหน้าจอ

แสดง pacman

เราจะใช้ assets ที่เตรียมไว้ให้แล้ว ถ้าต้องการแบบอื่นสามารถเปลี่ยนรูปได้ตามสะดวก ดาวน์โหลดไฟล์รูปที่ http://theory.cpe.ku.ac.th/~jittat/courses/ooplab/pacman/ จากนั้นจัดเก็บในไดเร็กทอรี core/assets (ที่เดียวกับที่เก็บไฟล์ badlogic.jpg)

เราจะโหลดรูปโดยสร้างเป็น Texture และเก็บเป็น field ชื่อ pacmanImg โดยแก้คลาส GameScreen ดังนี้

public class GameScreen extends ScreenAdapter {

    // ...
    private Texture pacmanImg;

    public GameScreen(PacmanGame pacmanGame) {
        // ...
        pacmanImg = new Texture("pacman.png");
    }
}

เราจะวาดรูปลงบนหน้าจอผ่านทาง SpriteBatch (batch จากคลาส PacmanGame) แก้เมท็อด render ให้วาด pacmanImg ลงไปที่ตำแหน่ง 100,100 โดยโค้ดเป็นดังนี้

    @Override
    public void render(float delta) {
        SpriteBatch batch = pacmanGame.batch;
        batch.begin();
        batch.draw(pacmanImg, 100, 100);
        batch.end();
    }

ทดลองสั่งให้ทำงาน

เราควรจะ commit งานเป็นระยะ ๆ อย่าลืมเพิ่ม assets พวก png ลงใน git repository ด้วย

Gitmark.png หลังจากแก้โปรแกรมให้แสดง pacman ได้ ก็ควรจะ commit สักครั้ง

pacman วิ่ง

ในขั้นต่อไปเราจะทำให้ pacman เคลื่อนที่โดยบังคับไม่ได้ก่อน เราจะเพิ่ม field x และ y เพื่อแทนตำแหน่งของ pacman

public class GameScreen extends ScreenAdapter {

    // ...
    private int x;
    private int y;

    public GameScreen(PacmanGame pacmanGame) {
        // ...
        x = 100;
        y = 100;
    }
}

จากนั้นในเมท็อด render เราจะปรับค่า x ขึ้น 5 หน่วย เพื่อให้ pacman วิ่งไปทางขวา

    public void render(float delta) {
        x += 5;
        // ...
        batch.draw(pacmanImg, x, y);
    }

ทดลองรัน...

เราจะเห็น pacman วิ่งเป็นสาย เพราะว่าเราไม่ได้ลบหน้าจอ เราจะลบหน้าจอด้วยคำสั่งของ OpenGL โดยเพิ่มคำสั่งสองบรรทัดนี้ลงไปตอนต้นของ render

        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

หลังจากทดลองรัน เราจะเห็น pacman วิ่งไปทางขวาแล้ว

ก่อนจะไปขั้นต่อไป เราจะแยกเมท็อด update ออกมาจาก render โดยเมท็อดนี้จะทำหน้าที่หลักในการแก้ไขสถานะต่าง ๆ ของเกม คำสั่งที่จะแยกออกมาจาก render คือ x += 5; นั่นเอง

ประกาศเมท็อด update ดังนี้ (หมายเหตุ: อย่าลืมลบ x += 5 ออกจาก render ด้วย ไม่เช่นนั้นขั้นตอนถัดไปจะพังได้)

    private void update(float delta) {
        x += 5;    
    }

และเรียก update จาก render (อย่าลืมลบ x+= 5 จาก render)

    public void render(float delta) {
        update(delta);
        // ...
    }

ทดลองรันอีกครั้งเพื่อทดสอบว่าโปรแกรมทำงานถูกต้องแล้ว (โปรแกรมควรทำงานเหมือนเดิมก่อนจะแยก update)

รับการกดปุ่ม

เราจะใช้เมท็อด Gdx.input.isKeyPressed ในการตรวจสอบการกดปุ่ม ในการตรวจสอบดังกล่าว เราจะต้องส่งรหัสหมายเลขปุ่ม ซึ่งถูกระบุเป็นค่าคงที่ไว้ในคลาส Keys

เมท็อด update ด้านล่างบังคับ pacman ให้ขยับซ้ายขวา

    private void update(float delta) {
        if(Gdx.input.isKeyPressed(Keys.LEFT)) {
            x -= 10;
        }
        if(Gdx.input.isKeyPressed(Keys.RIGHT)) {
            x += 10;
        }
    }

งานของคุณ: ให้แก้โค้ด update ให้สามารถบังคับ pacman ขยับขึ้นบนและลงล่างได้ด้วย

Gitmark.png หลังจากบังคับ pacman ได้แล้ว ให้ commit งานด้วย

แยกส่วนสถานะกับการแสดงผล

เราจะแยกส่วนจัดการสถานะของเกมทั้งหมด ออกจากส่วนแสดงผล เพื่อให้สามารถทดสอบการทำงานของส่วนจัดการสถานะได้ง่าย (เช่น เขียน unit test เป็นต้น)

เราจะมีสองคลาสใหญ่ ๆ คือ

  • World - จัดการสถานะทั้งหมด
  • WorldRenderer - จัดการวาดรูปต่าง ๆ ของสถานะใน World

โดยถ้าพิจารณาจากโค้ดล่าสุดของเรา ส่วนที่เป็นเมท็อด update จะถูกย้ายไปอยู่ในคลาส World ส่วนที่เกี่ยวกับการวาดรูปในเมท็อด render จะถูกย้ายไปที่ WorldRenderer

เมื่อแก้ไขเสร็จเมท็อด render จะมีลักษณะดังนี้

    public void render(float delta) {
        world.update(delta);
        
        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        worldRenderer.render();
    }

เราจะทยอยแก้ไปทีละขั้น หลักการคร่าว ๆ คือเราจะทยอยสร้างคลาสมาเพื่อ "ครอบ" รายละเอียดแต่ละส่วน เมื่อทำเสร็จแต่ละส่วนเราจะเทสเพื่อรับประกันว่าโปรแกรมยังทำงานได้อยู่

คลาส Pacman

ขั้นแรกเราจะสร้างคลาส Pacman เพื่อจัดการการเคลื่อนที่ของ pacman ก่อน จากนั้นค่อยย้ายการอ้างอิงส่วนนี้ไปไว้ในคลาส World

ก่อนที่เราจะเขียน ให้กลับไปพิจารณาก่อนคร่าว ๆ ว่าในโค้ดเก่าของเรา ส่วนใดบ้างที่เกี่ยวข้องกับกระบวนการ กิจกรรม หรือคุณสมบัติของอะไรต่าง ๆ ที่เราจะย้ายมาใส่ในคลาส pacman

.
.
.
. กรุณาย้อนกลับไปดูโค้ดเก่าก่อนอ่านต่อ
.
.
.

กดปุ่ม "ขยาย" ด้านขวาเพื่อดูเฉลย

  • ตำแหน่ง x, y ของ pacman
  • การสั่งให้ pacman เปลี่ยนตำแหน่งในเมท็อด update (เช่นสั่ง x += 10 เป็นต้น)
  • การอ้างตำแหน่งของ pacman ในการวาด texture

แนวคิดและโค้ดเหล่านี้จะต้องถูกย้ายไปปรากฏในคลาส pacman และจะต้องมีช่องทางให้โค้ดเก่าที่เหลือเรียกใช้ได้ เราจะค่อย ๆ ทยอยย้ายโค้ดเหล่านี้

ด้านล่างเป็นโค้ดเริ่มต้นของคลาส Pacman

public class Pacman {
    private Vector2 position;

    public Pacman(int x, int y) {
        position = new Vector2(x,y);
    }    

    public Vector2 getPosition() {
        return position;    
    }
}

สังเกตว่าเราใช้ Vector2 ในการเก็บตำแหน่งด้วย (อย่าลืมกดให้ import คลาสดังกล่าวเอง) เราสร้างเมท็อด getPosition เพื่อเป็นช่องทางให้สามารถอ่านตำแหน่งของ pacman มาวาดรูปบนหน้าจอได้

อย่างไรก็ตามสังเกตว่า field position นี้เป็น private การกำหนดขอบเขตดังกล่าวรับประกันว่าจะไม่มีโค้ดส่วนอื่นเข้ามาแก้ไขค่า field นี้ได้โดยที่ pacman นั้นไม่ทราบ

เรายังไม่มีโค้ดในส่วนของการปรับตำแหน่ง เราจะเพิ่มโค้ดส่วนนี้ในขั้นตอนถัด ๆ ไป

นำคลาส Pacman ไปใช้ใน GameScreen

เราจะแก้ไข constructor ของ GameScreen ให้เป็นดังด้านล่าง ในโค้ดดังกล่าว เราจะสร้าง object คลาส Pacman เพื่อไปใช้แทน field x และ y ก่อน และลบการประกาศ field x และ y ออก

public class GameScreen extends ScreenAdapter {
    // ...
    
    // int x,y; ----  delete this line
    private Pacman pacman;

    public GameScreen(PacmanGame pacmanGame) {
        this.pacmanGame = pacmanGame;

        pacmanImg = new Texture("pacman.png");

        pacman = new Pacman(100,100);
        
        // x = 100; ---- delete these lines
        // y = 100;
    }
    // ...
}

จากนั้น ในส่วนของการวาด texture เราจะปรับให้อ่านตำแหน่งทาง getPosition

        batch.begin();
        Vector2 pos = pacman.getPosition();
        batch.draw(pacmanImg, pos.x, pos.y);
        batch.end();

เชื่อมโยงการปรับตำแหน่ง

จากนั้นในส่วนตรวจสอบการกดปุ่ม เราจะสั่งให้ pacman ขยับ ถ้าเราพิจารณาเมท็อด update เราจะพบโค้ดดังด้านล่าง

    private void update(float delta) {
        if(Gdx.input.isKeyPressed(Keys.LEFT)) {
            x -= 10;
        }
        // ... และอีก 3 ทิศทาง
    }
จะมาเขียนต่อ

โครงสร้างคลาส (ปลายทาง)

แสดง Maze