2017年5月26日 星期五

Native Game Programming

用 Unity 做遊戲,C#寫遊戲程式,是工作上的必需。

用 C++ 寫手機遊戲程式,過程中有一種不同的樂趣與成就感。

所以我仍然抓著空餘時間套著 Android NDK 在手機上玩玩 Graphic,把以前半成品的 Engine 想辦法 Porting 到 Android 系統上玩。

對於要在 Visual Studio 上開發原生Android 程式,NVidia 的 Nsight Tegra for Visual Studio 是很好用的支援工具,當中的 Android Log Output 尤其好用。

android_log

即使是用 Unity 開發手機遊戲,這 Log Window 也幫助很大。

Android Logcat 輸出的訊息量很龐大,如果不能好好的過濾訊息,Debug 抓問題的時候,會幾乎不知從何下手。

Nsight Tegra 這套工具裡的 Log Output,給了還算完善的自訂過濾器功能,而且提供了 UI 來訂製過濾器。

所以我才會面對這樣的問題:

Visual Studio 今年改版出了 2017 版,但是 NVidia 遲遲不將 Nsight Tegra 支援 VS2017,以致我只能選擇繼續使用 VS2015或是,在 VS2017上用功能很差的Logcat。

最後我決定,在 VS2017上,自己開發個外掛,"山寨" Nsight Tegra 的 Log Output。

沒想到的是,我為了 Native Game Programming,還是繞了回來用 C#寫工具。

上面這句話才是本篇的重點。

這個 Extension 還沒完成,不過也還可以先給個 GitHub Link

Visual Studio Tool Window Extension for Android Logcat Output & Filter

2013年7月13日 星期六

Android NDK + Cocos2d-x + WinGDB Mobile + Visual Studio 2012

小小的搞定了 Cocos2d-x 這個跨平台開發的遊戲引擎,做個紀錄。

Android SDK, NDK 的安裝就不提了。WinGDB 有個 Mobile 版本,可以支援 Android NDK 的 Build 與 Debug,這個也要先安裝起來。

Cocos2d-x 先去下載來,這次裝的是新版本,Cocos-x 2.1.4。

接著就是整合的步驟了。

1. 以 WinGDB 建立新的 Project & Solution, “New Android Java+native package”

2. Cocos2dx SDK 裡的 Cocos2dx 目錄,複製到 Project Solution 目錄中,與 Package 的 src, res 等目錄同一層。

3. Solution 內 Add Project, "import Native Library android project", 導入 Cocos2dx 的 Android.mk, (project name “cocos2dx”, 不然會新增另一個目錄)

4. For Cocos2dx Project, Configuration/ Source, Additional C++ compile flags = -frtti, C++ Standard library = Static GNU libstdc++ image
Configuration/Target, Target ABI = armeabiimage
Common/General, Target Platform = android-10 (NDK 沒有 android-10, 會自動取用到 android-9)image

 

5. Cocos2dx_static 模組, Common/Compiler, Include Path 要設定相關的路徑image
Common/Linker, Linker Flags = -lGLESv2 -llog -lz -lEGL, Export Linker Flags = -llog –lz -lGLESv2image
其他的可以不用改變

 

6. Package Project, Common/General, Target Platform = android-10image
Common/API Levels, Minimum API Level = 10, Target API Level = 10,image
Configuration/Source, Additional C++ compile flags = -frtti, C++ Standard library = Static GNU libstdc++image
Configuration/Target, Target ABI = armeabiimage
Configuration/Debug, Debug Target Device 選用Device 或模擬器image

7. Package Project 的 Game Main 模組, Common/General, Module Type 為Shared Library image
Common/Compiler, Include Path 設定需要的路徑image
Common/Linker, Linked Static Library = libcocos2d libjpeg libpng libtiff libxml2, Linker Flags = -lGLESv2 -llog -lz -lEGL, Exported Linker Flags = -lGLESv2 -llog -lz -lEGLimage
其他的可以不用改變

 

8. 在 Package Project 內 Add new project item, 選擇 External Static library, Item name = cocos2d,image
設定模組的 Property, Common/General, Module type = External static library, External module path = ../obj/local/$(TARGET_ARCH_ABI)/libcocos2d.a (Like this)image
其他可以不須改變
同樣方式,import jpeg, png, tiff, webp 模組,使用在 third-party 目錄內的 .a

 

9. 把 cocos2dx\platform\android\jni 目錄裡的 .cpp,讓 GameMain 模組編譯,不要讓 cocos2d project 編譯。 cocos2dx\platform\android\src 裡的 org/cocos2dx/lib 裡的 java 檔,加入到 Package Project 的 src 目錄下。

10. Sample 程式碼的 main.cpp, Classes 內的.cpp, .h, 複製進來,建在jni目錄內。Sample src 目錄內的 java 檔,複製進來。在 android package project 內建立 assets 目錄,把 Resources 內的檔案,放在目錄內。

11. Solution 可以設定 Project Dependency, 然後 Build Solution。

12. Done!!

13. 如果使用 CocosDenshion, 類似 Cocos2d的方式加入 Solution。

2012年8月28日 星期二

第一支 Android App 上架,從無到有的全記錄 (4)

一個跟地圖有關的 App, 沒有地圖上的標記也是很奇怪的。所以,接下來我們要在地圖上加兩種標記。

首先我們要加上 Google Map API 提供的 MyLocationOverlay。

在 Main Activity 中宣告一個變數

private MyLocationOverlay m_MyLocationOverlay;

在 onCreate 函式裡,產生這個 Overlay,並且加入到 Map View 中。

m_MyLocationOverlay = new MyLocationOverlay(this, m_MapView);
m_MapView.getOverlays().add(m_MyLocationOverlay);

/*m_MyLocationOverlay.runOnFirstFix(new Runnable() {
    public void run() {
        m_MapView.getController().animateTo(m_MyLocationOverlay.getMyLocation());
    }
});*/

有一段程式碼註解起來了,這段程式碼的用意在,當我們取得 Location 後,把 Map View 上的地圖,搬移到我們所在的位置上。但是因為我們在 GeoUpdateListener 中也有相同的功能,所以,這段程式碼其實可有可無。

接著,在 Main Activity 上,我們覆寫兩個函式。

@Override
protected void onResume() {
    super.onResume();
    m_MyLocationOverlay.enableMyLocation();
    m_MyLocationOverlay.enableCompass();
}

@Override
protected void onPause() {
    super.onPause();
    m_MyLocationOverlay.disableMyLocation();
    m_MyLocationOverlay.disableCompass();
}

現在可以 Build 來測試看看。

device-2012-08-28-112023

如果用模擬器來跑的話,記得從模擬器上送出位置。

接下來,我們要來加上我們自己的標記。

首先,我抓了一個免費的藍色圖釘圖示。pin_blue加到 Android 專案的 resource 裡,名稱叫做 pin_blue。

然後,我們從 Map API 中的 ItemizedOverlay 抽象介面,實做一個叫做 PriLocOverlays 的類別。

public class PriLocOverlays extends ItemizedOverlay<OverlayItem>
{
    private static int MAXNUM = 50;
    private OverlayItem m_Overlays[] = new OverlayItem[MAXNUM];
    private int m_nCount = 0;

    public PriLocOverlays(Drawable defaultMarker) {
        super(boundCenterBottom(defaultMarker));
        // TODO Auto-generated constructor stub
    }

    @Override
    protected OverlayItem createItem(int i) {
        // TODO Auto-generated method stub
        return m_Overlays[i];
    }

    @Override
    public int size() {
        // TODO Auto-generated method stub
        return m_nCount;
    }

    public void addOverlay(OverlayItem overlay)
    {
        if (m_nCount < MAXNUM)
        {
            m_Overlays[m_nCount] = overlay;
            m_nCount++;
            setLastFocusedIndex(-1);
            populate();
        }
    }

}

OverlayItem 的陣列,我們必須要自己管理。我很簡單的只是用個固定陣列來放。然後再加個記數的變數。

createItem 以及 size 兩個函式是我們要實做的,就簡單的傳回陣列裡的物件跟數量就好了。要注意的是,這個 createItem 函式,並不真的需要去產生一個 Item,每次 Map View 需要更新 Overlay 時,都會呼叫這個函式。

在我們自己的 addOverlayItem 內,我們需要呼叫 populate 函式,通知 Map View 來更新這個 Overlay。並且,最好是在呼叫 populate 之前,呼叫 setLastFocusedIndex(-1),清掉之前的 Focus Item,否則咧,會因為找不到之前的 Focus Item 而讓 App Crash 的。( 我實際接到過這個問題的 Bug 回報,還好已經有神人找到問題解答了... )

接著,改 Main Activity,加入 PriLocOverlays 的變數,並且在 onCreate 中建立它。

Drawable drawable = this.getResources().getDrawable(R.drawable.pin_blue);
m_PrivateLocOverlay = new PriLocOverlays(drawable);
m_MapView.getOverlays().add(m_PrivateLocOverlay);

我們還需要有一個把目前位置建立成 OverlayItem 加入到 PrivateLocOverlay 裡的功能。就做在 Option Menu 裡吧...

在 Main Activity 加入函式。

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (item.getItemId() == R.id.menu_settings)
    {
        OverlayItem overlay = new OverlayItem(m_MyLocationOverlay.getMyLocation(), "Test Loc", "");
        m_PrivateLocOverlay.addOverlay(overlay);
        //requestAddCurrentLocation();
        return true;
    }
    return super.onOptionsItemSelected(item);
}

於是乎,程式跑起來就像這樣。

device-2012-08-28-131318

 

呃...

本來想,就先寫到這樣吧,後來又覺得該寫下這個...

我們在 PriLocOverlays 類別裡,可以覆寫 onTap ( int index ) 這個函式。

當地圖上的標記被點選到的時候,onTap 函式會被呼叫。這時候,我們可以用 index 取出 OverlayItem,進行處理。

@Override
protected boolean onTap(int index)
{
    OverlayItem overlayItem = m_Overlays[index];
    Toast.makeText(m_Activity, overlayItem.getTitle(), Toast.LENGTH_LONG).show();
    // TODO Auto-generated method stub
    return super.onTap(index);
}

我沒多做什麼,就是用 Toast 顯示了一下。

結果就類似這樣。

image

2012年8月20日 星期一

第一支 Android App 上架,從無到有的全記錄 (3)

先再幫自己工商服務一下...

https://play.google.com/store/apps/details?id=com.lancelot.privatelocationdatabase

接著前兩篇的內容。

可以載入地圖之後,下一個要做的事,就是抓到我們的位置,還有,把地圖移到我們所在的位置上。

我們需要的是 LocationManager, LocationListener, 還有 MapController。

LocationManager 的作用是定位我們現在的位置,然後再將定位的結果傳喚給 LocationListener。不過呢,LocationListener 是一個抽象類別,需要我們自己去繼承實做,主要要實做 onLocationChanged 這個函式。

所以,我們先建立一個 GeoUpdateListener 類別。

public class GeoUpdateListener implements LocationListener {

    MapController m_MapController;
    GeoPoint m_geoCurrentLoc;
    MainActivity m_Activity;
   
    public GeoUpdateListener(MainActivity activity, MapController map_controller)
    {
        m_MapController = map_controller;
        m_Activity = activity;
    }
    public void onLocationChanged(Location location) {
        // TODO Auto-generated method stub
        int lat = (int) (location.getLatitude() * 1E6);
        int lng = (int) (location.getLongitude() * 1E6);
        m_geoCurrentLoc = new GeoPoint(lat, lng);
        m_MapController.animateTo(m_geoCurrentLoc);
    }

    public void onProviderDisabled(String provider) {
        // TODO Auto-generated method stub

    }

    public void onProviderEnabled(String provider) {
        // TODO Auto-generated method stub

    }

    public void onStatusChanged(String provider, int status, Bundle extras) {
        // TODO Auto-generated method stub

    }

}

onLocationChanged 的程式碼很簡單,就是把取得來的位置,經緯度轉換成 MapController 能用的整數格式,生成 GeoPoint 物件後,交給 MapController,MapController 就會把地圖移動到我們指定的位置上。

接著改 Main Activity。

加入幾個 Private 變數。

private MapController m_MapController;
private MapView m_MapView;
private LocationManager m_LocationManager;
private GeoUpdateListener m_GeoUpdateListener;

在 Main Activity 的 onCreate函式裡,加入 Location Manager, Geo Update Listener, 取得 Map View, Map Controller。

        m_MapView = (MapView)findViewById(R.id.mapview);
        m_MapView.setBuiltInZoomControls(true);
        //m_MapView.setSatellite(true);
       
        m_MapController = m_MapView.getController();
        m_MapController.setZoom(14); // Zoom 1 is world view
       
        m_GeoUpdateListener = new GeoUpdateListener(this, m_MapController);
       
        m_LocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        m_LocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000,
                0, m_GeoUpdateListener);
        // also need 3G GPS
        m_LocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000,
                0, m_GeoUpdateListener);

那,從 Code看。

Location Manager 要從 System Service 中取得。

位置的提供者,有兩種,從 GPS 來,以及從手機基地台定位而來。我們可以兩種都用。所以在 requestLocationUpdates 函式裡,會有兩種參數 GPS_PROVIDER & NETWORK_PROVIDER。

同時,我們也告訴Location Manager,位置更新的時候,請呼喚 GeoUpdateListener。

程式就改到這邊。

還有一個地方要更動一下。在 AndroidManifest.xml 裡,要加入兩個 Permission。

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

現在我們可以做測試了。

不過這次,我們想用模擬器來玩。

因為,這個 App 的 Build Target 是 Google API,所以,我們必須要建立一個 Google API 的 模擬器。

image

開啟模擬器之後,跑起 App。

image

但是這樣的東西好像沒有定位的功能呢,怎麼辦?

把 Eclipse IDE 上的 DDMS 打開,有個 Emulator Control 的分頁,可以找到 Location Control 的功能。

image

我們試試看,把 Longitude 改成 –122.08, Latitude 改成 37.42,

image

地圖就改到別的地方啦!!

2012年8月16日 星期四

第一支 Android App 上架,從無到有的全記錄 (2)

這樣說吧, App 裡頭要使用 Google Map 的資料,需要 Google 的授權。所以, Google 要求開發者要登記一個 Google Map API 的金鑰。App 還在開發中的時候,先使用 Debug 版的金鑰就可。至於發佈版的金鑰,後面再說吧。

取得金鑰前,先要簽署 Maps API。

Sign Up for the Android Maps API - Android Maps API - Google Code

image

打勾,填入 MD5 Fingerprint,然後按下 "Generate API Key"。

MD5 Fingerprint 哪裡來??

Android SDK 附有一個名稱為 "debug.keystore" 的檔案。

  • Windows Vista / Windows 7: C:\Users\<user>\.android\debug.keystore
  • Windows XP: C:\Documents and Settings\<user>\.android\debug.keystore

在這個檔案的目錄內,建立一個 bat 檔。內容寫一行

<jdk path>\bin\keytool -list -v -alias androiddebugkey -keystore debug.keystore -storepass android -keypass android >> debug_map_key.txt

<jdk path> 是 Java SDK 的安裝目錄。

執行 bat 檔以後,會將輸出內容寫到 debug_map_key.txt 檔案裡。檔案裡就可以看到這麼一段 :

別名名稱: androiddebugkey
建立日期: 2012/1/18
項目類型: PrivateKeyEntry
憑證鏈長度: 1
憑證 [1]:
擁有者: CN=Android Debug, O=Android, C=US
發出者: CN=Android Debug, O=Android, C=US
序號: 4f162768
有效期自: Wed Jan 18 09:59:04 CST 2012 到: Fri Jan 10 09:59:04 CST 2042
憑證指紋:
     MD5:  1F:04:CE:54:D2:57:0D:7C:11:1F:5D:A1:B2:D6:E8:93
     SHA1: 4E:D1:A5:3E:68:F2:05:C8:09:9C:29:9C:A9:38:29:4C:3C:3B:A8:1A
     SHA256: C9:DA:67:77:3F:4B:94:F5:AE:47:7F:FF:A6:14:9C:B5:96:10:00:7E:99:C8:45:40:A3:64:9F:83:BA:12:56:D1
     簽章演算法名稱: SHA1withRSA
     版本: 3

那一行 MD5 就是我們要的。

複製貼上到網頁上,按下 "Generate API Key"。

image

到此,我們就取得金鑰了。

回到 Eclipse IDE 的程式碼。

在 main activity 的 layout xml 檔案裡,把 android:apiKey 那一段,"your key" 改為我們取得的金鑰。

    <com.google.android.maps.MapView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mapview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:apiKey="0vn4fYcAmXsjC902Q8puaWZLabVkY0t5jqjRzSw"
    android:clickable="true" />

再來,因為我們需要透過 Internet 下載地圖資料,所以,要在 AndroidMenifest.xml 裡加入 Internet 的 Permission。

<uses-permission android:name="android.permission.INTERNET"/>

現在 Build 執行看看吧...

2012-08-16_10-25-01

看到地圖囉!!!

可是看不到街道、看不到自己的位置...

那是後續的工作...

2012年8月15日 星期三

第一支 Android App 上架,從無到有的全記錄 (1)

screen_1screen_2

這支 App 用到三個程式模組,Google 地圖、資料庫、還有廣告。

App 所要達到的功能,很簡單 :

當出遊或是閒晃時,走到某個值得記錄的地點,可以將它標記起來。未來再度行經附近的時候,地圖上就會標示出來。

花了一陣子的業餘時間,前幾天丟到架上了。( 歡迎下載 )

https://play.google.com/store/apps/details?id=com.lancelot.privatelocationdatabase

好,先從 Google Map 開始。

要用 Google Map 功能,先需要 Google Map 的 API。

Android 雖然跟 Google 關係匪淺,但是, Android SDK 本身是不含有 Google Map 相關功能的,所以,必須要透過 Android SDK Manager 工具,下載 Google APIs。這一大包裡頭,就有 Google Map 相關的類別。

image

接著,可以從 Eclipse IDE 建立 Android Application 專案。專案的 Build Target 要改用 Google APIs。

image

然後應該就可以生出個預設的 Hello World 程式了。

接著先在 AndroidManifest.xml 檔案中,加入 Use Library 到 Application裡。我們要使用的是 com.google.android.maps 這個 package。

<uses-library android:name="com.google.android.maps"/>

image

然後在主畫面的 layout xml 檔案裡,加入 Map View 元件。

    <com.google.android.maps.MapView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mapview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:apiKey="your_key"
    android:clickable="true" />

最後,把主要 Activity 改為從 MapActivity 繼承。

import com.google.android.maps.MapActivity;

import android.os.Bundle;
import android.view.Menu;

public class MainActivity extends MapActivity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}

@Override
protected boolean isRouteDisplayed() {
// TODO Auto-generated method stub
return false;
}


}

好了,趕快 Build 一下,看一下成果。

成果是,只看到方格線,還有一個 Google 的浮水印。其他什麼都沒有。

2012-08-15_19-07-01

因為我們還有一個很重要的 Map API Key 還沒取得。

2012年5月9日 星期三

Unity3D Plug-in for Android -- Activity 擴展方法

Unity3D 的 Android Plug-in 還有另一種實做方法。當需要使用 Android SDK 中的 Java 物件時,利用這個方法來將 Unity3D 與 Android SDK 結合是比較好的。

簡單的步驟,

1. 先在 Eclipse IDE 中,新建一個 Android Project。

2. 在 Project 的 Property 中,點選 Java Build Path,切到 Libraries 的分頁,按鈕 "Add External JARs…",然後選擇 Unity 安裝目錄,找到 "\Editor\Data\PlaybackEngines\androidplayer\bin" 子目錄裡的檔案 classes.jar 來加入
clip_image002

3. 修改 Activity 的 Java Code

Activity 改成自 UnityPlayerActivity 繼承 ( import com.unity3d.player.UnityPlayerActivity ),在 Activity 的 OnCreate 函式裡,把預先產生的 setContentView() 函式呼叫刪除掉不要使用。

一個範例程式碼如下: ( UnityPlayerExtendActivity.java )
這個範例裡,我們不但使用了 Toast,還用到了Android SDK 中的 藍芽 API。這兩項功能,都是Unity3D無法提供的。

package com.activity.unityextend;

import com.unity3d.player.UnityPlayerActivity;

import android.os.Bundle;

import android.widget.Toast;

import android.bluetooth.BluetoothAdapter;

public class UnityPlayerExtendActivity extends UnityPlayerActivity {

private BluetoothAdapter mBluetoothAdapter = null;

/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

//setContentView(R.layout.main);

mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

if (mBluetoothAdapter == null) {

Toast.makeText(this,

"Bluetooth is not available.",

Toast.LENGTH_LONG).show();

finish();

return;

}

if (!mBluetoothAdapter.isEnabled()) {

Toast.makeText(this,

"Please check your BT settings.", // and re-run this program

Toast.LENGTH_LONG).show();

//finish();

//return;

}

}

}

另外呢,AndroidManifest.xml 這個檔案,需要調整一下。因為我們用到了藍芽功能,所以要開啟應用程式的藍芽權限。

image

4. 接著從 Eclipse IDE Export 出 jar 檔案。

 clip_image002[6]
clip_image004

5. 下一步,開啟 Unity3D,修改 Player Settings 設定。要注意的是, Bundle Identifier 與 Minimum API Level 要與 Eclipse 上的 Android Project 相同。

6. 然後將 Eclipse 的 Android Project 檔案複製到 Unity 專案的目錄下, 這裡有幾個需要複製的檔案。

clip_image008 


a. 在 Assets 目錄下建立 Plugins 目錄,再建立 Android 子目錄。

b. 將 Android Project 中的 AndroidManifest.xml 複製到 Plugins/Android 目錄內。

c. 在 Plugins/Android 目錄內建立 bin 子目錄,將 Android Project 輸出的 jar 放進來。

d. 最後將 Android Project 內的 res 子目錄複製到 Plugins/Android 目錄內

7. 最後,從 Unity3D Build & Run,就可以使用 Android SDK 提供的功能了。

 

 

(Update 2012.7.5) 加入了 AndroidManifest.xml 該修改的部分

2012年2月3日 星期五

Unity 3D Plug-in for Android 開發指南

這是一個摸索筆記。

以下是正文。

一. Unity3D Plug-in 必須知道的概念

Unity 的 Plug-in 是以二進位存在的,所以,在不同的平台上,必須開發不同的 Plug-in,例如,在 PC Windows 平台,使用 dll 檔,在 Android 系統,使用 .so 檔。

在使用 Unity 開發專案時,因為是在 PC Windows 平台上操作,所以當使用了 Android 的 .so 檔時,會造成無法在 Unity Editor 中即時操作遊戲的問題。至於是否可以有個取代方案,例如,另外寫個相同功能的 dll 檔來在 PC Windows 平台上操作,這要再做進一步測試。

二. Unity3D Plug-in 的安裝

Unity 的官方文件,是建議將 Plug-in 複製到專案的 Assets/Plugins/Android 目錄下,如果沒這個目錄,就自行建立一個。

所以,就是將寫好編譯好的 .so 檔,複製到這個目錄下就可。

官方文件上有一個模稜兩可的說法,說 Plug-in 安裝後,Unity 會自動抓出其中的 Java Class 什麼的,不要相信它,因為完全試不出來。

三. Unity3D Plug-in 的使用

.so 檔複製到專案以後,是無法取得任何資料的。必須透過 C# Script 來載入。 官方文件中,只提供了使用 C# Script 來參考 Plug-in 的方法,找不到 Java Script 的方法,所以就先以 C# Script來做。

官方網站上有一個可以參考的範例,"AndroidNativePlugin.zip",可以下載來參考。

zip 檔解開後,將 unity package import 進 Unity Editor,可以抓到 .so 檔,原始程式碼,以及使用 Plug-in 的 C# Script。

C# Script 如下 (已經改過了)

using UnityEngine;

using System.Collections;

using System.Runtime.InteropServices;

public class CallNativeCode : MonoBehaviour {

public GUISkin mySkin;

[DllImport("ttt")]

private static extern float add(float x, float y);

void OnGUI ()

{

GUI.skin = mySkin;

float x = 3;

float y = 10;

GUI.Label (new Rect (15, 125, 450, 100), "add" + x + " and " + y + " equals " + add(x,y));

}

}

幾個重點:

1. [DllImport(“ttt”)] : 這行程式,是負責載入 Plug-in,依照官方規定,這個Plug-in 的檔名是 libttt.so,也就是,前綴必須有”lib” 三個字母,後面是 .so 副檔名。

2. private static extern float add(float x, float y); -- 這一行是宣告Plug-in中的函式。

3. 載入與宣告函式完成後,在 Script 的 OnGUI 函式中,就可以使用這個函式了。

四. Android Plug-in 的開發

接著就是,.so 這個檔怎麼做出來了。

.so 檔,可以視作是 Android 系統上的 Dynamic Load Library (dll)

1. 建立開發環境

一般書籍或網站上找到的 Android 應用開發,多是以 Java 環境來做的,這是無法編譯 .so 檔案的。

Android 的 Development Kit 有兩種,一種是 SDK (Software Development Kit), 一種是 NDK (Native Development Kit),編譯 .so 檔,需要 NDK。

傳統的建立開發環境的方法是,要安裝 cygwin (Windows 上的 Linux 模擬器)、NDK、SDK、Eclipse IDE 等等,現在有個神人把整個開發環境整合到了 Visual Studio 2010上,所以建立開發環境的方法,就以整合 VS 2010 來做了。

vs-android -- Integrated development of Android NDK C/C++ software under Visual Studio

下載vs-android,sample solution,然後下載 Android NDK (r7 or later),SDK,Java SDK (32 bits版),以及 Ant。

需要安裝的安裝,可以解壓縮複製的,就解壓縮複製。( Android SDK 的安裝,比較複雜些,請找資料看看 )

然後要設定幾個環境變數

ANDROID_HOME : Android SDK 的根目錄位置,例如 C:\Develop\android-sdks

ANDROID_NDK_ROOT : Android NDK 的根目錄位置,例如 C:\Develop\android-ndk-r7

ANT_HOME : Apache Ant 的目錄,例如 C:\Develop\apache-ant-1.8.2

2. 測試範例程式 san-angeles

設定完成,打開 VS 2010,載入 Sample Solution,USB接上測試用設備,Build Solution,然後就 OK 了。

<< 不過,還是會碰到編譯不過的情形啦。以下是幾個可能要處理的狀況。

a.
Unable to resolve target 'android-4'
    因為在 SDK中的 Platform 版本裡沒有安裝 API Level 4,修改 project.properties 這個檔案中的 target=android-4,改為 SDK 有安裝的API

b.
ANTBUILD : [dx] error : Could not create the Java Virtual Machine.
ANTBUILD : [dx] error : A fatal exception has occurred. Program will exit.
[dx] Error occurred during initialization of VM
[dx] Could not reserve enough space for object heap
這問題,打開 android-sdks\platform-tools 目錄中的 dx.bat 檔案,將
set defaultXmx=-Xmx1024M
改為
set defaultXmx=-Xmx512M

>>

3. 建立要編譯 .so 檔的專案

因為 vs-android 的神人作者,還沒有寫好 Project Wizard,所以,要靠半手動的方式來建立新專案。

簡單一點的做法是,就在範例程式 solution 下新加入一個專案,選擇 Win32 Console App,然後建立 Empty Project。

Project 的名稱,以 lib 開始,符合 Unity Plug-in 的規則。例如,libunityplug1

專案建立完成,開啟專案的 Property Page,點選 Configuration Manager

clip_image004

新建立的專案還是 Win32 Platform,點選專案的 Platform 下拉箭頭,選擇 <<New>>,建立新的專案 Platform。

clip_image006

Platform 選擇 Android, Copy Setting 選擇 <Empty>,底下的 Create new solution platforms 要取消打勾。

然後按下 OK

新專案的 Project Platform 就會改成 Android,而專案中的 Property 也會自動調整為 Android 平台相關的設定。

因為要產生的是 Dynamic Library ( .so ) 檔,所以,將 Configuration Type 調整為 Dynamic Library。

clip_image008

其他的 API Level, Architecture,有需要也可以改看看。

然後就開始加入原始程式碼檔案吧!!

4. Plug-in 程式碼與使用

很快,隨便寫了個 .c 的程式

float multiply(float x, float y)

{

return x * y;

}

然後 Build 這個 libunityplug1 專案,沒問題的話,會得到 libunityplug1.so 的檔案,把 .so 檔案複製到 Unity 的 Assets/Plugins/Android 目錄下

修改 Unity C# Script

[DllImport("unityplug1")]

private static extern float multiply(float x, float y);

然後,就可以使用這個函式了。

2011年10月7日 星期五

Visual Studio 2010 是好物

很久沒碰 MFC了。

前一陣子 CryEngine 釋出 SDK,抓來看了以後,發現它的編輯器介面很漂亮,多翻了翻,才知道原來是用 MFC Library 做的。

這下子又引起我對 MFC 的興趣。

顯然 MFC 還不至於像傳說中的那麼慘。

最近打算重新改寫遊戲資源檔案打包上傳的工具程式,想到了 MFC,打算拿著來試試。

發現了這麼一篇 Blog

Your First MFC C++ Ribbon Application with Visual Studio 2010

哦~~~

MFC 做出來的應用程式介面不再那麼難看了耶~~

趕快隨手很快的建了一個專案,勾選幾個項目後,完整功能的 Dock Panel, Ribbon Menu ( 還有一個 Outlook的 Navigation Bar 沒勾) 都有了...

clip_image001

沒做什麼事,而應用程式看上去的專業度大大提升...

VS 2010 真是好物...

2011年7月19日 星期二

圖形除錯設備

今年 GDC 給我的一個大震撼,就是,Game Engine / Graphic Engine 裡,要加上圖形除錯功能。

回頭翻了一本 3D Game Engine Architecture 的書,才發現人家早早就有說了。

這些功能的用處很大,做了絕對有好處。

然後發現,啊,Engine 裡頭那個顯示 Bounding Box 的物件,不就是其中一個圖形除錯物件麼?

Bungie 在開發 HALO 時,連網路封包的數據與優先權都用即時圖形顯示。

image

我們真的該把圖形除錯的功能加到 Engine 裡。

我叫它做「圖形除錯設備」。 ( Graphic Debugging Facilities )

首先,我把 Game Timer 做了修改。加上加速、放慢、暫停、甚至每個 Frame 更新固定時間的功能。這些在我們做 Animation 的除錯時,應該會很有幫助。另外,我還加了一個 Real Life 的 Timer,我們總還是會需要知道真實時間的吧?

然後, Engine 裡面加了一整組的 Debug Drawing API,包括畫線、畫圓、畫顆 Wireframe 的球、畫個十字、畫個座標軸、顯示字串訊息在 3D 場景裡,還有一個很有用的,畫個 Frustum 出來。

最後一個組件呢,就是弄了一組除錯用的 Camera 跟 Frustum。遊戲場景的攝影機,是場景管理中很重要的運算基礎,但是我們怎麼知道這些運算有沒有問題? 例如,Frustum 的 Culling 運算、遠方物件的 LOD 選擇,有了除錯 Camera 的模式,我們就可以很簡單的做檢查。

image

Hmm... 看來 Frustum 的 Culling 並沒有算得很乾淨...

2011年6月5日 星期日

30 天打造 OS!

其實這是我今天帶回的一本奇書,它的副標題。

image

(圖片來源: 博客來書籍館)

日本人寫的,譯者有三位。

為什麼說它是奇書 ?

你看標題就知道了, 「自己做作業系統 ? 這什麼 ?」

台灣的資訊相關書籍,多如過江之鯽,作業系統核心的內容已經夠硬了,還要自己寫程式來做 ? 而且一開始還是拿組合語言拼出來的開機程式,這絕對是硬派中的硬派。

市場更是小之又小。

幾個月前我在書店裡看到的時候,愣了一下,覺得很神奇,怎麼會有人出版這種書 ? 有個怪咖會寫這種書不奇怪,但是翻譯了出版來賣 ? 這就很匪夷所思了。

就算上架了,大概也沒幾個人會在書店裡把它拿起來翻閱的吧?

果然, 今天閒晃的時候又看到它,整本書還是跟新的一樣。

我沒有多想,就把它買下帶回家了。

這種市場小之又小的書,一不注意,就會消失在書店裡,再也找不著。

話又說回來,書的封底是這麼寫的:

-----

可能是史上最有趣且實用的作業系統書籍!日文版已突破13刷!

這是一本兼具趣味、實用與學習性的作業系統書籍。作者以獨創手法,從電腦的構造、組合語言、C語言開始解說,讓讀者透過實作與測試學會演算法,並在自由愉悅的氛氛下,從零開始建構作業系統。

即使是初學者,也不需畏懼,因為連貓咪都能輕鬆學會自作作業系統!

-----

貓咪也能學會? 廣告不實...

2011年3月8日 星期二

GDC 2011 Day 5

最後一天,算算只有四個小時的課。

今天的開場,由 Bungie 的 Halo : Reach 開始。

首先是 Automated Level of Detail Generation。

顧名思義,就是用程式算出 LOD Mesh。裡頭的演算方法,是很不錯的想法,可以找時間來模擬嘗試看看。

而除了 LOD Mesh 之外,他們還提出了一個做法 – "Unified material model"。

簡單講,就是所有的 LOD Mesh 都使用同一種材質 Shader ,這樣一來,也可以提升很多效能。更重要的是,反正距離很遠、物件很小的時候,就算畫的很粗略,也不是那麼明顯...

在這裡,他們也展示了一個在除錯模式下的攝影機,可以用來檢查 LOD 的變化是否如預期。

Hmm… 我們似乎也該弄個 Debug Mode Camera 來了。

第二段,講述的是 Halo : Reach 裡的 Effect。

第一個來的 Effect,就是嚇死人的粒子系統。

有多恐怖?

1. 粒子的運算全部交由 GPU 計算,CPU 只決定粒子的數量。

2. 在 GPU 裡計算的粒子,照樣可以跟場景中的物件計算碰撞跟彈跳。

所以,只要一個 GPU Draw Call ,就可以畫出所有的粒子,而且還會有碰撞彈跳。

碰撞與彈跳計算的資料來源有兩個,Depth Buffer 與 Normal Buffer。計算的概念,說穿了一點都不難,但是,要能夠想到這點,還真的是不容易。

接下來的效果,都跟 Depth Buffer 有關。

Bungie 給了一個結論 – "Depth buffer is incredibly useful"

前兩天,岩田聰社長說 : "Content is KING"

今天我們的結論是 : "Depth Buffer is KING"

接下來聽了一場跟想像中的內容不太一樣的講題。

本來以為他們會介紹如何處理一個龐大遊戲場景的概念與方法,結果,他們是有介紹啦,但是卻是基於他們自己的工具系統,有一點失望,不過也還是有一點收穫就是。

下午聽了一場是關於拳擊遊戲裡,對於拳手的皮膚效果的處理方式,很新鮮,而且他們的專業研究,也不得不讓人佩服。

過去一整個看起來像 "塑膠" 的皮膚,經過研究分析改進之後,變得非常自然。

真是夠專業。

 

最後,把像素拼圖的完成圖拍了下來。 ( 雖然這已經是由工作人員完成的了... )

DSC01442

2011年3月4日 星期五

GDC 2011 Day 4

中午又跑去任天堂的展場去玩 3DS,本來是想要玩玩薩爾達的,可是人好多擠不進去,晃了一陣,只玩到 Street Fighter。

也很讚。

早上第一場,去聽了 Killzone 3 有關 Occlusion Culling 的技術,基本概念也還是用個 Buffer 來記錄深度,然後做測試運算,沒什麼新的。不過說的很詳細,比起昨天用兩三張畫面帶過的那一場,好太多了。

更重點是,他們做了一整套在 Occlusion Culling 上的 Debug 工具,可以很清楚看到 Occlusion Culling 計算前計算後的結果。

視覺化很重要,尤其是這東西跟美術有關。

接下來去聽了 Dead Rising 2 在網路功能方面的經驗分享。

這遊戲好像賣的很不錯? 整場聽眾有爆滿啊...不過我很懷疑有多少人不是技術背景的,至少最後發問的人裡頭有一兩個...( 問的問題居然是遊戲的銷售量?? )

網路功能,嗯,對我們在 MMO 上面已經有些經驗的人來說,這些分享的內容並沒有太多的不同。反倒是,他們也做了個 工具來監看 Traffic、監看資料同步的結果...就顯示在遊戲畫面上...Real-Time…

只能說,他們真的很愛做工具。不知道是因為專業還是因為人太多?

下午,有一個題目很吸引人。

"Adaptive Order Independent Transparency"

Render 半透明物件不用先排序。

基本概念還是與 A-Buffer 有關。只不過,將 A-Buffer 的第二階段 -- 排序與組成,引入一個 "Visibility Function",以當作權重的方式,計算半透明 Pixel 最後合成的結果。用這樣的方式,刪去耗費時間的排序工作。

效能增加很多。

不過這 "Visibility Function"的計算有些複雜。

接下來的課程是,Bugie 講述 Halo : Reach 遊戲裡網路相關的技術分享。

有一句話寫得好 : "Technology to help multiple players sustain the belief that they are playing a fun game together"

他舉了兩個遊戲中的例子,來看他們把網路延遲藏的有多好。

然後,又看到了 Traffic 監看的工具...也是直接顯示在遊戲畫面上...也是 Real-Time…

DSC01306

他們真的很喜歡做工具...

不過,這工具應該還是有用的,至少他們抓到了一個問題: 一個還在滾動的手榴彈用了很大量的頻寬...

最後一堂課程是超大的一顆地雷...

又沒有其他課程好去,所以就坐著混到下課...

 

今天的像素拼圖長這樣:

DSC01234

昨天那個日本人做的馬力歐已經不見了,應該是工作人員把它拆了。

那個蹲著在拼的,應該就是工作人員。手上有一大包同樣顏色的方塊,拼完了又去拿了一大包來拼。

有工作人員做修正,那這個活動也沒什麼意思了...

2011年3月3日 星期四

GDC 2011 Day 3

Game Develop Conference 今天算是正式開始。

一開始的盛事,就是任天堂社長岩田聰的 Keynote 演講。

DSC01132

DSC01134

說真的,日本人講的英文聽起來有難度,我想他說起來也不會簡單...

所以,在他演講台的正對面有大字報...

DSC01135

演講主題呢,就是內容啦、創意啦、熱情啦...這些東西...

一個小時的演講,中間還讓北美的任天堂主管進來工商服務一段 N3DS…

N3DS

真的是很讚。

中午休息時間,到任天堂的展場去,搶到空位,把玩了一下 N3DS 的任天貓狗,整個裸眼 3D 的畫面效果好逼真啊...

不過這個真的要親眼看,有圖就沒真相了。

其他的四堂課,好像都碰到了大大小小不等的地雷,沒有什麼非常 Exciting 的東西。倒是另一位同事選到了一兩個有意思的課程。

首先是 Autodesk 講他們正在進行的新計畫 -- DNA Language,用來做為 Game Runtime, Game Editor, DCC Tools 之間的資料庫管理橋樑。讓 Asset 的製作與測試,能夠有更快速順暢的 Workflow。

可是,我只是想知道 Asset 的版本管理方法啊... 課程簡介上不是寫的是這個嗎? 你們不能就這樣輕輕鬆鬆帶過啊...

下午,先去聽 Valve 的演講,這場技術性比較高些,可能跟這個演講者老是在玩組合語言有關。

網頁: http://bit.ly/hPCmVW

然後去聽了 BattleField 3 的新的場景 Culling 的方法。

什麼新方法?

把矩陣行列轉過來,讓 SIMD 運算比較快些,也算新方法?

然後講 Software Culling,什麼? 就畫在一張比較小的 Z-Buffer 上然後做 Z-Test ?

我快昏了...

最後一堂課,去聽 AMD 介紹新版 PerfStudio。心得就是 : AMD 用了一張顯示卡,換到了幾十張開發者的名片...

 

今天的像素拼圖是這樣--

DSC01151

那個蹲跪著在拼的應該是日本人,他也不管格子上面寫的是什麼符號,反正就是要從別的地方拆方塊下來拼馬利歐就對了。

我開始覺得,每個人都想要做自己的創意,這張圖應該沒辦法完成了...