การเขียนโปรแกรมด้วย PocketC# ตอนที่ 8-GameI

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

การเขียนเกมนั้น มีเทคนิคพิเศษเพิ่มเติมกว่าการเขียนโปรแกรมทั่วไปอีกเล็กน้อยครับ เทคนิคแรกที่เราจะลองใช้กันนั่นคือเทคนิคที่เรียกว่า double buffering

เนื่องจากโปรแกรมเกมนั้น ต้องการการวาดภาพบนจอและเปลี่ยนแปลงภาพอย่างรวดเร็วมาก เหมือนการฉายภาพยนตร์ เพื่อให้เกมตอบสนองต่อผู้เล่นอย่างทันทีทันใด ไม่มีการกระตุกหรือกะพริบของภาพ ซึ่งหากเราใช้คำสั่งวาดภาพต่าง ๆ ลงบนหน้าจอโดยตรงด้วยวิธีปกติ จะทำให้ภาพเกิดการกระตุกมาก ยิ่งมีวัตถุที่จะวาดลงบนจอมากเท่าไร ภาพก็จะกระตุกมากเท่านั้น

การใช้ double buffering มีแนวทางคือการสร้างหน้าจอเสมือน ขึ้นมาอีกจอหนึ่ง เรียกว่า backbuffer มีขนาด Bitmap(240,320) เท่ากับขนาดหน้าจอปกติ โดยเราจะวาดวัตถุต่าง ๆ ที่ใช้ในเกมของเราลงในหน้าจอเสมือนนี้ให้เรียบร้อยก่อน แล้วจึงใช้คำสั่ง Graphics.DrawImage() ดึงภาพจากหน้าจอเสมือนทั้งจอมาวางบนจอภาพปกติทำให้วัตถุที่วาดไว้ปรากฏบนหน้าจอพร้อม ๆ กันทีเดียวด้วย method OnPaint(..) และ OnPaintBackground(..) เช่นเดียวกับการฉายภาพยนตร์ทีละเฟรมต่อเนื่องกันไปจึงไม่มีอาการกระตุกของภาพ

เทคนิคประการที่สองคือ sprite ซึ่งหมายถึงภาพวัตถุที่เราจะนำมาแสดงบนหน้าจอ sprite จะเป็นภาพ Bitmap สี่เหลี่ยมเล็ก ๆ ซึ่งเป็นภาพของวัตถุที่เราจะนำมาแสดง เราจึงต้องสร้าง sprite ขึ้นมาก่อน จากนั้นจึงกำหนดการเคลื่อนที่ของ sprite เช่น ให้รับทิศทางการเคลื่อนที่จาก keyboard หรือ stylus หรือให้รับการสั่งงานจากตัวโปรแกรมเพื่อเปลี่ยนทิศทาง เมื่อได้ทิศทางการเคลื่อนที่ของ sprite แล้วก็เอา sprite ของเราไปแปะในหน้าจอเสมือน เมื่อแปะ sprite เสร็จแล้วจึงจะดึงหน้าจอเสมือนมาแสดงที่จอภาพปกติด้วยเทคนิค double buffering

เทคนิคประการที่สามคือ Timer Tick หมายถึงการรับสัญญาณนาฬิกาจาก pocketPC เพื่อนำมาใช้ในการกำหนดความเร็วของเกม และสั่งงานให้ sprite ต่าง ๆ เคลื่อนที่ รวมถึงการ update หน้าจอตามช่วงเวลาที่กำหนดเพื่อให้ภาพที่ปรากฏบนจอมีการเคลื่อนไหวหรือเคลื่อนที่ตามต้องการ เป็นการกำหนด Frame rate คือจำนวนภาพที่จะนำออกแสดงใน 1 วินาที ยิ่ง Frame rate สูง ภาพก็จะราบรื่น แต่ทั้งนี้ขึ้นกับความสามารถของ CPU ภายใน PocketPC ของเรา

In game programming, I use 3 techniques. The first is double buffering. It means drawing all graphics in Bitmap(240,320) that represent full screen (backbuffer) and use Graphics.DrawImage() to show that Bitmap to screen. This technique will reduce screen flickering. The second is using sprite. The third is using Timer to control the movement of sprite and screen update.

มาลองเขียนเกมกันดูเลยครับ

เราจะใช้ graphic.cs ที่เราเขียนไว้แล้วมาดัดแปลงและเขียนคำสั่งเพิ่ม เพื่อสร้างเกมขับเครื่องบินสักลำหนึ่งดีไหมครับ

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

ในเกมของเราช่วงแรกนี้จะมี sprite หรือวัตถุ 1 อัน คือเครื่องบิน 1 ลำ เท่านั้นที่ปรากฏบนหน้าจอครับ

In this section, I will create only one sprite. It is an airplane using Bitmap(60,60). Use your graphic.cs but please delete all methods about graphic and sound. Rewrite new codes as follow…

ในส่วนของ class grx : Form เราจะประกาศการใช้ Graphics grBuffer; เพื่อใช้เขียนภาพในหน้าจอเสมือน และประกาศสร้าง Bitmap สำหรับ sprite รูปเครื่องบินของเรา ขนาด 60x60 pixels และ Bitmap สำหรับหน้าจอเสมือน ขนาด 240x320 pixels จากนั้นก็กำหนดตำแหน่งของ sprite ด้วย spriteX,spriteY และกำหนดตำแหน่งควบคุมที่ต้องการให้ sprite เคลื่อนที่ ด้วย controlX,controlY ปิดท้ายด้วยการประกาศใช้ Timer เพื่อนำ Timer Tick มาควบคุมการทำงานของเกมของเราครับ

…

class grx : Form

{

//Graphics declaration

Graphics grBuffer;

Bitmap spritePlane=new Bitmap(60,60);

Bitmap buffer=new Bitmap(240,320);

//Sprite position

int spriteX,spriteY;

//control position

int controlX,controlY;

//set timer

Timer m_tmr=new Timer();

…

ในส่วนของ method grx() เราจะใช้คำสั่ง grBuffer=Graphics.FromImage(buffer); เพื่อกำหนดว่าเราจะวาดภาพลงใน buffer ก่อน จากนั้นเรียก method initSprite() เพื่อสร้าง sprite ด้วยการวาดรูปเครื่องบินลงใน spritePlane ซึ่งเป็น Bitmap ขนาด 60x60 pixels เสร็จแล้วก็เข้าส่วนคำสั่งเกี่ยวกับ Timer โดยตั้งค่า Interval=10 (ประมาณ 1/100 วินาที) เพื่อส่งค่านับเวลาไปยัง method OnTimerTick เพื่อกำหนดทิศทางการเคลื่อนที่ของเครื่องบินของเรา และแปะภาพเครื่องบินลงในหน้าจอเสมือนหรือ buffer แล้วจึงใช้ Invalidate(); เพื่อบังคับให้ OnPaint(..) นำภาพจากหน้าจอเสมือนขึ้นมาแสดงบนจอภาพปกติ ก็เป็นอันเสร็จกระบวนการสร้างเกมเครื่องบินเบื้องต้นครับ ยุ่งหน่อยแต่ไม่ยากใช่ไหมครับ

…

grx()

{

//Form header

MinimizeBox=false;

Text=”Game”;

//create graphics in Buffer

grBuffer=Graphics.FromImage(buffer);

initSprite();

//set time interval

m_tmr.Interval=10;

//Specify the timer callback method

t_tmr.Tick+=new EventHandler(OnTimerTick);

//Start the timer

m_tmr.Enabled=true;

}

…

initSprite() is a method to draw a blue airplane into sprite Bitmap.

ในส่วน method initSprite() เราจะวาดรูปเครื่องบินง่าย ๆ ด้วยคำสั่งวาดรูปสี่เหลี่ยมและวงรี เป็นเครื่องบินสีน้ำเงิน โดยเริ่มด้วยคำสั่ง Graphics.FromImage เพื่อกำหนดให้วาดรูปบน spritePlane ปิดท้ายด้วยคำสั่ง Dispose() เพื่อคืนหน่วยความจำที่ใช้วาดรูปให้แก่ระบบเป็นการประหยัด memory โดยในตอนต้นเราจะกำหนดจุดเริ่มของ sprite ที่ 0,0 และกำหนดทิศทางให้ sprite เคลื่อนไปที่จุด 100,250

…

private void initSprite()

{

spriteX=0;

spriteY=0;

controlX=100;

controlY=250;

Graphics gr=Graphics.FromImage(spritePlane);

gr.FillRectangle(new SolidBrush(Color.Blue),21,5,8,35);

gr.FillRectangle(new SolidBrush(Color.Blue),4,14,45,10);

gr.FillRectangle(new SolidBrush(Color.Blue),13,33,25,7);

gr.FillEllipse(new SolidBrush(Color.Yellow),22,15,6,9);

gr.Dispose();

}

…

ในส่วน method OnTimerTick(..) จะทำงานเมื่อได้รับสัญญาณนาฬิกาจากระบบ จากนั้นจะกำหนดทิศทางการเคลื่อนที่ของ spriteX,Y เพื่อมุ่งไปยัง controlX,Y โดยจะขยับไปทีละ 3 pixels เสร็จแล้วก็ใช้คำสั่ง DrawImage เพื่อแปะ sprite รูปเครื่องบินที่เราวาดไว้แล้ว ไปยังหน้าจอเสมือนหรือ buffer ตามด้วยคำสั่ง Invalidate() เพื่อบังคับให้ method OnPaint(..) ทำงาน และ Dispose() เพื่อคืนหน่วยความจำ

…

private void OnTimerTick(object sender, EventArgs evArgs);

{

if (spriteX<controlX)

spriteX+=3;

if (spriteX>controlX)

spriteX-=3;

if (spriteY<controlY)

spriteY+=3;

if (spriteY>controlY)

spriteY-=3;

grBuffer.DrawImage(spritePlane,spriteX,spriteY);

Invalidate();

grBuffer.Dispose();

}

I use OnTimerTick() method to update sprite position and draw sprite to back buffer. I use OnPaint() method to show back buffer to screen. Please notice that there is no command in OnPaintBackground() method. It means not to repaint screen background. I use this technique to reduce screen flickering.

สำหรับ method OnPaint(..) มีหน้าที่เดียวคือดึงภาพจากหน้าจอเสมือนหรือ buffer ทั้งจอมาฉายแสดงที่หน้าจอปกติ ด้วยคำสั่ง DrawImage และที่ขาดไม่ได้คือ method OnPaintBackground ซึ่งภายในจะไม่มีคำสั่งอะไรเลย เหตุที่ต้องมี method OnPaintBackground ว่างๆ ไว้อย่างนี้เป็นเทคนิคพิเศษเพื่อบังคับไม่ให้มีการวาด Background ซ้ำครับ ทำให้ช่วยลดการกะพริบที่หน้าจอได้อีกมากครับ

…

protected override void OnPaintBackground(PaintEventArgs p)

{

}

protected override void OnPaint(PaintEventArgs p)

{

p.Graphics.DrawImage(buffer,0,0);

}

}//end class

…

เรียบร้อยแล้วครับสำหรับเกมแรกของเรา คราวนี้เขียนโปรแกรมเยอะหน่อย เพราะทุกส่วนมีความเกี่ยวเนื่องกันครับ ขอให้เพื่อน ๆ ลอง build และ run ดู จะเห็นภาพเครื่องบินบินถอยหลังจากจุด 0,0 มายังจุด 100,250 ที่ด้านล่างกลางจอ เพื่อรอรับคำสั่งต่อไปครับ ระหว่างรออ่านตอนที่ 9 เพื่อน ๆ อาจจะลองเปลี่ยนค่า m_tmr.Interval=10; เป็นตัวเลขอื่น เพื่อดู frame rate ยิ่งค่ามากขึ้นก็ยิ่งเคลื่อนที่ช้าลงครับ นอกจากนี้อาจจะเปลี่ยนค่าจุดตั้งต้น spriteX,Y และจุดควบคุม controlX,Y เพื่อให้เครื่องบินบินไปยังจุดที่ต้องการ หรือเปลี่ยนค่า spriteX+=3; ฯลฯ เป็นตัวเลขอื่น เพื่อดูความนิ่มนวลของการเคลื่อนที่ ยิ่งตัวเลขน้อยก็ยิ่งนิ่มนวลแต่ต้องแลกกับความเร็วที่ลดลง ยิ่งค่ามากก็ยิ่งเคลื่อนที่เร็ว แต่ก็ไม่ราบรื่น และถ้ามากเกินไป อาจจะเห็นรอยของภาพเครื่องบินลำเดิมปรากฏอยู่เพราะภาพจาก sprite เดิมถูกทับไม่หมดครับ

Build and Run, you will see a blue airplane flying from top left to bottom middle of screen. You may change timer.Interval, spriteX,Y ,controlX,Y to learn how it works.

ขอให้สนุกครับ

[email protected]

19 สิงหาคม 2550

 

 

 

 

 

 

 

 

 

 

 

Hosted by www.Geocities.ws

1