View 如何对 SurfaceView 的方法调用做出反应?

How can a View react to a Method call of a SurfaceView?

继续
我正在编写吃豆人程序,它几乎完成了,但是我在显示得分和吃豆人当前方向的视图与绘制游戏的表面视图之间的通信方面遇到了问题。

PlayActivity(这是一个 AppCompatActivity)有一个 GameView(这是一个绘制游戏的 SurfaceView)并且知道 GameManager(我制作的一个 class 基本上知道 pacman,地图,幽灵,以及一切)。 PlayActivity 还有一个名为 scoreTv 的文本视图。我不知道如何更新这个文本视图。

想法是每当 GameManager 的方法 addScore(int),eatPallet(int, int), eatBonus(int,int) 时,scoreTv 都会更改其文本值; eatSuperPallet(int,int) 被调用。

这里是PlayActivity代码

package Activities;

import android.os.Bundle;
import android.view.SurfaceView;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.example.pacman.DBManager;
import Game.GameView;
import com.example.pacman.R;
import com.example.pacman.Score;

public class PlayActivity extends AppCompatActivity {
    private TextView playerNickname;
    TextView score;
    private TextView maxScore;
    private SurfaceView gameSurfaceView;
    private GameView gameView;


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //Modified code
        setContentView(R.layout.activity_game);
        //we get text view that we will use
        playerNickname=(TextView) this.findViewById(R.id.tv_player);
        score=(TextView) this.findViewById(R.id.tv_current_score);
        maxScore=(TextView) this.findViewById(R.id.tv_current_max_score);
        gameSurfaceView= (GameView) this.findViewById(R.id.game_view);

        //set text view initial values
        playerNickname.setText(getIntent().getExtras().getString("playerNickname"));
        score.setText("0");

        maxScore.setText("To modify");


        this.gameView=new GameView(gameSurfaceView.getContext());
        this.gameSurfaceView.getHolder().addCallback(this.gameView);
    }

    protected void onResume(){
        super.onResume();
        this.gameView.resume();
    }
    protected void onPause(){
        super.onPause();
        this.gameView.pause();
    }

    public void updateScore(int score){
        this.score.setText(""+score);
    }

    public void onLose(double score){
        //We try to save the score, if there is a previous register we write only if this score
        //is better that the one before
        DBManager manager;
        long raw;
        Score scoreToSave;
        manager=new DBManager(this);

        scoreToSave=new Score(this.playerNickname.toString(), score);
        if(manager.saveScore(scoreToSave)==-1){
            //if i couldn't save the score
            if(manager.updateScore(scoreToSave)!=-1){
                //if my new score is better than the one previous
            }else{
                //if my new score is worse or equal than the one previous
            }
        }
    }
}

这是游戏视图代码

package Game;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.TextView;

import androidx.annotation.RequiresApi;

import Activities.PlayActivity;
import Game.Character_package.Ghost;
import Game.Character_package.Pacman;

public class GameView extends SurfaceView implements Runnable, SurfaceHolder.Callback, GestureDetector.OnGestureListener {
    private static final float SWIPE_THRESHOLD = 2;
    private static final float SWIPE_VELOCITY = 2;
    private boolean GHOST_INICIALIZED=false;
    private GestureDetector gestureDetector;
    private GameManager gameManager;
    private Thread thread; //game thread
    private SurfaceHolder holder;
    private boolean canDraw = false;
    private int blockSize;                // Ancho de la pantalla, ancho del bloque
    private static int movementFluencyLevel=8; //this movement should be a multiple of the blocksize and multiple of 4, if note the pacman will pass walls

    private int totalFrame = 4;             // Cantidad total de animation frames por direccion
    private int currentArrowFrame = 0;      // animation frame de arrow actual
    private long frameTicker;               // tiempo desde que el ultimo frame fue dibujado

    //----------------------------------------------------------------------------------------------
    //Constructors
    public GameView(Context context) {
        super(context);
        this.constructorHelper(context);
    }

    public GameView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.constructorHelper(context);
    }

    public GameView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        this.constructorHelper(context);

    }

    private void constructorHelper(Context context) {
        this.gestureDetector = new GestureDetector(this);
        this.setFocusable(true);
        this.holder = getHolder();
        this.holder.addCallback(this);
        this.frameTicker = (long) (1000.0f / totalFrame);

        this.gameManager=new GameManager(this);

        int screenWidth=getResources().getDisplayMetrics().widthPixels;
        this.blockSize = ((((screenWidth/this.gameManager.getGameMap().getMapWidth())/movementFluencyLevel)*movementFluencyLevel)/4)*4;
        this.holder.setFixedSize(blockSize*this.gameManager.getGameMap().getMapWidth(),blockSize*this.gameManager.getGameMap().getMapHeight());

        this.gameManager.getGameMap().loadBonusBitmaps(this);
        this.gameManager.setPacman(new Pacman("pacman","",this,this.movementFluencyLevel));

        Ghost.loadCommonBitmaps(this);
    }
    //----------------------------------------------------------------------------------------------
    //Getters and setters
    public int getBlockSize() {
        return blockSize;
    }
    public GameManager getGameManager() {
        return gameManager;
    }
    public int getMovementFluencyLevel(){return movementFluencyLevel;}
    //----------------------------------------------------------------------------------------------

    private synchronized void initGhost(){
        if(!GHOST_INICIALIZED){
            GHOST_INICIALIZED=true;
            this.gameManager.initGhosts(this);
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.N)
    @Override
    public void run() {
        long gameTime;
        Canvas canvas;
        while (!holder.getSurface().isValid()) {
        }
        this.initGhost();
        this.setFocusable(true);
        while (canDraw) {
            gameTime=System.currentTimeMillis();
            if(gameTime > frameTicker + (totalFrame * 15)){
                canvas = holder.lockCanvas();
                if(canvas!=null){
                    if(this.updateFrame(gameTime,canvas)){
                        try {
                            Thread.sleep(3000);
                        }catch (Exception e){}
                    }
                    holder.unlockCanvasAndPost(canvas);
                    if(this.gameManager.checkWinLevel()){
                        canDraw=false;
                        this.gameManager.cancelThreads();
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {}
                        //animation
                        Log.i("Game","You win");
                    }else if(!this.gameManager.getPacman().hasLifes()){
                        //we lost

                        canDraw=false;
                        this.gameManager.cancelThreads();

                        //animation
                        Log.i("Game","You lose");
                    }
                }
            }
        }

    }

    // Method to capture touchEvents
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        //To swipe
        //https://www.youtube.com/watch?v=32rSs4tE-mc
        this.gestureDetector.onTouchEvent(event);
        super.onTouchEvent(event);
        return true;
    }

    //Chequea si se deberia actualizar el frame actual basado en el
    // tiempo que a transcurrido asi la animacion
    //no se ve muy rapida y mala
    @RequiresApi(api = Build.VERSION_CODES.N)
    private boolean updateFrame(long gameTime, Canvas canvas) {
        Pacman pacman;
        Ghost[] ghosts;
        boolean pacmanIsDeath;

        pacman=this.gameManager.getPacman();
        ghosts=this.gameManager.getGhosts();

        // Si el tiempo suficiente a transcurrido, pasar al siguiente frame
        frameTicker = gameTime;
        canvas.drawColor(Color.BLACK);
        this.gameManager.getGameMap().draw(canvas, Color.BLUE,this.blockSize,this.gameManager.getLevel());
        this.gameManager.moveGhosts(canvas,this.blockSize);
        pacmanIsDeath=pacman.move(this.gameManager,canvas);

        if(!pacmanIsDeath){
            // incrementar el frame
            pacman.changeFrame();
            for(int i=0; i<ghosts.length;i++){
                ghosts[i].changeFrame();
            }
            currentArrowFrame++;
            currentArrowFrame%=7;
        }else{
            pacman.setNextDirection(' ');
            for(int i=0; i<ghosts.length;i++){
                ghosts[i].respawn();
            }
        }
        return pacmanIsDeath;
    }

    //----------------------------------------------------------------------------------------------
    //Callback methods
    @RequiresApi(api = Build.VERSION_CODES.N)
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        canDraw = true;
        this.thread= new Thread(this);
        this.thread.start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
    }

    //----------------------------------------------------------------------------------------------
    public void resume() {
        this.canDraw = true;
        thread = new Thread(this);
        thread.start();
    }

    public void pause() {
        this.canDraw = false;
        while (true) {
            try {
                thread.join();
                return;
            } catch (InterruptedException e) {
                // retry
            }
            break;
        }
        this.thread=null;
    }

    @Override
    public boolean onDown(MotionEvent e) {
        return false;
    }

    @Override
    public void onShowPress(MotionEvent e) {
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        return false;
    }

    @Override
    public void onLongPress(MotionEvent e) {
    }

    @Override
    public boolean onFling(MotionEvent downEvent, MotionEvent moveEvent, float velocityX, float velocityY) {
        //To swipe
        //https://www.youtube.com/watch?v=32rSs4tE-mc
        float diffX, diffY;
        Pacman pacman;
        Log.i("Fling", "detected");

        diffX = moveEvent.getX() - downEvent.getX();
        diffY = moveEvent.getY() - downEvent.getY();
        pacman=this.gameManager.getPacman();

        if (Math.abs(diffX) > Math.abs(diffY)) {
            //right or left swipe
            if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY) {
                if (diffX > 0) {
                    //right
                    pacman.setNextDirection('r');
                } else {
                    //left
                    pacman.setNextDirection('l');
                }
            }

        } else {
            //up or down swipe
            if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY) {
                if (diffY > 0) {
                    //down
                    pacman.setNextDirection('d');
                } else {
                    //up
                    pacman.setNextDirection('u');
                }
            }
        }
        return true;
    }

}

最后是 GameManager 代码

package Game;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Build;
import android.util.Log;
import android.widget.TextView;

import androidx.annotation.RequiresApi;

import com.example.pacman.R;

import org.jetbrains.annotations.NotNull;

import Game.Behavior.ChaseBehavior.*;
import Game.Character_package.Ghost;
import Game.Character_package.Pacman;
import Game.GameCountDown.*;


public class GameManager {
    private static final int TOTAL_LEVELS=256;
    private GameMap gameMap;
    private int level,bonusResetTime,score;
    private CountDownScareGhosts scareCountDown;
    private Pacman pacman;
    private Ghost[] ghosts;
    private boolean fruitHasBeenInTheLevel;

    public GameManager(GameView gameView){
        this.fruitHasBeenInTheLevel=false;
        this.score=0;
        this.gameMap=new GameMap();
        this.gameMap.loadMap1();
        this.level=1;
        this.ghosts=new Ghost[4];
        this.bonusResetTime = 5000;
        this.scareCountDown=null;
    }

    public void addScore(int s){
        this.score+=s;
    }

    public int getScore() {
        return this.score;
    }
    public int getLevel() {
        return this.level;
    }
    public GameMap getGameMap() {
        return this.gameMap;
    }
    public Ghost[] getGhosts(){
        return this.ghosts;
    }
    public Ghost getGhost(int i) {
        return this.ghosts[i];
    }
    public Pacman getPacman(){
        return this.pacman;
    }
    public void setPacman(Pacman pacman){
        this.pacman=pacman;
    }


    public void eatPallet(int posXMap, int posYMap){
        this.score+=10;
        //Log.i("Score", Double.toString(this.score).substring(0,Double.toString(this.score).indexOf('.')));
        this.gameMap.getMap()[posYMap][posXMap]=0;
    }

    public void eatBonus(int posXMap,int posYMap){
        this.score+=500;
        //Log.i("Score", Double.toString(this.score).substring(0,Double.toString(this.score).indexOf('.')));
        this.gameMap.getMap()[posYMap][posXMap]=0;
    }

    public void eatSuperPallet(int posXMap,int posYMap){
        this.score+=50;
        Log.i("Score", Double.toString(this.score).substring(0,Double.toString(this.score).indexOf('.')));
        this.gameMap.getMap()[posYMap][posXMap]=0;

        //Si hay un timer andando lo cancelo y ejecuto otro
        if (this.scareCountDown != null){
            this.scareCountDown.cancel();
        }
        this.scareCountDown = new CountDownScareGhosts(this.ghosts,this.gameMap.getMap());
        this.scareCountDown.start();
    }

    public void tryCreateBonus(){
        //only if pacman has eaten 20 pallets we should allow the fruit appear
        if(!this.fruitHasBeenInTheLevel && this.gameMap.getEatenPallets()>=20){
            //to not allow the fruit be again in the level
            this.fruitHasBeenInTheLevel=true;
            new CountdownBonusThread(this.gameMap,this.bonusResetTime).start();
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.N)
    public void moveGhosts(Canvas canvas,int blocksize) {
        for (int i = 0; i < ghosts.length; i++) {
            ghosts[i].move(this.gameMap.getMap(),this.pacman);
            ghosts[i].draw(canvas);
        }
    }

    public synchronized void initGhosts(@NotNull GameView gv) {
        int[][]spawnPositions,cornersPositions, notUpDownPositions,defaultTargets;
        int movementeFluency;

        defaultTargets=this.gameMap.getDefaultGhostTarget();
        notUpDownPositions=this.gameMap.getNotUpDownDecisionPositions();
        spawnPositions=this.gameMap.getGhostsSpawnPositions();
        cornersPositions=this.gameMap.getGhostsScatterTarget();
        movementeFluency=gv.getMovementFluencyLevel();
        //start position
        // 5 blinky spawn [13, 11]
        // 6 pinky spawn [15,11]
        // 7 inky spawn [13,16]
        // 8 clyde spawn [15,16]
        this.ghosts=new Ghost[4];
        ghosts[0] = new Ghost("blinky",gv,spawnPositions[0], cornersPositions[0] ,new BehaviorChaseAgressive(notUpDownPositions,movementeFluency,defaultTargets[0]),movementeFluency,notUpDownPositions,'l',defaultTargets[0]);
        ghosts[1] = new Ghost("pinky",gv,spawnPositions[1],cornersPositions[1],new BehaviorChaseAmbush(notUpDownPositions,movementeFluency,defaultTargets[1]),movementeFluency,notUpDownPositions,'r',defaultTargets[1]);
        ghosts[2] = new Ghost("inky",gv,spawnPositions[2],cornersPositions[2],new BehaviorChasePatrol(notUpDownPositions,this.ghosts[0],movementeFluency,defaultTargets[0]),movementeFluency,notUpDownPositions,'l',defaultTargets[0]);
        ghosts[3] = new Ghost("clyde",gv,spawnPositions[3],cornersPositions[3],new BehaviorChaseRandom(notUpDownPositions,cornersPositions[3],movementeFluency,defaultTargets[1]),movementeFluency,notUpDownPositions,'r',defaultTargets[1]);

        try{
            Thread.sleep(200);
        }catch(Exception e){}

        for (int i=0;i<ghosts.length;i++){
            ghosts[i].onLevelStart(1);
        }

    }

    public boolean checkWinLevel() {
        //player win the level if he has eaten all the pallet
        return this.gameMap.countPallets()==0;
    }

    public void onResume(){
        for (int i=0 ; i<this.ghosts.length;i++){
            this.ghosts[i].cancelBehavoirThread();
        }
        if(this.scareCountDown!=null && !this.scareCountDown.hasEnded()){
            this.scareCountDown.start();
        }
    }

    public void onPause(){
        for (int i=0 ; i<this.ghosts.length;i++){
            this.ghosts[i].cancelBehavoirThread();
        }
        if(this.scareCountDown!=null && !this.scareCountDown.hasEnded()){
            this.scareCountDown=this.scareCountDown.onPause();
        }
    }

    public void cancelThreads(){
        for (int i=0 ; i<this.ghosts.length;i++){
            this.ghosts[i].cancelBehavoirThread();
        }
        if(this.scareCountDown!=null){
            this.scareCountDown.cancel();
        }
    }

}

目前我所知道的是我无法将 scoreTv 发送到 GameView,因为 GameView 由于不在同一个线程中而无法更改它。每当我之前告诉您的任何方法被调用时,我都需要 PlayActivity 实例做出反应。

好吧,我找到了一个解决方案,我不喜欢,艰难。 接下来我做了什么:

  1. 我在 AppCompatActivity 中创建了一个 STATIC SEMAPHORE
  2. 我也将它作为静态信号传递给 class GameManager,并且我已将此 class 中的分数更改为静态
  3. 最后,我在 PlayActivity 中 运行 一个线程,使用方法 运行OnUiThread 来更新 scoreTv,这个线程将获得一个许可,这个线程将在 SCORE 更新时被释放在 GameManager 中避免主动等待

您可能会问我为什么要标注 STATIC。如果我不这样做,我不知道为什么 GameManager 的引用会丢失。如果有人知道答案请post吧。 这里是 git 仓库的 link,这里是 classes

的代码
public class PlayActivity extends AppCompatActivity {
    private TextView playerNickname;
    private TextView scoreTv;
    private TextView maxScore;
    private SurfaceView gameSurfaceView;
    private GameView gameView;
    private GameManager gameManager;
    private static Semaphore CHANGE_SCORE_MUTEX=new Semaphore(0,true);
    private static Semaphore CHANGE_DIRECTION_MUTEX=new Semaphore(0,true);
    private Thread changeScoreThread, changeDirectionThread;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //Modified code
        setContentView(R.layout.activity_game);
        //we get text view that we will use
        playerNickname=(TextView) this.findViewById(R.id.tv_player);
        scoreTv=(TextView) this.findViewById(R.id.tv_current_score);
        maxScore=(TextView) this.findViewById(R.id.tv_current_max_score);
        gameSurfaceView= (GameView) this.findViewById(R.id.game_view);

        //set text view initial values
    
  playerNickname.setText(getIntent().getExtras().getString("playerNickname"));
       scoreTv.setText("0");

       maxScore.setText("To modify");

       this.gameView=new GameView(gameSurfaceView.getContext());
       this.gameManager=this.gameView.getGameManager();
       this.gameView.setSemaphores(CHANGE_SCORE_MUTEX,CHANGE_DIRECTION_MUTEX);
       this.gameSurfaceView.getHolder().addCallback(this.gameView);
    }    

    protected void onResume(){
        super.onResume();
        this.gameView.resume();
        this.initChangerThreads();
    }

    public void updateScoreTv(int score){
        this.scoreTv.setText(""+score);
    }

    protected void onPause(){
        super.onPause();
        this.gameView.pause();
        //in order to stop the threads
        CHANGE_SCORE_MUTEX.release();
        CHANGE_DIRECTION_MUTEX.release();
    }

    public void onLose(double score){
        //We try to save the score, if there is a previous register we write only if this score
        //is better that the one before
        DBManager manager;
        long raw;
        Score scoreToSave;
        manager=new DBManager(this);

        scoreToSave=new Score(this.playerNickname.toString(), score);
        if(manager.saveScore(scoreToSave)==-1){
            //if i couldn't save the score
            if(manager.updateScore(scoreToSave)!=-1){
                //if my new score is better than the one previous
            }else{
                //if my new score is worse or equal than the one previous
            }
        }
    }   

    private void initChangerThreads() {
        this.changeScoreThread = new Thread(new Runnable() {
        public void run() {
            while (gameView.isDrawing()) {
                //Log.i("Score ",""+gameManager.getScore());
                try {
                    CHANGE_SCORE_MUTEX.acquire();
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            updateScoreTv(gameView.getGameManager().getScore());
                        }
                    });
                }catch (Exception e){}
             }
        }
        });
        this.changeScoreThread.start();
    }
}

GameView:我刚刚添加了这个方法

public void setSemaphores(Semaphore changeScoreSemaphore, Semaphore changeDirectionSemaphore){
    this.gameManager.setChangeScoreSemaphore(changeScoreSemaphore);
    this.gameManager.getPacman().setChangeDirectionSemaphore(changeDirectionSemaphore);
    Log.i("Semaphore", "setted");
}

游戏管理器

public class GameManager {
    private static final int TOTAL_LEVELS=256;
    private static int SCORE=0;
    private GameMap gameMap;
    private int level,bonusResetTime;//,score;
    private CountDownScareGhosts scareCountDown;
    private Pacman pacman;
    private Ghost[] ghosts;
    private boolean fruitHasBeenInTheLevel;
    private static Semaphore CHANGE_SCORE_MUTEX;

    public GameManager(){
        this.fruitHasBeenInTheLevel=false;
        //this.score=0;
        this.gameMap=new GameMap();
        this.gameMap.loadMap1();
        this.level=1;
        this.ghosts=new Ghost[4];
        this.bonusResetTime = 5000;
        this.scareCountDown=null;
    }

    public void setChangeScoreSemaphore(Semaphore changeScoreSemaphore) {
        CHANGE_SCORE_MUTEX = changeScoreSemaphore;
        //if(this.changeScoreSemaphore==null){
        //    Log.i("Change Score Semaphore","I'm null");
        //}else{
        //    Log.i("Change Score Semaphore","I'm not null");
        //}
    }

    public void addScore(int s){
        //this.score+=s;
        SCORE+=s;
        CHANGE_SCORE_MUTEX.release();
        /*if(this.changeScoreSemaphore==null){
            Log.i("Change Score Semaphore","I'm null");
        }else{
            Log.i("Change Score Semaphore","I'm not null");
        }*/
        //this.changeScoreSemaphore.release();
    }

    public int getScore() {
        return SCORE;
        //return this.score;
    }

    public int getLevel() {
        return this.level;
    }
    public GameMap getGameMap() {
        return this.gameMap;
    }
    public Ghost[] getGhosts(){
        return this.ghosts;
    }
    public Pacman getPacman(){
        return this.pacman;
    }
    public void setPacman(Pacman pacman){
        this.pacman=pacman;
    }


    public void eatPallet(int posXMap, int posYMap){
        SCORE+=10;
        CHANGE_SCORE_MUTEX.release();
        //this.score+=10;
        Log.i("Score GM", ""+SCORE);
        //Log.i("Score GM", ""+this.score);
        this.gameMap.getMap()[posYMap][posXMap]=0;
        //this.changeScoreSemaphore.release();
        //if(this.changeScoreSemaphore==null){
        //    Log.i("Change Score Semaphore","I'm null");
        //}else{
        //    Log.i("Change Score Semaphore","I'm not null");
        //}
    }

    public void eatBonus(int posXMap,int posYMap){
        SCORE+=500;
        CHANGE_SCORE_MUTEX.release();
        //this.score+=500;
        //Log.i("Score", Double.toString(this.score).substring(0,Double.toString(this.score).indexOf('.')));
        this.gameMap.getMap()[posYMap][posXMap]=0;
        //this.changeScoreSemaphore.release();
    }

    public void eatSuperPallet(int posXMap,int posYMap){
        SCORE+=50;
        CHANGE_SCORE_MUTEX.release();
        //this.score+=50;
        this.gameMap.getMap()[posYMap][posXMap]=0;

        //Si hay un timer andando lo cancelo y ejecuto otro
        if (this.scareCountDown != null){
            this.scareCountDown.cancel();
        }
        this.scareCountDown = new CountDownScareGhosts(this.ghosts,this.gameMap.getMap());
        this.scareCountDown.start();
        //this.changeScoreSemaphore.release();
    }

    public void tryCreateBonus(){
        //only if pacman has eaten 20 pallets we should allow the fruit appear
        if(!this.fruitHasBeenInTheLevel && this.gameMap.getEatenPallets()>=20){
            //to not allow the fruit be again in the level
            this.fruitHasBeenInTheLevel=true;
            new CountdownBonusThread(this.gameMap,this.bonusResetTime).start();
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.N)
    public void moveGhosts(Canvas canvas,int blocksize) {
        for (int i = 0; i < ghosts.length; i++) {
            ghosts[i].move(this.gameMap.getMap(),this.pacman);
            ghosts[i].draw(canvas);
        }
    }

    public synchronized void initGhosts(int blocksize, Resources res, String packageName,int movementFluency) {
        int[][]spawnPositions,cornersPositions, notUpDownPositions,defaultTargets;

        defaultTargets=this.gameMap.getDefaultGhostTarget();
        notUpDownPositions=this.gameMap.getNotUpDownDecisionPositions();
        spawnPositions=this.gameMap.getGhostsSpawnPositions();
        cornersPositions=this.gameMap.getGhostsScatterTarget();
        //start position
        // 5 blinky spawn [13, 11]
        // 6 pinky spawn [15,11]
        // 7 inky spawn [13,16]
        // 8 clyde spawn [15,16]
        this.ghosts=new Ghost[4];
        ghosts[0] = new Ghost("blinky",spawnPositions[0], cornersPositions[0] ,new BehaviorChaseAgressive(notUpDownPositions,movementFluency,defaultTargets[0]),movementFluency,notUpDownPositions,'l',defaultTargets[0],blocksize,res,packageName);
        ghosts[1] = new Ghost("pinky",spawnPositions[1],cornersPositions[1],new BehaviorChaseAmbush(notUpDownPositions,movementFluency,defaultTargets[1]),movementFluency,notUpDownPositions,'r',defaultTargets[1],blocksize,res,packageName);
        ghosts[2] = new Ghost("inky",spawnPositions[2],cornersPositions[2],new BehaviorChasePatrol(notUpDownPositions,this.ghosts[0],movementFluency,defaultTargets[0]),movementFluency,notUpDownPositions,'l',defaultTargets[0],blocksize,res,packageName);
        ghosts[3] = new Ghost("clyde",spawnPositions[3],cornersPositions[3],new BehaviorChaseRandom(notUpDownPositions,cornersPositions[3],movementFluency,defaultTargets[1]),movementFluency,notUpDownPositions,'r',defaultTargets[1],blocksize,res,packageName);

        try{
            Thread.sleep(200);
        }catch(Exception e){}

        for (int i=0;i<ghosts.length;i++){
            ghosts[i].onLevelStart(1);
        }

    }

    public boolean checkWinLevel() {
        //player win the level if he has eaten all the pallet
        return this.gameMap.countPallets()==0;
    }

    public void onResume(){
        for (int i=0 ; i<this.ghosts.length;i++){
            this.ghosts[i].cancelBehavoirThread();
        }
        if(this.scareCountDown!=null && !this.scareCountDown.hasEnded()){
            this.scareCountDown.start();
        }
    }

    public void onPause(){
        for (int i=0 ; i<this.ghosts.length;i++){
            this.ghosts[i].cancelBehavoirThread();
        }
        if(this.scareCountDown!=null && !this.scareCountDown.hasEnded()){
            this.scareCountDown=this.scareCountDown.onPause();
        }
    }

    public void cancelThreads(){
        for (int i=0 ; i<this.ghosts.length;i++){
            this.ghosts[i].cancelBehavoirThread();
        }
        if(this.scareCountDown!=null){
            this.scareCountDown.cancel();
        }
    }

}

我在任何地方都找不到这样的解决方案,如果您目前遇到这样的问题,我希望我能为您节省大量研究时间:D