游戲中攝像頭的原理介紹
在游戲開發中更新攝像頭的位置可以決定屏幕顯示的內容,尤其是RPG類游戲攝像頭有著非常重要的作用,我舉一個例子 有時候我們在玩RPG游戲的時候進入一個新的場景 觸發一段腳本後 發現鏡頭開始向上移動 根據鏡頭移動玩家可以大概浏覽一下這個場景有什麼東西 ,觸發什麼樣的劇情。這個實現的方式就是游戲攝像頭原理。上章學習了Android游戲開發地圖編輯器有需要的可以看下。
如圖所示:首先攝像頭顯示的區域也是手機屏幕顯示的區域 如果需要更改攝像頭的位置 其實是更改背景地圖的位置 利用程序拖動背景地圖 給玩家一種假象讓玩家感覺像是攝像頭在移動而不是背景地圖在移動。
游戲中地圖的繪制原理介紹 根據地圖編輯器生成的出來的數組的每一個tile 的 ID 找到每一個tile的地圖資源原始文件的XY坐標 算出來圖片的顯示位置利用程序的切割的方法把每一個tile切割出來顯示在手機屏幕中。 切割圖片的代碼所示:
/**
* 繪制圖片中的一部分圖片
*
* @param canvas
* @param paint
* @param bitmap
* @param x
* @param y
* @param src_x
* @param src_y
* @param src_width
* @param src_Height
*/
private void DrawClipImage(Canvas canvas, Paint paint, Bitmap bitmap,
int x, int y, int src_x, int src_y, int src_xp, int src_yp) {
canvas.save();
canvas.clipRect(x, y, x + src_xp, y + src_yp);
canvas.drawBitmap(bitmap, x - src_x, y - src_y, paint);
canvas.restore();
}
canvas.save();
切割圖片之前先把Canvas保存起來 然後在切割 繪制結束後
canvas.restore();
在把Canvas的在狀態重置回來 如果不這麼做的話 第一張圖片切割後就會擋住以後所有的圖片,所以大家一定要記住這一點喔。
圖所示:每一張tile的繪制原理就是這樣,說到這裡有些朋友可能就要問 如果我的地圖無限大那根據這個方法豈不是要循環無限次?其實屏幕須要繪制的tile數量只需要繪制屏幕顯示區域以內的, 屏幕現實區域以外的我們不用考慮繪制 只需要更新地圖的坐標數據就可以,比如我的模擬器屏幕的大小是320X480 那麼我實際繪制的tile數量只是 10 X15 (塊)。其實游戲開發繪制中還有一個更重要的繪制技術就是雙緩沖技術它可以用來解決屏幕閃爍問題,下一章中我會詳細介紹。
昨天有朋友跟我提出這種用數組的方式來繪制地圖不科學我很同意他的觀點,為什麼不科學? 原因是現在我們只有一個場景我們用一個數組來繪制地圖 萬一我們的游戲有100個場景 我們豈不是要在程序中寫100個數組了?其實在實際開發中我們是把這些地圖的信息轉成xml文件 打到游戲的包中 玩家在切換游戲場景的時候便會讀取當前游戲場景中的地圖xml文件。其實這些xml文件中也是保存這地圖的二位數組信息 但是這樣做的好處就是數據驅動 程序員不用定義N個數組 做N種判斷 只須要根據當前切換的場景的ID就可以得到地圖的信息
十分方便 也可以避免代碼中由於筆誤造成的的錯誤 何樂而不為。
但是不管用任何方法處理數據 它的繪制原理都是一樣的。
如何更新游戲中攝像頭
效果圖:程序取隨機數更新游戲攝像頭
目前以每10000毫秒更新一下攝像頭的位置 (隨機數) 我們有了攝像頭的位置以後 就可以在算出背景圖片的相對顯示位置 移動背景圖片的位置後就可以給玩家制造出一種攝像頭在移動的假象了。
地圖塊是我新拼的 長寬的tile塊數是20X20。
使用 CTRL+C 復制,使用 CTRL+V 粘貼。
package cn.m15.xys;
import java.io.InputStream;
import java.util.Random;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.Display;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
public class CameraAcitvity extends Activity {
MapView mMapView = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 全屏顯示窗口
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
// 獲取屏幕寬高
Display display = getWindowManager().getDefaultDisplay();
// 顯示自定義的游戲View
mMapView = new MapView(this,display.getWidth(), display.getHeight());
setContentView(mMapView);
}
public class MapView extends View {
// tile塊的寬高
public final static int TILE_WIDTH = 32;
public final static int TILE_HEIGHT = 32;
// tile塊的寬高的數量
public final static int TILE_WIDTH_COUNT = 20;
public final static int TILE_HEIGHT_COUNT = 20;
// 地圖的寬高的
public final static int MAP_WIDTH = 640;
public final static int MAP_HEIGHT = 640;
//鏡頭移動范圍
public final static int CAMERA_MOVE = 10;
// 屏幕的寬高
public int mScreenWidth = 0;
public int mScreenHeight = 0;
// 數組元素為0則什麼都不畫
public final static int TILE_NULL = 0;
// 第一層游戲View地圖數組
public int[][] mMapView = {
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 137, 137, 137, 137, 137, 1, 1, 1,
1, 1, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 137, 137, 137, 137, 137, 137, 1, 1, 1,
1, 1, 1, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 1, 137, 137, 137, 137, 137, 1, 1, 1, 1,
1, 1, 1 },
{ 1, 1, 1, 1, 1, 137, 137, 137, 137, 137, 137, 137, 137, 1, 1,
1, 1, 1, 1, 1 },
{ 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137,
137, 137, 137, 137, 137, 1, 1, 1 },
{ 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137, 137,
137, 137, 137, 137, 137, 1, 1, 1 },
{ 137, 137, 137, 1, 1, 137, 137, 137, 137, 137, 137, 137, 137,
137, 137, 137, 1, 1, 1, 1 },
{ 1, 137, 137, 1, 1, 1, 137, 137, 137, 137, 137, 137, 137, 137,
137, 137, 137, 1, 137, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 137, 137, 137, 137, 137, 137, 137, 137,
137, 137, 137, 137, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 137, 137, 137, 137, 1, 1, 1, 137, 137,
137, 137, 137, 137 },
{ 1, 1, 1, 1, 1, 1, 1, 1, 137, 137, 137, 137, 1, 1, 1, 137,
137, 137, 137, 1 },
{ 1, 1, 1, 1, 1, 1, 137, 137, 137, 137, 137, 137, 137, 1, 1, 1,
1, 137, 137, 137 },
{ 1, 1, 1, 1, 1, 1, 137, 137, 137, 137, 137, 1, 1, 1, 1, 1, 1,
1, 137, 137 },
{ 1, 1, 1, 1, 1, 1, 1, 137, 137, 137, 137, 1, 1, 1, 1, 1, 1,
137, 137, 137 },
{ 1, 1, 1, 1, 1, 137, 137, 137, 137, 137, 137, 137, 1, 1, 1, 1,
1, 137, 137, 137 },
{ 1, 1, 1, 1, 1, 1, 137, 137, 137, 137, 137, 137, 1, 1, 1, 1,
1, 1, 1, 1 },
{ 1, 1, 1, 1, 1, 1, 137, 137, 137, 137, 137, 1, 1, 1, 1, 1, 1,
1, 1, 1 },
{ 1, 1, 1, 1, 1, 1, 137, 137, 137, 137, 137, 1, 1, 1, 1, 1, 1,
1, 1, 1 },
{ 1, 1, 1, 1, 1, 1, 137, 137, 137, 137, 137, 1, 1, 1, 1, 1, 1,
1, 1, 1 },
{ 1, 1, 1, 1, 1, 1, 1, 137, 137, 137, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1 } };
// 第二層游戲實體actor數組
public int[][] mMapAcotor = {
{ 143, 144, 0, 102, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
185, 186, 187, 188 },
{ 151, 152, 0, 110, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
193, 194, 195, 196 },
{ 159, 160, 0, 110, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
201, 202, 203, 204 },
{ 0, 0, 0, 126, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
209, 210, 211, 212 },
{ 0, 0, 0, 134, 136, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 218, 219, 220 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 227, 228 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0 },
{ 102, 103, 103, 103, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0 },
{ 110, 111, 111, 111, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0 },
{ 110, 111, 111, 111, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0 },
{ 126, 127, 127, 127, 128, 0, 0, 0, 0, 0, 0, 0, 165, 166, 0, 0,
0, 0, 0, 0 },
{ 123, 124, 124, 124, 125, 0, 0, 0, 0, 0, 0, 0, 173, 174, 175, 176,
0, 0, 0, 0 },
{ 229, 230, 231, 232, 0, 0, 0, 0, 0, 0, 0, 0, 181, 182, 183, 184,
0, 0, 0, 0 },
{ 237, 238, 239, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0 },
{ 245, 246, 247, 248, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0 },
{ 0, 254, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 233, 234, 235,
236, 0, 0, 0 },
{ 0, 262, 263, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 241, 242, 243,
244, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 250, 251,
0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 258, 259,
0, 0, 143, 144 }
};
// 第三層游戲碰撞物理層數組
// 下一章介紹
// ....................
// 游戲地圖資源
Bitmap mBitmap = null;
// 資源文件
Resources mResources = null;
// 游戲畫筆
Paint mPaint = null;
// 橫向縱向tile塊的數量
int mWidthTileCount = 0;
int mHeightTileCount = 0;
// 橫向縱向tile塊的數量
int mBitMapWidth = 0;
int mBitMapHeight = 0;
//攝像頭的焦點 0.0點為焦點在中心。
int mCameraPosX = 0;
int mCameraPosY = 0;
//地圖左上角錨點坐標
int mMapPosX =0;
int mMapPosY =0;
//記錄上次時間
private long statrTime = 0;
/**
* 構造方法
*
* @param context
*/
public MapView(Context context,int screenWidth, int screenHeight) {
super(context);
mScreenHeight = screenHeight;
mScreenWidth = screenWidth;
mPaint = new Paint();
mBitmap = ReadBitMap(context, R.drawable.map);
mBitMapWidth = mBitmap.getWidth();
mBitMapHeight = mBitmap.getHeight();
mWidthTileCount = mBitMapWidth / TILE_WIDTH;
mHeightTileCount = mBitMapHeight / TILE_HEIGHT;
statrTime = System.currentTimeMillis();
}
@Override
protected void onDraw(Canvas canvas) {
UpdateCamera();
DrawMap(canvas, mBitmap);
DrawRectText(canvas);
super.onDraw(canvas);
invalidate();
}
private void DrawMap(Canvas canvas, Bitmap bitmap) {
int i, j;
for (i = 0; i < TILE_HEIGHT_COUNT; i++) {
for (j = 0; j < TILE_WIDTH_COUNT; j++) {
int ViewID = mMapView[i][j];
int ActorID = mMapAcotor[i][j];
int x = (j* TILE_WIDTH) + mMapPosX;
int y = (i* TILE_HEIGHT) + mMapPosY;
// 繪制地圖第一層
if (ViewID > TILE_NULL) {
DrawMapTile(ViewID, canvas, mPaint, bitmap, x, y);
}
// 繪制地圖第二層
if (ActorID > TILE_NULL) {
DrawMapTile(ActorID, canvas, mPaint, bitmap, x, y);
}
}
}
}
private void DrawRectText(Canvas canvas) {
canvas.clipRect(0, 0,mScreenWidth, 30);
mPaint.setColor(Color.WHITE);
canvas.drawRect(0, 0,mScreenWidth, 30, mPaint);
mPaint.setColor(Color.RED);
canvas.drawText("當前攝像頭X坐標:" + mCameraPosX + "當前攝像頭Y坐標 :" + mCameraPosY, 0, 20, mPaint);
}
private void UpdateCamera() {
long nowTime = System.currentTimeMillis();
//每100毫秒更新一下攝像頭的位置
if(nowTime - statrTime > 1000) {
//隨機獲得攝像頭的坐標
mCameraPosX = UtilRandom(0,MAP_WIDTH - mScreenWidth);
mCameraPosY = UtilRandom(0,MAP_HEIGHT - mScreenHeight);
//根據攝像頭的坐標更新地圖坐標
mMapPosX = -mCameraPosX;
mMapPosY = -mCameraPosY;
statrTime = nowTime;
}
}
/**
* 返回一個隨機數
* @param botton
* @param top
* @return
*/
private int UtilRandom(int botton, int top) {
return ((Math.abs(new Random().nextInt()) % (top - botton)) + botton);
}
/**
* 根據ID繪制一個tile塊
*
* @param id
* @param canvas
* @param paint
* @param bitmap
*/
private void DrawMapTile(int id, Canvas canvas, Paint paint,
Bitmap bitmap, int x, int y) {
// 根據數組中的ID算出在地圖資源中的XY 坐標
// 因為編輯器默認0 所以第一張tile的ID不是0而是1 所以這裡 -1
id--;
int count = id / mWidthTileCount;
int bitmapX = (id - (count * mWidthTileCount)) * TILE_WIDTH;
int bitmapY = count * TILE_HEIGHT;
DrawClipImage(canvas, paint, bitmap, x, y, bitmapX, bitmapY,
TILE_WIDTH, TILE_HEIGHT);
}
/**
* 讀取本地資源的圖片
*
* @param context
* @param resId
* @return
*/
public Bitmap ReadBitMap(Context context, int resId) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
// 獲取資源圖片
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is, null, opt);
}
/**
* 繪制圖片中的一部分圖片
*
* @param canvas
* @param paint
* @param bitmap
* @param x
* @param y
* @param src_x
* @param src_y
* @param src_width
* @param src_Height
*/
private void DrawClipImage(Canvas canvas, Paint paint, Bitmap bitmap,
int x, int y, int src_x, int src_y, int src_xp, int src_yp) {
canvas.save();
canvas.clipRect(x, y, x + src_xp, y + src_yp);
canvas.drawBitmap(bitmap, x - src_x, y - src_y, paint);
canvas.restore();
}
}
}
最後如果你還是覺得我寫的不夠詳細 看的不夠爽 不要緊我把源代碼的下載地址貼出來 歡迎大家一起討論學習。
源碼下載:CameraView