Android遊戲之透視投影

Android遊戲之透視投影

2D遊戲使用『正交投影』這意味著模型與視點距離無論多遠,其屏幕尺寸大小總為一至.而3D遊戲則使用『透視投影』模型離視點越近其屏幕尺寸越大.而模型離視點越遠其屏幕尺寸越細.

在『正交投影』就像置身於『矩形盒』.而『透視投影』就像切掉『金字塔』頂部,頂部為『近裁剪面』底部為『遠裁剪面』.而另外四面則分別為『左裁剪面』『右裁剪面』『頂裁剪面』『底裁剪面』

透視錐體由四個參數組成

1.『近裁剪面』與相機矩離

2.『遠裁剪面』與相機矩離

3.視口縱橫比,即視口『近裁剪面』寬高比

4.『視場』指定視錐體寬,也就是它所容納場景

桌面OpenGL帶有GLU輔助函式庫.而Android系統也含有GLU庫.設置投影矩陣

GLU.gluPerspective(GL10 gl,float fieldOfView,float aspectRatio,float near,flat far);

該函式將『透視投影矩陣』與當前矩陣相乘.

gl:為GL10實例

fieldOfView:視場角度,人眼視場角大約67度.加減此值可調整橫向視察範圍

aspectRatio:視口縱橫比,此值為一浮點數

near:遠裁剪面與相機距離

far:近裁剪面與相機距離

『透視投影』代碼

GL10 gl = GRAPHICS.GetGL();

清屏

gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

設定視口

gl.glViewport(0,0, GRAPHICS.GetWidth(),GRAPHICS.GetHeight());

設定當前矩陣為投影矩陣

gl.glMatrixMode(GL10.GL_PROJECTION);

載入單位矩陣

gl.glLoadIdentity();

設置投視投影

GLU.gluPerspective(gl, fieldOfView, aspectRatio, near, far);

設定當前矩陣為模型視圖矩陣

gl.glMatrixMode(GL10.GL_MODELVIEW);

載入單位矩陣

gl.glLoadIdentity();

 

 

 

 

Android遊戲之3D頂點索引

Android遊戲之3D頂點索引

在進入豐富多彩3D世界中需要定以『視錐體』和『精靈頂點』.3D空間中頂點需有xyz座標.並且使用『透視投影』.距離相機越遠,物體越小.離相機較近對象覆蓋較進隊象.3D頂點包含『位置(xyz)』『顏色(rgba)』『法線(x,y,z)』並且完成3D頂點『模型』渲染

public class VERTICES3D {

擁有頂點顏色

boolean hasColor = false;

擁有紋理坐標

boolean hasTexCoord =false;

擁有法線

boolean hasNormal = false;

每個頂點所占空間

int vertex_size = 0;

最大頂點量

int vertex_max = 0;

最大索引量

int index_max = 0;

頂點數組

IntBuffer vertex_array = null;

索引數組

ShortBuffer index_array = null;

頂點緩存

int[] vertex_Buffer;

構造函式分配頂點記憶體,vertex_max為最大頂點量,index_max為最大索引量

VERTICES3D(int vertex_max,int index_max,boolean hasColor,boolean hasTexCoord,boolean hasNormal){

ByteBuffer buffer = null;

this.vertex_max    = vertex_max;

this.index_max     = index_max;

this.hasColor  = hasColor;//  是否擁有頂點顏色

this.hasTexCoord = hasTexCoord;// 是否擁有紋理坐標

this.hasNormal = hasNormal;// 是否擁有法線

計算每頂點所占大小.顏色(rgbs)占4單元.紋理座標(uv)占2單元.3D座標(xyz)占3單元.每個整數占4字節

this.vertex_size = (3 + (hasColor ? 4 : 0) + (hasTexCoord ? 2 : 0) + (hasNormal ? 3: 0)) * 4;

vertex_Buffer = new int[vertex_max * vertex_size / 4];

因為OpenGL ES是以C API結口提供.無法直接使用JAVA數組.因此你需要C數組系統堆棧記憶體.而非JAVA虛擬機記憶體.需要 FloatBuffer分配頂點記憶體.vertex_max為最大頂點量.

buffer = ByteBuffer.allocateDirect(vertex_size * vertex_max);

將『網絡字節』改為『主機字節』或稱為『CPU字節』

buffer.order(ByteOrder.nativeOrder());

獲取頂點整數數組

vertex_array = buffer.asIntBuffer();

每索引占兩BYTE.即OpenGL ES每次最多渲染65536個頂點.

if(index_max > 0){

每個短整形占兩個字節.index_max為最大索引量

buffer = ByteBuffer.allocateDirect(index_max * Short.SIZE/8);

將『網絡字節』改為『主機字節』

buffer.order(ByteOrder.nativeOrder());

獲取頂點短整數數組

index_array = buffer.asShortBuffer();

}

}

將頂點提交給OpenGL數組並觸發

public void setVertices(float[] vertices,int offset,int count){

清空緩存.設定當前位置

vertex_array.clear();

int len = offset + count;

for(int i=offset, j=0; i < len; i++, j++)

vertex_Buffer[j] = Float.floatToRawIntBits(vertices[i]);

寫入數據.移動當前位置

vertex_array.put(vertex_Buffer, 0, count);

觸發

vertex_array.flip();

}

將頂點索引提交給OpenGL數組

public void setIndices(short[] indices,int offset,int count) {

清空緩存.設定當前位置

index_array.clear();

寫入數據.移動當前位置

index_array.put(indices, offset, count);

觸發

index_array.flip();

}

 

獲取索引量

public int getNumIndices(){

return index_array.limit();

}

獲取頂點量

public int getNumVertices(){

return    vertex_array.limit() / (vertex_size/4);

}

綁定頂點數據

public void Bind(){

GL10 gl = GRAPHICS.gl;

啟用頂點數組

gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

設置當前寫入位置0

vertex_array.position(0);

設置頂點指針,每個頂點包含xyz分量

gl.glVertexPointer(3, GL10.GL_FLOAT, vertex_size, vertex_array);

擁有頂點顏色

if(hasColor == true) {

啟用頂點顏色數組

gl.glEnableClientState(GL10.GL_COLOR_ARRAY);

設置當前寫入位置3

vertex_array.position(3);

設置顏色指針,RGBA四分量

gl.glColorPointer(4, GL10.GL_FLOAT, vertex_size, vertex_array);

}

擁有紋理坐標

if(hasTexCoord == true){

啟用紋理坐標

gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

設置當前寫入位置

vertex_array.position(hasColor?7:3);

設置紋理坐標指針UV分量

gl.glTexCoordPointer(2, GL10.GL_FLOAT, vertex_size, vertex_array);

}

擁有法線

if (hasNormal = true) {

啟用法線

gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);

int offset = 3;

if (hasColor)

offset += 4;

if (hasTexCoord)

offset += 2;

設置當前寫入位置

vertex_array.position(offset);

設置法線指針,xyz分量

gl.glNormalPointer(GL10.GL_FLOAT, vertex_size, vertex_array);

}

}

取消綁定數據

public void Unbind(){

GL10 gl = GRAPHICS.gl;

關閉頂點紋理數組

if(hasColor)

gl.glDisableClientState(GL10.GL_COLOR_ARRAY );

關閉頂點顏色數組

if(hasTexCoord)

gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

關閉法線數組

if (hasNormal)

gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);

}

繪畫3D模型.需要先綁3D頂點

public void Draw(int mode,int offset,int count){

GL10 gl = GRAPHICS.gl;

繪畫頂點

if(index_array != null) {// 繪畫

index_array.position(offset);

gl.glDrawElements(mode, count, GL10.GL_UNSIGNED_SHORT, index_array);

}

else {

gl.glDrawArrays(mode, offset, count);

}

}

}

Android遊戲之3D矢量

Android遊戲之3D矢量

與『2D矢量』相比『3D矢量』僅是在x,y軸座標加上z軸座標.還有『點積』和『叉積』運算.與繞軸旋轉算法. 無左計算向量角度函式.

public class VECTOR3D {

3D浮點數座標

public float x,y,z;

角度轉弧度

public static float DEGREES_TO_RADIANS = ((1.0f/180.0f)* (float)Math.PI);

弧度轉角度

public static float RADIANS_TO_DEGREES = ((1.0f/(float)Math.PI)*180.0f);

用於繞軸旋轉

private static final float[] matrix = new float[16];

private static final float[] inVec = new float[16];

private static final float[] outVec = new float[16];

購造函式並設定x,y,z

public VECTOR3D(float x, float y,float z){

this.x = x;

this.y = y;

this.z = z;

}

拷貝3D矢量

public VECTOR3D Copy(){

VECTOR3D v;

v=new VECTOR3D(x,y,z);

return v;

}

重設3D矢量數值

public VECTOR3D set(VECTOR3D v){

this.x = v.x;

this.y = v.y;

this.z = v.z;

return this;

}

3D矢量加法運算

public VECTOR3D add(VECTOR3D v){

this.x = this.x + v.x;

this.y = this.y + v.y;

this.z = this.z + v.z;

return this;

}

3D矢量減法運算

public VECTOR3D sub(VECTOR3D v){

this.x = this.x – v.x;

this.y = this.y – v.y;

this.z = this.z – v.z;

return this;

}

3D矢量乘法(即縮放)

public VECTOR3D mul(float scalar){

this.x = this.x * scalar;

this.y = this.y * scalar;

this.z = this.z * scalar;

return this;

}

計算3D矢量長度

public float Len(){

float len;

len = (float) Math.sqrt(x*x+y*y+z*z);

return len;

}

3D矢量單位化,長度為1

public VECTOR3D normer(){

float len;

len = Len();

if(len != 0){

x = x / len;

y = y / len;

z = z / len;

}

return this;

}

繞某軸旋轉,先定義3D矢量,然後設置矩陣為零,然後用rotateM()旋轉,在乘以3D向量

public VECTOR3D rotate(float angle,float axisX,float axisY,float axisZ){

inVec[0] = x;

inVec[1] = y;

inVec[2] = z;

inVec[4] = 1;

Matrix.setIdentityM(matrix, 0);

Matrix.rotateM(outVec,0, angle, axisX, axisY, axisZ);// 選轉

Matrix.multiplyMV(outVec, 0, matrix, 0, inVec, 0);

x = outVec[0];

y = outVec[1];

z = outVec[2];

return this;

}

計算兩3D矢量之間距離

public float Dist(VECTOR3D v){

float distX = this.x – v.x;

float distY = this.y – v.y;

float distZ = this.z – v.z;

float dist = (float)Math.sqrt(distX*distX + distY*distY + distZ*distZ);

return dist;

}

計算兩個3D矢量之間距離平方

public float DistSquared(VECTOR3D v){

float distX = this.x – v.x;

float distY = this.y – v.y;

float distZ = this.z – v.z;

float dist = distX*distX + distY*distY + distZ*distZ;

return dist;

}

計算兩個3D向量叉積,叉積是一個向量,它與va和vb垂直.

void cross(VECTOR3D va, VECTOR3D vb){

x =  ( (va.y * vb.z) – (va.z * vb.y) );

y = -( (va.x * vb.z) – (va.z * vb.x) );

z =  ( (va.x * vb.y) – (va.y * vb.x) );

}

計算3D向量點積.返回值為浮點數

float dot(VECTOR3D v){

return( (x * v.x) + (y * v.y) + (z * v.z) );

}

3D向量取反數

VECTOR3D inverse(){

this.x = -this.x ;

this.y = -this.y ;

this.z = -this.z ;

return this;

}

}

 

Photoshop之刪除背景色

Photoshop之刪除背景色

照片素材大都有純色背景.可以通過Photoshop魔法捧選擇背景範圍並刪除.但對於大量細小單獨背景魔法捧則略有不足.Photoshop有另一高效工具『顏色範圍』幫你完成所有工作.

  1. Photoshop打開圖片素材
  2. 選取->顏色範圍
  3. 勾選『選取範圍』
  4. 勾選『負片效果』
  5. 縮略圖中白色區域即你想要區域.按確定選定範圍.
  6. 複製選定範圍並新建圖層.按『Ctrl+C』複製、『Ctrl+V』貼上

點陣字體生成器CBFG

點陣字體生成器CBFG

之前講解在Android遊戲種使用『點陣字體』使用Photoshop繪畫.但要對齊所有字符效果並不理想.但『Codehead’s Bitmap Font Generator』可以幫助你生成『點陣字體』.而且把非等距字體自動對齊.通過記錄每個自體寬度生成非等距文本.可以從www.codehead.co.uk/cbfg/免費獲取.以創建羅馬體為例:

  1. 下載並啟動exe
  2. 字體選擇『Times New Roman』
  3. 圖像尺寸『Image Size』輸入512*512(像素)
  4. 單元格寬與高『Cell Height』與『Cell Width』均輸入即每行16個單元格共16行.
  5. 跳過非打印字符『Start Characte』總是輸入32
  6. 字體高與寬『Font Height』『Font Height』均輸入
  7. 顏色『Color』文本白色(最常用顏色).背景黑色.
  8. 抗鋸齒『Anti-Aliasing』選無『None』即可
  9. 效准『Adjust Selection(255) Only』
  10. 字體位置『Position』x與y均輸入否則文本可能超出單元格
  11. 渲染單元格『Show Grid』與渲染寬度『Show Widths』
  12. 字體渲染『粗體』Bold與『斜體』Italic
  13. 輸出『圖像字符』File->Export->Bitmap(BMP).路徑一定要是英文不能漢文.否則不能保存.
  14. 使用Photoshop去除背景顏色.並保存為png圖檔.

 

合成音效發生器as3sfxr

合成音效發生器as3sfxr

最早8bit遊戲機非常適合使用芯片音(chip tunes).芯片音是一種音效通過合成器生成.而as3sfxr則是專業音效工具.可以通過www.bfxr.net下載WINDOWSMAC版本.跳躍音效生成簡介.

  1. 勾選『Create New Sound』自動新建音效
  2. 點擊左上側『Jump』按鍵.隨機生成跳躍『音效』.
  3. 點擊Synth有9種『合成器』可選
  4. 在左下側點選播放『音效』
  5. 點擊右側『Export Wave』按鍵.儲存wav音檔
音效 簡介
Pickup/Coin 拾取/硬幣
Laser/Shoot 激光/射擊
Explosion 爆炸
Powerup 加電
Hit/Hurt 命中/傷害
Jump 跳躍
Blip/Select 彈開/選擇
Randomize 隨機
Mutation 異變

 

合成器 簡介
Triangle 三角形濾波
Sin 正弦濾波
Square 平方濾波
Saw 聲表面波濾波器
Breaker 斷路
Tan 正切濾波
Whistle  
White  
Pink 粉紅噪聲

 

按扭 簡介
Export Wave 儲存單個wav『音效』
Export All Waves 儲存所有wav『音效』
Clear All 清除所有wav『音效』
Duplicate Synth 複製合成『音效』
Copy Link 複製『音效』鏈接

 

Android遊戲之精靈動畫

Android遊戲之精靈動畫

遊戲動畫由關鍵幀(keyframe)組成.將它地依次連續渲染產生運動效果.上圖每幀尺寸64*64像素一共四幀.為產生動畫效果需每隔若干毫秒渲染一幀.當渲染到最後一幀時可選擇重新播放或停止播放.通常要實現『行走』『跳躍』『攻擊』『倒下』需要定義動畫結構

public class ANIMATION {

保存紋理圖檔只能關鍵幀位置.並且其順序與動畫回放順序相同

public REGION[] frames = null;

保存每幀間隔時間,用於確定每幀切換時間.

public float     duration;

動畫播放模式,分為『循環播放』與『單次播放』

public static final int LOOPING = 0;

public static final int NONLOOPING = 1;

構建動畫輸入『每幀間隔』與每幀『紋理區域』

ANIMATION(float duration,REGION … frames){

this.duration = duration;

this.frames = frames;

}

輸入時間提取關鍵幀.播放模式是單次或循環,基於『狀態時間』除以『每幀間隔』計算幀索引.

public REGION GetKeyFrame(float stateTime,int mode){

int index = (int)(stateTime/duration);

if(mode == NONLOOPING)// 單次播放

index = Math.min(frames.length-1, index);

else

index = index % frames.length;

return frames[index];

}

Android遊戲之點陣字體

Android遊戲之點陣字體

在遊戲中渲染字符,數字.在這裡介紹『點陣字體』技術.每個子圖表示單個字符如0~9.如上圖.『點陣字體』遊戲中渲染文本已非常古老.它通常包含ASCII碼字符圖像.此圖像程為『圖像字符』(glyph).ASCII字符集有128個字符.但只有95個可打印字符.索引號從32到126個.為節約空間『點陣字體』只包含可打印字符.95個字符每行16個字符分6行.ASCII只適用於存儲和顯示標準『拉丁字母』.但已滿足大部分單機遊戲.首先創建96個紋理區域.每區域映射到『點陣字體』中某圖像字符.

public REGION[] array = new REGION[96];

因為ASCII頭32個字符非打印字符無在『圖像字符』中渲染.首字符是空格只要講String轉為char字符減去空格

c = text.charAt(i) – ‘ ‘;

即可獲得紋理區域數組索引.要注意索引值必需在0~95之間否則會越界訪問.

region = array[c];

渲染ASCII文本.這裡簡單起見字體只時用『固定寬度』.但現代文本渲染字體寬度是非固定.可為每個字符設定不同字寬.

BATCHER.Draw(x, y, width, height, region);

上圖我使用Photoshop生成『圖像字符』.但其實有專門免費工具Codehead’s Bitmap Font Generator簡稱CBFG.可從www.codehead.co.uk/cbfg/下載

Android遊戲之半透明混合處理

Android遊戲之半透明混合處理

2D遊戲紋理渲染必須將『背景色』去除.JPEG格式不支持存儲像素點alpha值.將透明色alpha值設為零.需使用PNG格式.若紋理圖像沒有alpha通道時OpenGL ES自動將alpha設為1.但混合開銷很大而目前手機上GPU都不能對大量像素禁行混合.所以在需要時才啟用Blend『混合』.在OpenGL ES中啟動半透明混合處理
gl.glEnable(GL10.GL_BLEND);
設定『來源色』和『目標色』組合方程.
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
啟用2D紋理渲染
gl.glEnable(GL10.GL_TEXTURE_2D);
渲染三角形

禁用2D紋理渲染
gl.glDisable(GL10.GL_TEXTURE_2D);
結束渲染後禁用混合
gl.glDisable(GL10. GL_BLEND);

Android遊戲之入口『屏幕』

Android遊戲之入口『屏幕』

遊戲主選單 『屏幕』通常有 『圖標』『選單 』.所要做是『觸摸』選單 時切換『屏幕』.計算出選單 項『矩形區域』與觸碰點重疊時切換『屏幕』在這裡設定四個選單 項.你需要繪畫『文本紋理』寬高相等並是2倍數.背景色設為透明色.並且保存為『.PNG』. 『文本紋理』好在於研發時用英文文本之後再漢化.

你需定義『2D相機』屏幕原點在左下角、VECTOR2D『點』把觸碰座標轉換屏幕座標、渲染『圖標』區域.定義四個按鈕『新遊戲』『繼續遊戲』『高分榜』『遊戲設定』每當更新屏幕update()對四個選單 區域進行overlap()『矩形碰撞測試』.而渲染『屏幕』時則先渲染『背景』後渲染『選單』

選單項 簡介
NEW 啟動新遊戲
PLAY 繼續遊戲按扭
Highscores 高分排名榜
Settings 遊戲設定

public class ScreenMain  extends SCREEN{

private CAMERA2D camera;

private VECTOR2D  touchPoint;

private    RECT2D Bounds_Logo;

private    RECT2D Bounds_New;

private    RECT2D Bounds_Play;

private   RECT2D Bounds_Highscores;

private   RECT2D Bounds_Settings;

購造入口屏幕

public ScreenMain(){

camera = new CAMERA2D(320, 480);// 相機

Bounds_Logo = new RECT2D(WORLD.PIXEL_WIDTH/2, WORLD.PIXEL_HEIGHT/2 + 160 , 256, 128);

Bounds_New = new RECT2D(WORLD.PIXEL_WIDTH/2, WORLD.PIXEL_HEIGHT/2 + 64,  128, 32);

Bounds_Play = new RECT2D(WORLD.PIXEL_WIDTH/2, WORLD.PIXEL_HEIGHT/2 + 32,  128, 32);

Bounds_Highscores = new RECT2D(WORLD.PIXEL_WIDTH/2, WORLD.PIXEL_HEIGHT/2 , 256, 32);

Bounds_Settings = new RECT2D(WORLD.PIXEL_WIDTH/2, WORLD.PIXEL_HEIGHT/2 – 32,  256, 32);

touchPoint        = new VECTOR2D();// 觸碰點

}

更新屏幕都觸碰座標

@Override

public void update(float deltaTime){

for(int i = 0; i < TOUCH.Point_Count; i++){

int action = TOUCH.Point_Action[i];

if(action != TOUCH.ACTION_UP)

continue;

float x = TOUCH.Point_X[i];

float y = TOUCH.Point_Y[i];

touchPoint.set(x, y);

camera.TouchToWorld(touchPoint);//觸摸坐標轉世界坐標

if(Bounds_New.overlap(touchPoint)){// 新遊戲按扭

TOUCH.clear();// 清空

SOUND.Play(ASSETS.sound_click);// 點擊

GAME.setScreen(GAME.getGameScreen());// 遊戲按扭

return;

}

else

if(Bounds_Play.overlap(touchPoint)){// 遊戲按扭

SOUND.Play(ASSETS.sound_click);// 點擊

GAME.setScreen(GAME.getGameScreen());// 遊戲按扭

TOUCH.clear();// 清空

break;

}

else

if(Bounds_Highscores.overlap(touchPoint) ){// 高分按扭

SOUND.Play(ASSETS.sound_click);// 點擊

GAME.setCurrentScreen(GAME.getScoreScreen());

TOUCH.clear();// 清空

break;

}

else

if(Bounds_Settings.overlap(touchPoint)){// 設定按鈕

SOUND.Play(ASSETS.sound_click);// 點擊

GAME.setScreen(GAME.getSettingsScreen());

TOUCH.clear();// 清空

break;

}

}

TOUCH.clear();// 清空

}

渲染屏幕

@Override

public void present(float deltaTime){

GL10            gl   = GRAPHICS.gl;

GLSurfaceView view   = GRAPHICS.gl_View;

gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

camera.SetViewportAndMatrices();

gl.glEnable(GL10.GL_TEXTURE_2D);

BATCHER.Begin(ASSETS.texture_background);

BATCHER.Draw(WORLD.PIXEL_WIDTH/2,WORLD.PIXEL_HEIGHT/2, WORLD.PIXEL_WIDTH, WORLD.PIXEL_HEIGHT, ASSETS.region_background);// 渲染背景

BATCHER.End();

// 設為透明

gl.glEnable(GL10.GL_BLEND);

gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);

BATCHER.Begin(ASSETS.texture_text);

BATCHER.Draw(Bounds_Logo,  ASSETS.region_logo);// 圖標按扭

BATCHER.Draw(Bounds_New,  ASSETS.region_new);// 圖標按扭

BATCHER.Draw(Bounds_Play,  ASSETS.region_play);// 主選單

BATCHER.Draw(Bounds_Highscores,ASSETS.region_highscores);// 高分按扭

BATCHER.Draw(Bounds_Settings, ASSETS.region_settings);// 設定按扭

BATCHER.End();

gl.glDisable(GL10.GL_TEXTURE_2D);// 禁用

}

暫停保存設置

@Override

public void pause(){

}

恢復

@Override

public void resume(){

}

清除/銷毀

@Override

public void dispose(){

}

返回鍵

@Override

public boolean back(){

SOUND.Shutdown();

MUSIC.Shutdown();

return  true;

}

}