Day 24 - AndEngine
สวัสดีครับ บทความนี้เป็นบทความที่ 24 แล้วนะครับ ที่ผมจะมาเขียน ในซีรีย์ Learn 30 Android Libraries in 30 days
สำหรับบทความทั้งหมด อ่านได้จากด้านล่างครับ
- Day 1 : AndroidStaggered Grid
- Day 2 : Paralloid
- Day 3 : Retrofit
- Day 4 : SwipeRefreshLayout
- Day 5 : Android GraphView
- Day 6 : Holo Color Picker
- Day 7 : Android Async Http
- Day 8 : Crashlytics
- Day 9 : Butter Knife
- Day 10 : Android Annotations
- Day 11 : DateTimePicker
- Day 12 : Circular Progress Button
- Day 13 : ViewPager
- Day 14 : ViewPagerIndicator
- Day 15 : FadingActionBar
- Day 16 : AutofitTextView
- Day 17 : SwipeListView
- Day 18 : ShowcaseView
- Day 19 : GreenDAO
- Day 20 : AndroidViewAnimation
- Day 21 : ActiveAndroid
- Day 22 : Twitter4J
- Day 23 : ListViewAnimations
- Day 24 : AndEngine
- Day 25 : EazeGraph
- Day 26 : Cardslib
- Day 27 : AdapterKit
- Day 28 : WeatherLib
- Day 29 : FlatUI
- Day 30 : Android Firebase
สำหรับวันนี้ขอนำเสนอเรื่อง AndEngine เป็น Open Source OpenGL Game Engine ที่เอาไว้เขียนเกมบน Android ตัวนึงที่ได้รับความนิยมในระดับหนึ่ง ส่วนตัวผมเคยได้ยิน แต่เพิ่งจะได้ลองใช้ก็วันนี้แหละครับ
Installation
ขั้นตอนการติดตั้ง AndEngine อาจจะยุ่งยากซักหน่อยนะครับ Tutorial ในเว็บก็เก่า แถมเป็น Eclipse แต่ในบทความนี้ผมจะติดตั้งผ่าน Android Studio อาศัยความมั่ว แล้วก็ลองไปลองมา เอานะครับ :D
เริ่มแรก ให้ทำการ clone project จาก Github มาที่เครื่องก่อน
git clone https://github.com/nicolasgramlich/AndEngine.git
หรือจะก็อปปี้ไฟล์ .zip เอาก็ได้ ถ้าเป็นไฟล์ zip ก็แตกไฟล์ออกมา จะได้โฟลเดอร์ AndEngine-GLES2
ต่อมาทำการ import เข้าสู่โปรเจ็ค สมมติว่า ตอนนี้ใน Android Studio สร้างโปรเจ็คไว้แล้ว 1 โปรเจ็ค ก็ทำการเพิ่ม Module โดยไปที่ตัวโปรเจ็ค คลิกขวา ์New => Module จากนั้นเลือก Import Existing Project
เลือกโปรเจ็คที่โหลดมาเมื่อกี้
Android Studio จะทำการแปลงไฟล์จาก Eclipse ให้เป็น Android Studio จะได้ผลลัพธ์แบบนี้ (บางไฟล์จะถูกย้ายโฟลเดอร์)
Moved Files:
------------
Android Gradle projects use a different directory structure than ADT
Eclipse projects. Here's how the projects were restructured:
* AndroidManifest.xml => andEngine/src/main/AndroidManifest.xml
* jni/ => andEngine/src/main/jni/
* libs/armeabi-v7a/libandengine.so => andEngine/src/main/jniLibs/armeabi-v7a/libandengine.so
* libs/armeabi/libandengine.so => andEngine/src/main/jniLibs/armeabi/libandengine.so
* libs/x86/libandengine.so => andEngine/src/main/jniLibs/x86/libandengine.so
* res/ => andEngine/src/main/res/
* src/ => andEngine/src/main/java/
แต่เอ๊ะ พอมาดูที่ console ปรากฎว่ามี error แฮะ
Error แบบนี้ เนื่องจากว่าเครื่องผม ไม่มีตัว SDK Platform เวอร์ชัน15 มีต่ำสุดก็ 16 ฉะนั้น ผมเลยเปลี่ยนให้ของ AndEngine เป็น 16 ด้วย โดยแก้ที่ไฟล์ build.gradle
ในโมดูล andEngine
ดังนี้
apply plugin: 'com.android.library'
android {
compileSdkVersion 16
...
}
จากนั้น ทำการเพิ่ม Module AndEngine เข้าไปในโปรเจ็คหลักของเรา โดยเปิด build.gradle
ของโมดูลหลัก โดยเพิ่มนี้ลงไป
dependencies {
compile project (':andEngine')
}
หากใครไม่ถนัดวิธีการเพิ่ม Module แบบนี้ ก็ทำแบบ GUI โดยคลิกขวาที่ Module เรา เลือก Open Module Settings แล้วก็เลือก :andEngine
กด Sync Project with Gradle
Error:Execution failed for task ':andEngine:compileReleaseNdk'.
> NDK not configured.
Download the NDK from http://developer.android.com/tools/sdk/ndk/.Then add ndk.dir=path/to/ndk in local.properties.
(On Windows, make sure you escape backslashes, e.g. C:\\ndk rather than C:\ndk)
ปรากฎว่ามี Error ด้านบนครับ เนื่องจากว่า Android Studio ไม่ยอมคอมไพล์ ndk ในโฟลเดอร์ jni
เหมือนว่ามันมองไม่เห็น SourceSets ก็เลยจัดการเพิ่่มให้มันซะ โดยเข้าไปแก้ไฟล์ build.gradle
ของ appEngine โดยเพิ่มนี้ลงไป ภายในบล็อก android {}
apply plugin: 'com.android.library'
android {
...
sourceSets {
main {
jni.srcDirs = []
}
}
...
}
ทำการ Sync Gradle อีกที เรียบร้อยครับ คราวนี้แอพเราก็ใช้ AndEngine ได้แล้ว!
Getting Started
ทำการสร้าง Activity ขึ้นมาเลย 1 คลาส ผมตั้งชื่อให้มันว่า AndEngineActivity
จากนั้นก็ extends คลาส SimpleBaseGameActivity
ซึ่งเป็นคลาสของ AndEngine ดังนี้
package com.devahoy.learn30androidlibraries.day24;
import org.andengine.engine.options.EngineOptions;
import org.andengine.entity.scene.Scene;
import org.andengine.ui.activity.SimpleBaseGameActivity;
public class AndEngineActivity extends SimpleBaseGameActivity {
@Override
public EngineOptions onCreateEngineOptions() {
return null;
}
@Override
protected void onCreateResources() {
}
@Override
protected Scene onCreateScene() {
return null;
}
}
หากใคร มีปัญหา การ import หรือว่า รันโค๊ดด้านบนไม่ได้ แสดงว่าท่านยัง add library ของ AndEngine ไม่ถูกต้องนะครับ
มาดูว่าแต่ละเมธอด มันมีหน้าที่และไว้ใช้ทำอะไรกันบ้าง เริ่มจาก
onCreateEngineOptions() : เป็นหัวใจหลักของ AndEngine เลย เพราะเป็นตัวที่เอาไ้ว้กำหนด option ต่างๆของ Engine เช่นกำหนด Camera กำหนด frame rate ให้กับตัวเกม
onCreateResources() : เมธอดนี้ เอาไว้สำหรับสร้างไฟล์ resource ต่างๆ เช่น พวกรูปภาพ เสียง ที่ใช้ในเกม แล้วทำการโหลดไว้ที่ VRAM (หน่วยความจำ)
onCreateScene() : เมธอดนี้จะถูกเรียกสุดท้าย หลักจาก 2 เมธอดด้านบนเสร็จแล้ว เป็นเมธอดที่เอาไว้แสดง Scene หรือโชว์ ไฟล์รูปภาพจาก VRAM ขึ้นหน้าจอ
ทีนี้ เราจะเริ่มทำเกม เริ่มแรก ต้องมี Camera
โดยเราจะทำการ สร้าง Camera ที่เมธอด onCreatEngineOptions()
และในส่วน onCreateScene()
ก็จะโชว์ Scene โดยยังไม่มีการโหลดรูปภาพมาแสดงครับ ได้โค๊ดด้งนี้
package com.devahoy.learn30androidlibraries.day24;
import org.andengine.engine.camera.Camera;
import org.andengine.engine.options.EngineOptions;
import org.andengine.engine.options.ScreenOrientation;
import org.andengine.engine.options.resolutionpolicy.IResolutionPolicy;
import org.andengine.engine.options.resolutionpolicy.RatioResolutionPolicy;
import org.andengine.entity.scene.Scene;
import org.andengine.ui.activity.SimpleBaseGameActivity;
import java.io.IOException;
public class AndEngineActivity extends SimpleBaseGameActivity {
private static final int CAMERA_WIDTH = 800;
private static final int CAMERA_HEIGHT = 480;
private Camera mCamera;
@Override
public EngineOptions onCreateEngineOptions() {
mCamera = new Camera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT);
IResolutionPolicy resolutionPolicy =
new RatioResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT);
return new EngineOptions(true,
ScreenOrientation.LANDSCAPE_FIXED,
resolutionPolicy,
mCamera);
}
@Override
protected void onCreateResources() throws IOException {
}
@Override
protected Scene onCreateScene() {
Scene scene = new Scene();
scene.getBackground().setColor(0.2f, 0.5f, 0.7f);
return scene;
}
}
ด้านบน ที่เมธอด onCreaetEngineOptions()
เป็นการกำหนด Camera มีขนาดความกว้าง ยาว เท่ากับ 800x480 ส่วน EngineOptions()
ก็มี parameter ดังนี้ครับ
- true : คือ FullScreen เลือกให้ GameEngine อยู่ในโหลด FullScreen
- ScreenOrientation.LANDSCAPE_FIXED : คือโหมดแบบแนว Landscape แบบ Fixed ขนาด
- resolutionPolicy : อันนี้ไม่แน่ใจ น่าจะเป็นการ Scale ขนาดหน้าจอตามมือถือแต่ละขนาด รึเปล่า?
- mCamera : ใช้ Camera ที่เราสร้างไว้ เพื่อกำหนด ขนาด ความสูง ความกว้าง ของ GameScreen
ต่อมาที่เมธอด onCreateScene()
ทำการสร้าง Scene ขึ้นมา จากนั้นก็ใส่พื้นหลังให้มันซะ ทดลองรันโปรแกรมดูครับ จะได้ผลแบบนี้ (มีแค่พื้นหลัง)
อ้อ ถ้าใช้ Android Emulator ต้องให้แน่ใจว่าให้รองรับ GPU Emulator ด้วยนะครับ
โหลดรูปภาพ
หลักจากที่เราได้ background แล้ว ต่อมาเราจะลองทำการแสดงรูปภาพ บนหน้าจอบ้าง สำหรับการใช้รูปภาพ และการโหลดรูปภาพ เราจะใช้ Texture
และ TextureRegion
โดยทำการโหลดไฟล์รูปภาพที่ต้องการมาไว้ใน VRAM ในเมธอด onCreateResource()
ดังนี้
private ITextureRegion mTextureRegion;
private ITexture mTexture;
@Override
protected void onCreateResources() throws IOException {
mTexture = new AssetBitmapTexture(getTextureManager(), getAssets(), "player.png");
mTextureRegion = TextureRegionFactory.extractFromTexture(mTexture);
mTexture.load();
}
@Override
protected Scene onCreateScene() {
Scene scene = new Scene();
scene.getBackground().setColor(0.2f, 0.5f, 0.7f);
Sprite sprite = new Sprite(mCamera.getWidth() / 2, mCamera.getHeight() / 2,
mTextureRegion, getVertexBufferObjectManager());
scene.attachChild(sprite);
return scene;
}
จากโค๊ดด้านบน เราทำการโหลดรูปภาพ player.png
ซึ่งผมโหลดมาจากเว็บนี้ Kenny OpenGameArt หรือใครจะโหลดจากที่นี่ก็ได้ เซฟไปไว้ที่โฟลเดอร์ /src/main/assets/
ชื่อว่า player.png
จากนั้นก็โหลด Texture เก็บไว้ที่ VRAM ก่อน ต่อมาในส่วน onCreateScene()
ก็ทำการสร้าง Sprite
ขึ้นมา โดยใช้ TextureRegion
จาก Texture
นั่นแหละครับ แสดงที่ตำแหน่ง กึ่งกลางของหน้าจอ จากนั้น ก็ให้ scene.attachChild()
เพื่อใส่รูปภาพเข้าไปใน Scene
ทดลองรันโปรแกรม จะได้ผลลัพธ์ดังนี้ (แต่ไม่รู้ทำไม รูปมันไม่ยอมกึ่งกลางนะ ?)
แสดง Animation
สุดท้ายละครับ ลองแสดง Animation ดีกว่า ในส่วนนี้เราจะใช้รูปจากนี้นะครับ OldMan OpenGameArt
การทำ Animation จะใช้คลาส AnimatedSprite
, TiledTextureRegion
และ BitmapTextureAtlas
ดังนี้
private AnimatedSprite mAnimatedSprite;
private TiledTextureRegion mTiledTextureRegion;
private BitmapTextureAtlas mBitmapTextureAtlas;
@Override
protected void onCreateResources() throws IOException {
mBitmapTextureAtlas = new BitmapTextureAtlas(getTextureManager(), 100, 100, TextureOptions.BILINEAR);
mTiledTextureRegion = BitmapTextureAtlasTextureRegionFactory
.createTiledFromAsset(mBitmapTextureAtlas, getAssets(), "npc-oldman1.png", 0, 0, 4, 1);
mBitmapTextureAtlas.load();
}
@Override
protected Scene onCreateScene() {
Scene scene = new Scene();
scene.getBackground().setColor(0.2f, 0.5f, 0.7f);
mAnimatedSprite = new AnimatedSprite(200, 200, mTiledTextureRegion, this.getVertexBufferObjectManager());
long[] frameDuration = {500, 500, 500, 500};
mAnimatedSprite.animate(frameDuration);
scene.attachChild(mAnimatedSprite);
return scene;
}
อธิบายทีละขั้นตอนเลยนะครับ เริ่มจาก ทำการสร้าง BitmapTextureAtlas
ขนาด 100x100 จากนั้นก็โหลดรูป npc-oldman1.png
ของเรามาใส่ใน BitmapTextureAtlas
ซึ่งไฟล์ npc-oldman1.png
มันมีขนาด 80x37 BitmapTextureAtlas
ต้องมีขนาดที่ใหญ่กว่าขนาดของ image spritesheet ของเรานะครับ ส่วน parameter ตรง
.createTiledFromAsset(mBitmapTextureAtlas, getAssets(), "npc-oldman1.png", 0, 0, 4, 1);
ในส่วน 0, 0, 4, 1
: 0, 0
สองตัวแรกคือระยะ x, y ของ TextureAtlas เริ่มจากตำแหน่ง 0, 0 ส่วน 4, 1
คือ จำนวนการแบ่ง คอลัม และ แถวนะครับ อย่างเช่น ภาพนี้ ถูกแบ่ง column = 4 และ row = 1 มันก็จะได้ดังภาพนี้
เวลาเล่น animation มันก็จะขยับไปทีละ block ถูกต้อง แต่ถ้าสมมติ เราเปลี่ยน column = 2 และ row = 1 ภาพมันจะเป็นแบบนี้
เวลาแสดงผล มันจะผิดเนื่องจาก มันจะมี spritesheet 2 อันทับกัน ไม่เชื่อลองเปลี่ยนค่าตรงนี้ดูได้ครับ เช่นกัน หากเราใส่เป็น column = 4 row = 2 ภาพจะเป็นแบบนี้
ส่วนในเมธอด onCreateScene()
เราสร้างออปเจ็ค AnimatedSprite
โดยกำหนด ให้มันแสดงผลที่ตำแหน่ง x = 200, y = 200 จากนั้นก็ สั่ง animation ให้มัน ด้วยการใช้ frame 4 frame แต่ละ frame มีความยาว 0.5วินาที (จำนวน frame ควรจะเท่ากับ ขนาดของ column นะครับ หรือน้อยกว่า column ได้ แต่ว่าห้ามมีขนาดมากกว่าจำนวน column)
สุดท้าย รองรันโปรแกรมครับ จะเห็นไอ้ตัว npc มันขยับๆ
สรุป
หลักจากได้ลอง AndEngine เป็นครั้งแรกเลยครับ ที่ได้จับ เมื่อก่อน ตอนที่รู้สึกสนใจด้านพัฒนาเกม Android ก็ชั่งใจอยู่ว่าจะเลือกอะไรระหว่าง AndEngine กับ Libgdx สุดท้ายก็ตัดสินใจเลือก libgdx เพราะว่า เข้าไปหน้าเว็บ AndEngine แล้วมันไม่รู้จะไปไหนต่อ Documents ก็ไม่มีอะไรเลย แถมตอนนั้นอ่านหนังสือเล่มนี้ Beginning Android 4 Games Development แล้วเห็นว่าคนเขียนหนังสือ คือคนเดียวกับคนสร้าง libgdx ทำให้ยิ่งตัดสินใจง่ายขึ้น
หลักจากทำบทความนี้เสร็จ ต้องบอกเลยว่า ความปรทับใจแรกนั้น ไม่ประทับใจเลยครับ เป็นเพราะว่า Documents ห่วยมากๆ Community ก็ค่อนข้างน้อย แถม Code ไม่ได้อัพเดทเป็นเวลานานแล้ว เทียบกับ Libgdx ซึ่งฝั่งนั้นดูจะดีกว่ามาก ในเรื่องของ Documents และการ active ของพวก Contributor ต่างๆ แถมเป็น Cross-Platform อีกต่างหาก
สุดท้าย บทความนี้อาจจะมีผิดพลาดบ้างนะครับ เนื่องจากผมเพิ่งจะศึกษาวันแรกเท่านั้น หากใครที่มีประสบการณ์ผ่านมาอ่าน ตรงไหนผิดพลาด รบกวนช่วยบอกด้วยนะครับ ขอบคุณครับ
- Authors
- Name
- Chai Phonbopit
- Website
- @Phonbopit