Day 24 : AndEngine

Day 24 : AndEngine Cover Image

สวัสดีครับ บทความนี้เป็นบทความที่ 24 แล้วนะครับ ที่ผมจะมาเขียน ในซีรีย์ Learn 30 Android Libraries in 30 days

สำหรับบทความทั้งหมด อ่านได้จากด้านล่างครับ

สำหรับวันนี้ขอนำเสนอเรื่อง 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

Import

Import Project

เลือกโปรเจ็คที่โหลดมาเมื่อกี้

Porject

Translate

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

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

Add Module

กด 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

ปรากฎว่ามี 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 ขึ้นมา จากนั้นก็ใส่พื้นหลังให้มันซะ ทดลองรันโปรแกรมดูครับ จะได้ผลแบบนี้ (มีแค่พื้นหลัง)

Result

อ้อ ถ้าใช้ Android Emulator ต้องให้แน่ใจว่าให้รองรับ GPU Emulator ด้วยนะครับ

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 หรือใครจะโหลดจากที่นี่ก็ได้ Kenny เซฟไปไว้ที่โฟลเดอร์ /src/main/assets/ ชื่อว่า player.png จากนั้นก็โหลด Texture เก็บไว้ที่ VRAM ก่อน ต่อมาในส่วน onCreateScene() ก็ทำการสร้าง Sprite ขึ้นมา โดยใช้ TextureRegion จาก Texture นั่นแหละครับ แสดงที่ตำแหน่ง กึ่งกลางของหน้าจอ จากนั้น ก็ให้ scene.attachChild() เพื่อใส่รูปภาพเข้าไปใน Scene

ทดลองรันโปรแกรม จะได้ผลลัพธ์ดังนี้ (แต่ไม่รู้ทำไม รูปมันไม่ยอมกึ่งกลางนะ ?)

Result2

แสดง Animation

สุดท้ายละครับ ลองแสดง Animation ดีกว่า ในส่วนนี้เราจะใช้รูปจากนี้นะครับ OldMan OpenGameArt

OldMan

การทำ 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 มันก็จะได้ดังภาพนี้

Column4-1

เวลาเล่น animation มันก็จะขยับไปทีละ block ถูกต้อง แต่ถ้าสมมติ เราเปลี่ยน column = 2 และ row = 1 ภาพมันจะเป็นแบบนี้

Column2-1

เวลาแสดงผล มันจะผิดเนื่องจาก มันจะมี spritesheet 2 อันทับกัน ไม่เชื่อลองเปลี่ยนค่าตรงนี้ดูได้ครับ เช่นกัน หากเราใส่เป็น column = 4 row = 2 ภาพจะเป็นแบบนี้

Column4-2

ส่วนในเมธอด onCreateScene() เราสร้างออปเจ็ค AnimatedSprite โดยกำหนด ให้มันแสดงผลที่ตำแหน่ง x = 200, y = 200 จากนั้นก็ สั่ง animation ให้มัน ด้วยการใช้ frame 4 frame แต่ละ frame มีความยาว 0.5วินาที (จำนวน frame ควรจะเท่ากับ ขนาดของ column นะครับ หรือน้อยกว่า column ได้ แต่ว่าห้ามมีขนาดมากกว่าจำนวน column)

สุดท้าย รองรันโปรแกรมครับ จะเห็นไอ้ตัว npc มันขยับๆ

Result 3

สรุป

หลักจากได้ลอง AndEngine เป็นครั้งแรกเลยครับ ที่ได้จับ เมื่อก่อน ตอนที่รู้สึกสนใจด้านพัฒนาเกม Android ก็ชั่งใจอยู่ว่าจะเลือกอะไรระหว่าง AndEngine กับ Libgdx สุดท้ายก็ตัดสินใจเลือก libgdx เพราะว่า เข้าไปหน้าเว็บ AndEngine แล้วมันไม่รู้จะไปไหนต่อ Documents ก็ไม่มีอะไรเลย แถมตอนนั้นอ่านหนังสือเล่มนี้ Beginning Android 4 Games Development แล้วเห็นว่าคนเขียนหนังสือ คือคนเดียวกับคนสร้าง libgdx ทำให้ยิ่งตัดสินใจง่ายขึ้น

หลักจากทำบทความนี้เสร็จ ต้องบอกเลยว่า ความปรทับใจแรกนั้น ไม่ประทับใจเลยครับ เป็นเพราะว่า Documents ห่วยมากๆ Community ก็ค่อนข้างน้อย แถม Code ไม่ได้อัพเดทเป็นเวลานานแล้ว เทียบกับ Libgdx ซึ่งฝั่งนั้นดูจะดีกว่ามาก ในเรื่องของ Documents และการ active ของพวก Contributor ต่างๆ แถมเป็น Cross-Platform อีกต่างหาก

สุดท้าย บทความนี้อาจจะมีผิดพลาดบ้างนะครับ เนื่องจากผมเพิ่งจะศึกษาวันแรกเท่านั้น หากใครที่มีประสบการณ์ผ่านมาอ่าน ตรงไหนผิดพลาด รบกวนช่วยบอกด้วยนะครับ ขอบคุณครับ

Source Code ของวันนี้

Chai Chai Phonbopit : MEAN Stack @Nextzy • ผู้ชายธรรมดาๆ ที่ชื่นชอบ Android, JavaScript (Node.js) และ Open Source มีงานอดิเรกเป็น Acoustic Guitar และ Football

บทความล่าสุด