欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SurfaceView開發(fā)[捉小豬]手機(jī)游戲 (二)

 更新時間:2021年08月25日 17:43:01   作者:陳小緣  
這篇文章主要介紹了用SurfaceView開發(fā)[捉小豬]手機(jī)游戲 (二)本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下

我們在上一回(Android使用SurfaceView開發(fā)《捉小豬》小游戲 (一))搞懂了這個模式的基本實(shí)現(xiàn)思路,小豬如何找出最短的逃跑路線和如何播放路徑動畫. 還封裝了我們自己的PathAnimation和Drawable。
還差下面樹樁出現(xiàn)的效果:

 

哈哈,記得植物大戰(zhàn)僵尸里面有個關(guān)卡的道具出現(xiàn)也是這種效果的。
本來做這個效果的時候,想著用一個方便快捷的方法:一個新線程中,不斷遍歷已出現(xiàn)的樹樁,然后判斷是否已到達(dá)目標(biāo)位置,如果未到達(dá)就直接 x - -
后來發(fā)現(xiàn),用這個方法存在三個問題:
1. 某個任務(wù),假設(shè)在配置一般的手機(jī)上面運(yùn)行,需要1秒,那么在一些配置較高的手機(jī)上,可能0.2秒就完成了,試想一下我們的這個方法,如果運(yùn)行在高配置的手機(jī)上,那偏移的速度,你懂的。
2. 因?yàn)槊看沃皇窍蜃笃?個像素點(diǎn),所以在屏幕分辨率較高的手機(jī)上面,移動的就會比小屏的手機(jī)慢,哈哈,當(dāng)然了,解決這個問題可以用動態(tài)調(diào)整偏移量的方法,比如現(xiàn)在在720*1280的手機(jī)上面,每次的偏移量是2,那么在1440*2560的手機(jī)上面就是4了,這樣的話,即使屏幕分辨率相差很遠(yuǎn),樹樁偏移的時間也差不多是一樣的。
3. 還記不記得多線程在單核cpu上面是怎么工作的?哈哈,雖然現(xiàn)在的手機(jī)都不是單核的,但是也會出現(xiàn)cpu滿載的情況,當(dāng)cpu比較忙碌時,可能一些優(yōu)先級比較低的線程,就會得不到照顧。想一下,因?yàn)槲覀冇玫氖敲看纹埔欢ň嚯x的方法,也就是它每偏移一次,都是建立在線程爭取到cpu時間片的基礎(chǔ)上,才能更新位置,當(dāng)cpu任務(wù)較多時,線程獲取到時間片的周期也會變長,周期一長,那么樹樁的位置更新,也會變慢。

所以這種方法不可取,那么我們用哪種方法呢?
記不記得我們在上一回中,自己封裝了個動畫類,動畫進(jìn)度的更新,是根據(jù)當(dāng)前動畫已執(zhí)行時間和動畫時長來計算的。
我們也可以用這個方法來做樹樁的偏移動畫,不過首先,肯定不能每個樹樁對應(yīng)一個線程的,這樣無疑會增大cpu的開銷,正確的方法應(yīng)該只開一個線程來控制全部的樹樁。
我們來新建一個輔助類PropOffsetHelper:
里面維護(hù)一個PropData的list,這個PropData里面有一個我們自定義的drawable, 還有記錄上一次更新的時間:

public class PropData {
    public MyDrawable drawable;
    public long lastUpdateTime;

    public PropData(MyDrawable drawable) {
        this.drawable = drawable;
        lastUpdateTime = SystemClock.uptimeMillis();
    }

    public void draw(Canvas canvas) {
        drawable.draw(canvas);
    }

    public float getX() {
        return drawable.getX();
    }

    public void setX(float x) {
        drawable.setX(x);
    }

    public float getY() {
        return drawable.getY();
    }

    public void setY(float y) {
        drawable.setY(y);
    }

    public void release() {
        if (drawable != null) {
            drawable.release();
        }
    }

    @Override
    public String toString() {
        return String.format(Locale.getDefault(), "%f, %f", drawable.getX(), drawable.getY());
    }
}

我們的PropOffsetHelper聲明以下成員變量:

    private float mPropOffsetSpeed;//樹頭的移動速度
    private MyDrawable mPropDrawable;//樹頭的圖片
    private List<PropData> mProps;//全部樹頭的數(shù)據(jù)
    private List<Integer> mLeavedProps;//已放置的樹頭(索引)
    private float mStartX, mStartY;//樹頭一開始的位置
    private int mPropSize;//樹頭尺寸
    private Future mUpdateTask;//更新位置的線程
    private float mLeftOffset;//左邊的偏移量
    private volatile boolean isNeed;//是否需要更新位置
    private long mLastStopTime;//上一次暫停的時間

下面我們來看最重要的方法:

    /**
     * 開始更新樹樁的位置
     */
    public void startComputeOffset() {
        updatePropGenerateTime();
        isNeed = true;
        //更新樹頭位置線程
        mUpdateTask = ThreadPool.getInstance().execute(() -> {
            boolean isFinished;//樹頭是否已經(jīng)到對應(yīng)的位置
            float distance,//需要偏移的路程
                    offset;//本次更新的偏移量
            int hitOffsetCount;//排在該樹頭前面的,并且已經(jīng)離隊(duì)的(已放置),需要忽略距離
            long intervalTime,//上次更新與現(xiàn)在的間隔時間
                    updateTime;//今次更新時間
            while (isNeed) {
                for (int i = 0; i < mProps.size(); i++) {
                    PropData prop = mProps.get(i);
                    //已離隊(duì)的不需要更新位置
                    if (mLeavedProps.contains(i)) {
                        continue;
                    }
                    //計算出總距離
                    distance = i * mPropSize + mLeftOffset;
                    //離隊(duì)樹樁數(shù)量
                    hitOffsetCount = 0;
                    for (int j = 0; j < mLeavedProps.size(); j++) {
                        //檢查是否有離隊(duì)的樹頭
                        if (mLeavedProps.get(j) < i) {
                            hitOffsetCount++;
                        }
                    }
                    //減去已離隊(duì)的樹樁占用的位置,得出真實(shí)的位置
                    distance -= mPropSize * hitOffsetCount;
                    //樹樁的x軸小于或等于實(shí)際的偏移距離,則認(rèn)為已經(jīng)偏移完成,不需要繼續(xù)更新位置
                    isFinished = prop.getX() <= distance;
                    updateTime = SystemClock.uptimeMillis();
                    if (!isFinished) {
                        //計算間隔時間
                        intervalTime = updateTime - prop.lastUpdateTime;
                        //路程 = 時間 * 速度
                        offset = intervalTime * mPropOffsetSpeed;
                        //更新x軸位置
                        prop.setX(prop.getX() - offset);
                    }
                    //刷新上一次的更新時間
                    prop.lastUpdateTime = updateTime;
                }
            }
        });
    }

    /**
     * 更新線程停止后又重新開始,需要加上停止的這段時間
     */
    private void updatePropGenerateTime() {
        if (mLastStopTime > 0) {
            //總停止時間 = 當(dāng)前時間 - 上次更新時間
            long totalStoppedTime = SystemClock.uptimeMillis() - mLastStopTime;
            mLastStopTime = 0;
            for (int i = 0; i < mProps.size(); i++) {
                //加上這段時間
                mProps.get(i).lastUpdateTime += totalStoppedTime;
            }
        }
    }

這次我們樹樁的位置是根據(jù)時間來更新的, 這樣就算在cpu滿載的時候,也不會出現(xiàn)偏移很慢的情況,只是屏幕刷新頻率慢了(掉幀)最多只是偏移的動畫不是那么流暢而已。


好了,現(xiàn)在我們的準(zhǔn)備工作都已經(jīng)差不多了,下面我們來將它們拼到一起。
我們先看這張圖:

這里寫圖片描述 

有沒有發(fā)現(xiàn),一個小格子只能容納1樣?xùn)|西(小豬或者樹樁),如果格子上面已經(jīng)有東西了的話,再放東西上去,是會自動偏移到離他最近的一個空閑的格子上的。好吧,我們先搞定格子的位置吧:
聲明兩個二維數(shù)組(一個存放格子坐標(biāo),一個記錄格子狀態(tài):小豬占用、樹樁占用、空閑):

    private Rect[][] mItems;//矩形二維數(shù)組
    private volatile int[][] mItemStatus;//用來保存對應(yīng)的矩形狀態(tài)(小豬占用,木頭占用,空閑)

下面我們來看看怎么初始化格子的坐標(biāo):

    private void initItems() {
        mItems = new Rect[VERTICAL_COUNT][HORIZONTAL_COUNT];
        mItemStatus = new int[VERTICAL_COUNT][HORIZONTAL_COUNT];
        int currentX, currentY;
        int childrenY = (getHeight() - mItemSize * VERTICAL_COUNT - mItemSize) / 2 + mItemSize / 2,
                childrenX = (getWidth() - mItemSize * HORIZONTAL_COUNT - mItemSize) / 2 + mItemSize / 2;
        //初始化矩形二維數(shù)組, 用單雙行交錯的方式排列
        for (int vertical = 0; vertical < VERTICAL_COUNT; vertical++) {
            currentY = mItemSize * vertical;
            for (int horizontal = 0; horizontal < HORIZONTAL_COUNT; horizontal++) {
                //如果行數(shù)是雙數(shù),則向右偏移半個格子
                currentX = mItemSize * horizontal + (vertical % 2 == 0 ? mItemSize / 2 : 0);
                Rect rect = new Rect(childrenX + currentX, childrenY + currentY,
                        childrenX + currentX + mItemSize, childrenY + currentY + mItemSize);
                mItems[vertical][horizontal] = rect;
                changeItemStatus(vertical, horizontal, Item.STATE_UNSELECTED);
            }
        }
    }

我們現(xiàn)在看到的效果是這樣的:

這里寫圖片描述 

我們放置樹樁的時候,肯定不是每次都剛好落到格子的中心點(diǎn)的,所以當(dāng)手指松開的時候,還要我們?nèi)フ{(diào)整一下樹樁的位置,好讓它剛好落到中心點(diǎn)上,當(dāng)然了,我們還要判斷離它最近的格子上是不是空閑狀態(tài),如果不是,那就尋找下一個,直到找到空閑的格子為止。
我們先看看當(dāng)手指松開后,怎么確定樹樁的位置:
還記不記得我們的格子(Rect) 存放在一個二位數(shù)組里面?當(dāng)拖動樹樁的手指松開后,我們可以遍歷這個二維數(shù)組,然后逐個判斷event.getX和getY是否在該矩形里面,如果在,那就根據(jù)它的坐標(biāo)來確定樹樁的位置了,如果它是不可放置狀態(tài)(小豬占用或已有樹樁) 那就以它為起點(diǎn)尋找下一個空閑的格子,哈哈,這個還是用深度優(yōu)先遍歷來實(shí)現(xiàn):

    /**
     * 以currentPos為中心點(diǎn),向周圍6個方向查找空閑的位置(廣度優(yōu)先遍歷)
     * @param items 格子狀態(tài)
     * @param ignorePos 需要忽略的格子
     * @param currentPos 起始的格子(以這個格子為起點(diǎn)向四周查找)
     * @return 空閑的格子
     */
    public static WayData findNextUnSelected(int[][] items, List<WayData> ignorePos, WayData currentPos) {
        int verticalCount = items.length;
        int horizontalCount = items[0].length;
        Queue<WayData> way = new ArrayDeque<>();
        int[][] pattern = new int[verticalCount][horizontalCount];
        for (int vertical = 0; vertical < verticalCount; vertical++) {
            //復(fù)制數(shù)組(因?yàn)橐獙?shù)組元素值進(jìn)行修改,且不能影響原來的)
            System.arraycopy(items[vertical], 0, pattern[vertical], 0, horizontalCount);
        }
        way.offer(currentPos);//當(dāng)前pos先入隊(duì)
        pattern[currentPos.y][currentPos.x] = STATE_WALKED;//狀態(tài)標(biāo)記(已走過)
        while (!way.isEmpty()) {//隊(duì)列不為空
            WayData header = way.poll();//隊(duì)頭出隊(duì)
            List<WayData> directions = getCanArrivePosUnchecked(pattern, header);//獲取周圍6個方向的位置(不包括越界的)
            //遍歷周邊的位置
            for (int i = 0; i < directions.size(); i++) {
                WayData direction = directions.get(i);
                //判斷該位置是否空閑,如果是空閑則直接返回,如果不是空閑,則入隊(duì),下次以它為中心,尋找周邊的元素
                if (!currentPos.equals(direction) && items[direction.y][direction.x] == Item.STATE_UNSELECTED
                        && !(ignorePos != null && ignorePos.contains(direction))) {
                    return direction;
                } else {
                    way.offer(direction);
                }
            }
        }
        //隊(duì)列直至為空還沒返回,則找不到了
        return null;
    }

我們找到這個空閑的格子之后,更新格子狀態(tài),然后再檢測當(dāng)前小豬的路徑動畫中,是否經(jīng)過這個格子,如果經(jīng)過這個格子的話,需要重新找路徑(不能在樹樁上面走過):

    /**
     * 通知有新的樹頭放下, 有逃跑路徑在這個新占用位置上的小豬,都要重新計算新的逃跑路線(舊的已經(jīng)無效了)
     */
    private void positionOccupied(int vertical, int horizontal) {
        for (int i = 0; i < PIGGY_COUNT; i++) {
            Pig pig = mPiggies[i];
            List<WayData> pathData = pig.getPathData();
            if (pathData == null || pig.getState() != Pig.STATE_RUNNING) {
                continue;
            }
            int currentIndex = -1;
            if (pig.isRepeatAnimation()) {
                currentIndex = 0;
            } else {
                for (int j = 0; j < pathData.size(); j++) {
                    WayData pos = pig.getPosition();
                    if (pathData.get(j).equals(pos)) {
                        currentIndex = j;
                        break;
                    }
                }
            }
            if (currentIndex != -1) {
                for (int k = currentIndex; k < pathData.size(); k++) {
                    if (pathData.get(k).x == horizontal && pathData.get(k).y == vertical) {
                        stopTask(pig);
                        pig.setState(Pig.STATE_STANDING);
                        initRunAnimation(pig, true);
                        break;
                    }
                }
            }
        }
    }

我們再來看一下這張圖:

這里寫圖片描述 

有沒有發(fā)現(xiàn),四個格子里面剛好放進(jìn)了四只小豬,第五只怎么放也放不下,盡管有時候最下面的那個格子沒有小豬。
如果小豬的位置不會變,是固定的話,那還好辦,但是現(xiàn)在的小豬位置是不斷的在變的,而且小豬之間還會重疊,這樣一來,如果是直接根據(jù)小豬當(dāng)前位置去判斷的話,肯定是不行的,那應(yīng)該要怎么做呢?
哈哈,看這個:

    /**
     * 判斷當(dāng)前位置是否能放置樹樁或小豬(如果在一個封閉的圈子里面,則連小豬當(dāng)前位置也要計算)例如:(0表示樹頭 .表示小豬)
     * * * * * * *
     *  * 0 0 0 * *
     * * 0 . . 0 *
     *  * 0 * 0 * *
     * * * 0 0 * *
     *  * * * * * *
     * 計算出來空閑的結(jié)果是1,也即是可以放置,如果再多一個小豬在里面,則不可放置
     * @param items 格子狀態(tài)
     * @param occupiedPos 小豬們的所在位置
     * @param currentPos 起點(diǎn)
     * @param result 空閑的格子
     * @return 圈子內(nèi)能否放置
     */
    public static boolean isCurrentPositionCanSet(int[][] items, WayData[] occupiedPos, WayData currentPos, List<WayData> result) {
        int verticalCount = items.length;
        int horizontalCount = items[0].length;
        Queue<WayData> way = new ArrayDeque<>();
        int[][] pattern = new int[verticalCount][horizontalCount];
        for (int vertical = 0; vertical < verticalCount; vertical++) {
            //復(fù)制數(shù)組(因?yàn)橐獙?shù)組元素值進(jìn)行修改,且不能影響原來的)
            System.arraycopy(items[vertical], 0, pattern[vertical], 0, horizontalCount);
        }
        for (WayData tmp : occupiedPos) {
            if (tmp != null) {
                //先將小豬們占用的位置標(biāo)記未未選中
                pattern[tmp.y][tmp.x] = Item.STATE_UNSELECTED;
            }
        }
        //以currentPos為起點(diǎn)
        way.offer(currentPos);
        //標(biāo)記狀態(tài)(已走過)
        pattern[currentPos.y][currentPos.x] = STATE_WALKED;
        if (items[currentPos.y][currentPos.x] != Item.STATE_SELECTED) {
            //如果起點(diǎn)也是空閑狀態(tài),則算他一個
            result.add(currentPos);
        }
        //開始廣度優(yōu)先遍歷
        while (!way.isEmpty()) {
            //隊(duì)頭出隊(duì)
            WayData header = way.poll();
            //尋找周圍6個方向可以到達(dá)的位置(不包括越界的,標(biāo)記過的,不是空閑的)也就是空閑的格子
            List<WayData> directions = getCanArrivePos(pattern, header);
            for (int i = 0; i < directions.size(); i++) {
                WayData direction = directions.get(i);
                //將這些位置添加進(jìn)去
                result.add(direction);
                way.offer(direction);
            }
        }
        int count = 0;
        //重點(diǎn)來了
        //現(xiàn)在result里面保存的位置,都是忽略了小豬的坐標(biāo)的,所以現(xiàn)在要重新計算一下
        //遍歷小豬們當(dāng)前所在位置,是否在result中,如果在,記錄一下
        for (WayData tmp : occupiedPos) {
            if (tmp != null && result.contains(tmp)) {
                count++;
            }
        }
        //最后,如果空閑格子內(nèi)的小豬數(shù) < 總的空閑格子數(shù),則認(rèn)為這個圈內(nèi)還能放得下,反之
        return count < result.size();
    }

有的小伙伴看完可能就會有疑惑,為什么用List的contains方法判斷也可以呢?它們的內(nèi)存地址可能都不相同的啊,
哈哈,這個,我們先來看一下ArrayList中contains方法是怎么實(shí)現(xiàn)的:

    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

調(diào)用了indexOf方法,那我們再看看indexOf:

    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

哈哈,有沒有發(fā)現(xiàn),如果我們傳進(jìn)去的對象不為空,那么它就會調(diào)用這個對象的equals方法,看到這個方法,我們大多數(shù)時候,都是只用來判斷字符串是否一樣是吧,這個方法在Object類中,是直接返回 this == obj的,那么我們可以在WayData中重寫equals方法,然后再判斷它們的x和y是否相等就行了,嘻嘻:

    @Override
    public boolean equals(Object obj) {
        return obj instanceof WayData ? x == ((WayData) obj).x && y == ((WayData) obj).y : this == obj;
    }

好了,本篇文章到此結(jié)束,有錯誤的地方請指出,謝謝大家!
完整代碼地址: https://github.com/wuyr/CatchPiggy
游戲主頁: https://wuyr.github.io/

到此這篇關(guān)于SurfaceView開發(fā)[捉小豬]手機(jī)游戲 (二)的文章就介紹到這了,更多相關(guān)SurfaceView游戲內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Android?JobScheduler詳細(xì)介紹

    Android?JobScheduler詳細(xì)介紹

    JobScheduler是Android?5.0引入的系統(tǒng)服務(wù),它可以根據(jù)網(wǎng)絡(luò)狀態(tài)、充電狀態(tài)、電量和存儲狀況等來觸發(fā)相應(yīng)的JobService執(zhí)行任務(wù),它支持多條件組合、持久性任務(wù),以及在API?21以上版本的Android系統(tǒng)中使用,對Android?JobScheduler相關(guān)知識感興趣的朋友跟隨小編一起看看吧
    2024-09-09
  • Android如何自定義按鈕效果

    Android如何自定義按鈕效果

    這篇文章主要為大家詳細(xì)介紹了Android如何自定義按鈕效果的方法,幫助大家設(shè)計簡潔大方的按鈕,感興趣的小伙伴們可以參考一下
    2016-08-08
  • Android通過XListView實(shí)現(xiàn)上拉加載下拉刷新功能

    Android通過XListView實(shí)現(xiàn)上拉加載下拉刷新功能

    這篇文章主要為大家詳細(xì)介紹了Android通過XListView實(shí)現(xiàn)上拉加載下拉刷新功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • Android控件之EditView常用屬性及應(yīng)用方法

    Android控件之EditView常用屬性及應(yīng)用方法

    本篇文章介紹了,Android控件之EditView常用屬性及應(yīng)用方法。需要的朋友參考下
    2013-04-04
  • Android中數(shù)據(jù)解析的五種方式

    Android中數(shù)據(jù)解析的五種方式

    今天小編就為大家分享一篇關(guān)于Android中數(shù)據(jù)解析的五種方式,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-02-02
  • Android實(shí)現(xiàn)動態(tài)高斯模糊效果

    Android實(shí)現(xiàn)動態(tài)高斯模糊效果

    在Android開發(fā)中常常會用到高斯模糊,但有的時候我們可能會需要一個圖片以不同的模糊程度展現(xiàn)出來,那如何實(shí)現(xiàn)呢,一起通過本文來學(xué)習(xí)學(xué)習(xí)吧。
    2016-08-08
  • Android視頻處理之動態(tài)時間水印效果

    Android視頻處理之動態(tài)時間水印效果

    這篇文章主要A為大家詳細(xì)介紹了Android視頻處理之動態(tài)時間水印效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-09-09
  • FragmentTabHost使用方法詳解

    FragmentTabHost使用方法詳解

    這篇文章主要為大家詳細(xì)介紹了Android中FragmentTabHost的使用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-08-08
  • Android開發(fā)仿QQ空間根據(jù)位置彈出PopupWindow顯示更多操作效果

    Android開發(fā)仿QQ空間根據(jù)位置彈出PopupWindow顯示更多操作效果

    我們打開QQ空間的時候有個箭頭按鈕點(diǎn)擊之后彈出PopupWindow會根據(jù)位置的變化顯示在箭頭的上方還是下方,比普通的PopupWindow彈在屏幕中間顯示好看的多,今天就給大家分享下實(shí)現(xiàn)代碼,需要的朋友參考下吧
    2016-12-12
  • Android實(shí)現(xiàn)View拖拽跟隨手指移動效果

    Android實(shí)現(xiàn)View拖拽跟隨手指移動效果

    這篇文章主要介紹了Android實(shí)現(xiàn)View拖拽跟隨手指移動效果,主要使用setTranslationX() 和setTranslationY() 屬性方法實(shí)現(xiàn)的,需要的朋友參考下吧
    2017-08-08

最新評論