Oop lab/bullets
- หน้านี้เป็นส่วนหนึ่งของ oop lab
ในปฏิบัติการนี้ เราจะทดลองใช้ interface Entity และทดลองสร้าง subclass นอกจากนี้เรายังจะได้ใช้ collection LinkedList เพื่อเก็บข้อมูล entity ด้วย
หมายเหตุ: การใช้ inheritance ในการเพิ่มประสิทธิภาพของคลาสนั้น ในบางมุมพิจารณาว่าเป็นเทคนิคที่อาจจะไม่ได้ดีที่สุดในการออกแบบคลาส อย่างไรก็ตาม ในส่วนนี้เพื่อฝึกเขียน เราจะใช้วิธีดังกล่าวไปก่อน
เนื้อหา
เริ่มต้น
สร้างโปรเจ็ค bulletgame จากนั้นสร้างคลาส BulletGame ที่ extends มาจาก BasicGame ตามที่เราเคยสร้างตามปกติ เพิ่มเมท็อดที่ต้อง implement ทั้งหมด (init, update, render) จากนั้นทดลองรันให้โปรแกรมแสดงหน้าจอว่าง ๆ
interface Entity
ในลักษณะเดียวกับที่เราทำในคลิป YouTube เรื่อง interface เราจะสร้าง interface Entity เพื่อใช้ระบุเมท็อดพื้นฐานทั้งหมดที่ "ของ" ที่จะอยู่บนหน้าจอเกมของเราจะต้องเขียน
public interface Entity {
void render(Graphics g);
void update(int delta);
}
สังเกตว่าเมท็อด render จะส่ง Graphics g มาด้วย และเมท็อด update ก็จะส่ง delta มาให้ด้วยเช่นกัน
เราจะประกาศ interface ทิ้งไว้ก่อน ส่วนโค้ดที่เรียกใช้งานนั้น เราจะเขียนหลักเขียนคลาส Bullet แล้ว
คลาส Bullet
คลาส Bullet นี้จะเป็นคลาสพื้นฐานของกระสุนทั้งหมด โดยทำหน้าที่หลักเพียงแค่วาดรูปกระสุนด้วยวงกลม และสามารถขยับกระสุนไปในทิศทางแกน y เท่านั้น
สังเกตว่าในคลาสนี้ เราให้ field x และ y เป็น private แต่เราสร้าง getters/setters ดังนี้
- getX, getY เป็น public
- setXY เป็น protected เพราะว่าเราต้องการให้ subclass คำนวณการเคลื่อนที่ของ Bullet ได้ แต่ต้องเรียกผ่านทางเมท็อดนี้
public class Bullet implements Entity {
private static final float BULLET_SIZE = 5;
private float x;
private float y;
public Bullet(float x, float y) {
this.setXY(x,y);
}
@Override
public void render(Graphics g) {
g.fillOval(getX(), getY(), BULLET_SIZE, BULLET_SIZE);
}
@Override
public void update(int delta) {
y += 10;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
protected void setXY(float x, float y) {
this.x = x;
this.y = y;
}
}
เพิ่ม Bullet ใน BulletGame
เราจะสร้าง bullet เพื่อทดลองในโปรแกรม ในคลาส BulletGame โดยในโปรแกรมจะพิจารณาวัตถุทั้งหมดเป็น Entity
เพิ่มฟิลด์ entities ในคลาส BulletGame
private LinkedList<Entity> entities;
สังเกตว่าเราใช้ LinkedList ในการเก็บ entity เพราะว่าสุดท้าย เราจะต้องจัดการลบ entity ที่ไม่ได้ใช้งานออกจากระบบด้วย entity พวกนี้ เช่น กระสุนที่วิ่งออกไปนอกจอแล้ว เป็นต้น ถ้าเราใช้ ArrayList การลบข้อมูลดังกล่าวจะไม่ค่อยมีประสิทธิภาพเท่า LinkedList
ปรับ render และ update ให้เรียก render และ update ทุก ๆ entity ใน entities
@Override
public void render(GameContainer container, Graphics g) throws SlickException {
for (Entity entity : entities) {
entity.render(g);
}
}
@Override
public void update(GameContainer container, int delta) throws SlickException {
for (Entity entity : entities) {
entity.update(delta);
}
}
สุดท้าย สร้างกระสุนใน init
@Override
public void init(GameContainer container) throws SlickException {
entities.add(new Bullet(200,0));
}
ทดลองเรียกโปรแกรมให้ทำงาน ดูว่ากระสุนวิ่งหรือไม่
กระสุนแบบมีทิศทาง
เราจะสร้างกระสุนแบบมีทิศทาง โดยกระสุนดังกล่าว เมื่อสร้าง จะมีการกำหนดทิศทางและความเร็วได้ กระสุน DirectionalBullet นี้เป็น subclass ของ Bullet
public class DirectionalBullet extends Bullet {
private float dir;
private float velocity;
public DirectionalBullet(float x, float y, float dir, float velocity) {
super(x, y);
this.dir = dir;
this.velocity = velocity;
}
public float getVelocity() {
return velocity;
}
public float getDir() {
return dir;
}
}
สังเกตว่า:
- เรามี field dir และ velocity แต่ทั้งสอง field มีแต่ getters ไม่มี setters เนื่องจากเราไม่ต้องการให้มีการเปลี่ยนแปลงทิศทางของกระสุน เราจึงไม่สร้างเมท็อดเอาไว้
แบบฝึกหัด: ปรับตำแหน่ง
ให้แก้คลาส DirectionalBullet ให้ปรับตำแหน่งของกระสุนให้วิ่งไปในทิศทางที่กำหนดด้วยความเร็วที่กำหนด โดยให้พิจารณาค่าทิศทางเป็นมุมที่คิดแบบองศา
แก้ส่วน init ใน BulletGame ให้สร้าง directional bullet ดังนี้
@Override
public void init(GameContainer container) throws SlickException {
entities.add(new DirectionalBullet(320,240,70,10));
}
อย่าลืมทดสอบโดยทดลองปรับมุมเป็นค่าต่าง ๆ และความเร็วเป็นค่าต่าง ๆ ด้วย
วิ่งแบบเป็นคลื่น SineBullet
เราจะสร้างคลาส SinceBullet ที่ทำให้กระสุนวิ่งเป็นคลื่น วิธีการที่เราจะทำให้กระสุนวิ่งเป็นคลื่นนั้น คือเราจะมี track position ที่วิ่งในลักษณะเดียวกับ directional bullet แต่เมื่อเวลาผ่านไป เราจะปรับทิศทางที่เราย้ายออกไปทางซ้ายและขวาเป็นฟังก์ชันแบบ sine แสดงดังตัวอย่างด้านล่าง
แบบฝึกหัด: ให้คุณเขียนคลาส SineBullet ให้เป็น subclass ของ DirectionalBullet และมี constructor แบบเดียวกับ DirectionalBullet ในการเคลื่อนที่ให้กระสุนวิ่งไปในทิศที่กำหนด แต่มีการส่ายไปมาด้วย
ย้ายโค้ดส่วน track position ไปที่ DirectionalBullet
สังเกตว่าความสามารถในการ track ตำแหน่งนั้นเป็นประโยชน์มาก ในการสร้างกระสุนที่วิ่งแบบมีทิศทาง เราจะย้ายความสามารถดังกล่าวมาไว้ในคลาส DirectionalBullet ดังนี้
เพิ่ม field trackX และ trackY
private float trackX;
private float trackY;
และเพิ่ม getters
protected float getTrackX() {
return trackX;
}
protected float getTrackY() {
return trackY;
}
จากนั้นใน constructor เราจะเก็บค่าเริ่มต้นของ trackX และ trackY
public DirectionalBullet(float x, float y, float dir, float velocity) {
// ...
trackX = x;
trackY = y;
}
เราจะต้อง update ค่า field trackX และ trackY ในเมท็อด update ในลักษณะด้านล่าง
public void update(int delta) {
trackX = (float)(getX() + dx);
trackY = (float)(getY() + dy);
setXY(trackX, trackY);
}
อย่างไรก็ตาม โค้ดดังกล่าวมีปัญหาเมื่อ subclass ต้องการใช้ค่า trackX และ trackY ในเมท็อด update ของ subclass เพราะว่า