Почему игра заканчивается на экране? - PullRequest
2 голосов
/ 18 мая 2019

Я сделал игру в Android Studio с целью избежать падения предметов.В общем, приложение работает хорошо, но по какой-то причине, когда я захожу в игру поверх экрана и нажимаю edittext, чтобы добавить рекорд, игровой опыт сильно запинается и запаздывает (с помощью клавиатуры и клавиш).

Я уже звонил finish() (что можно увидеть в "FishView") в моей основной деятельности, поэтому я не понимаю, как это может быть настолько медленным в игре на экране, как это не должно бытьнадо беспокоиться обо всем, кроме игры за экраном, как только она появится, и игра за экраном очень проста.

Мне трудно найти источник проблемы, поэтому я и прошу помощи здесь.

Вот некоторый код, который, я надеюсь, будет достаточным для обнаружения проблемы:

MainActivity (имеет дело с animation, повышением уровня, объектами порождения и взаимодействием между объектами и правилами)

public class MainActivity extends AppCompatActivity implements GarbageListener {

    //global variable of FishView
    private FishView gameView;

    //handle animation task
    private final Handler handler = new Handler();

    //global variable of screen
    private RelativeLayout screen;

    //time before level update
    private int levelChangeTime = 3; //initialize small garbage in X seconds
    private int spawnBossGarbage = 25; //initialize big garbage in X seconds
    private int spawnHeart = 40; //initialize heart in X seconds

    //pause variables
    private Button pauseButton;
    private boolean pauseFlag = false;

    //left and right button
    private Button leftButton;
    private Button rightButton;

    //List of small garbage on screen
    private final List<SmallGarbage> smallGarbages = new ArrayList<>();
    //List of big garbage on screen
    private List<BigGarbage> bigGarbages = new ArrayList<>();
    //List of heart on screen
    private List<LifePoint> lifePoints = new ArrayList<>();

    //create timer for animation and level increase
    private Timer mainTimer;

    //create timer fro holding left or right
    private Timer movingLeft;
    private Timer movingRight;
    private final boolean buttonIsPressed = false; //so players can't hold both buttons down
    private final int holdMovementPeriod = 9;

    //keep track of song
    public static Intent themeSong;
    //keep track of how far we are in the song, serviceStop() deletes everything in service ThemeSong so variable must be saved elsewhere
    public static int lengthOfSong = 0;
    public static boolean backButtonPressed = false; //check if backButton was pressed in service ThemeSong oonDestroy() since that's the last thing that is run


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

        themeSong=new Intent(this, ThemeSong.class);
        startService(themeSong); //OR stopService(svc);

        leftButton = findViewById(R.id.leftArrow);
        rightButton = findViewById(R.id.rightArrow);

        screen = findViewById(R.id.gameScreen);
        gameView = new FishView(this);
        screen.addView(gameView);

        pauseButton = findViewById(R.id.pauseButton);

        mainTimer = new Timer();
        createNewAnimationTask();
        createNewLevelTask();

        //create listeners fo holding left or right button
        findViewById(R.id.leftArrow).setOnTouchListener(new View.OnTouchListener() {
            @SuppressLint("ClickableViewAccessibility")
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    holdLeft();
                    rightButton.setEnabled(false);}
                if (event.getAction() == MotionEvent.ACTION_UP) {
                    rightButton.setEnabled(true);
                    if (movingLeft!=null){
                    movingLeft.cancel();
                    }}
                return false;}
        });

        findViewById(R.id.rightArrow).setOnTouchListener(new View.OnTouchListener() {
            @SuppressLint("ClickableViewAccessibility")
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    holdRight();
                    leftButton.setEnabled(false);}
                if (event.getAction() == MotionEvent.ACTION_UP) {
                    leftButton.setEnabled(true);
                    if (movingRight!=null){
                    movingRight.cancel();}}
                return false;}
        });
    }

    public void moveLeft(@SuppressWarnings("unused") View v){
        if (buttonIsPressed){return;}
        gameView.setLeftPressed(true);
        gameView.leftFishAnimation();//before running the animations we first set which fish animations to run (left or right)
        gameView.invalidate();
    }

    public void moveRight(@SuppressWarnings("unused") View view) {
        if (buttonIsPressed){return;}
        gameView.setRightPressed(true);
        gameView.rightFishAnimation();
        gameView.invalidate();
    }

    public void pauseGame(View v){
        String resume = "Resume";
        String pause = "Pause";
        if (!pauseFlag){
            stopService(themeSong); //turn of music
            pauseFlag = true;
            pauseButton.setText(resume);
            pauseButton.setBackgroundResource(R.drawable.roundbuttonred);

            //disable animation and level tasks
            mainTimer.cancel();
            //disable all falling garbage on screen
            for (SmallGarbage smallGarbage : smallGarbages) {smallGarbage.disableGarbageTimer();}
            for (BigGarbage bigGarbage : bigGarbages) {bigGarbage.disableGarbageTimer();}
            for (LifePoint lifePoint : lifePoints) {lifePoint.disableGarbageTimer();}
            //disable buttons
            leftButton.setEnabled(false);
            rightButton.setEnabled(false);

        }
        else{
            startService(themeSong); //start music
            pauseFlag=false;
            pauseButton.setText(pause);
            leftButton.setEnabled(true);
            rightButton.setEnabled(true);
            pauseButton.setBackgroundResource(R.drawable.roundbuttonblue);
            //resume falling garbage
            for (SmallGarbage smallGarbage : smallGarbages) {smallGarbage.startFallingGarbage();}
            for (BigGarbage bigGarbage : bigGarbages) {bigGarbage.startFallingGarbage();}
            for (LifePoint lifePoint : lifePoints) {lifePoint.startFallingGarbage();}
            //resume animation and level increase
            mainTimer = new Timer();
            createNewAnimationTask();
            createNewLevelTask();
        }

    }

    private void createNewAnimationTask(){
        TimerTask newAnimationTask = new TimerTask() {
            @Override
            public void run() {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        //here we set the animation
                        int selectedFish = gameView.getSelectedFish();
                        selectedFish ++;
                        if (selectedFish==2){
                            selectedFish = 0;}

                        gameView.setSelectedFish(selectedFish);

                        //update screen
                        gameView.invalidate();
                    }
                });
            }
        };
        long animationPeriod = 600;
        mainTimer.scheduleAtFixedRate(newAnimationTask, 0, animationPeriod);
    }

    private void createNewLevelTask(){
        TimerTask levelCountDown = new TimerTask(){
            @Override
            public void run() {
                levelChangeTime--;
                spawnBossGarbage--;
                spawnHeart--;
                if (levelChangeTime==0 || spawnBossGarbage == 0 || spawnHeart == 0){
                    //move task that updates the UI onto the main thread
                    runOnUiThread(new Runnable() { //this tells the program to run this on the UI(aka main) thread, we could also call on new Thread if wanted to start new thread
                        @Override
                        public void run() {
                            if (levelChangeTime==0){generateNewGarbage("smallGarbage");}
                            if (spawnBossGarbage==0){generateNewGarbage("bigGarbage");}
                            if (spawnHeart==0){generateNewGarbage("lifePoint");}// when this is added we can't lose life?
                        }
                    });
                }
            }
        };
        mainTimer.scheduleAtFixedRate(levelCountDown,0,1000);
    }


    private void holdLeft(){
        movingLeft = new Timer();
        final View v = new View(this); //create view so moveLeft() can called
        TimerTask holdLeftTask = new TimerTask(){
            @Override
            public void run() {
                handler.post(new Runnable() {
            @Override
            public void run() {
                moveLeft(v);
            }
                });
        }};
        movingLeft.scheduleAtFixedRate(holdLeftTask,0,holdMovementPeriod);
    }

    private void holdRight(){
        movingRight = new Timer();
        final View v = new View(this);
        TimerTask holdRightTask = new TimerTask(){
            @Override
            public void run() {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        moveRight(v);

                    }
                });
            }};
        movingRight.scheduleAtFixedRate(holdRightTask,0,holdMovementPeriod);
    }

    private void generateNewGarbage(String garbage){
        switch (garbage){
            case "bigGarbage":
                spawnBossGarbage = 40; //time to next spawn
                BigGarbage newBigGarbage = new BigGarbage(MainActivity.this);
                newBigGarbage.setListener(MainActivity.this);
                bigGarbages.add(newBigGarbage);
                screen.addView(newBigGarbage);
                break;
            case "smallGarbage":
                levelChangeTime = new Random().nextInt(20)+3; //set seconds between 3 and 20 at random
                //this create SmallGarbage and initialize its task
                SmallGarbage newGarbage = new SmallGarbage(MainActivity.this);
                newGarbage.setListener(MainActivity.this); // set listener for garbage
                smallGarbages.add(newGarbage);
                screen.addView(newGarbage);
                break;
            case "lifePoint":
                spawnHeart=30; //time to next spawn
                //this create SmallGarbage and initialize its task
                LifePoint newLifePoint = new LifePoint(MainActivity.this);
                newLifePoint.setListener(MainActivity.this); // set listener for garbage
                lifePoints.add(newLifePoint);
                screen.addView(newLifePoint);
                break;
        }
    }

    //here starts the GarbageListener
    @Override
    public void handleAvoidedGarbage(String avoidedGarbage) {
        gameView.avoidedGarbage(avoidedGarbage);
    }

    @Override
    public boolean handleHitPlayer(int x, int y, String garbageType) {
        return gameView.hitWasteChecker(x,y, garbageType);
    }

    @Override
    public void handleLoseLife() {
        gameView.loseLife();
    }


    //empty lives on screen, once they have landed or hit player
    @Override
    public void emptyLifePointList(){
        lifePoints.clear();
        lifePoints = new ArrayList<>();
    }

    //empty big garbage on screen, once they have landed or hit player
    @Override
    public void emptyBigGarbageList(){
        bigGarbages.clear();
        bigGarbages = new ArrayList<>();
    }

    //saving and setting length of played song
    public static int getLengthOfSong() {
        return lengthOfSong;
    }

    public static void setLengthOfSong(int lengthOfSong) {
        MainActivity.lengthOfSong = lengthOfSong;
    }

    //onStop runs AFTER onBackPressed(), so lengthOfSong must be reset there
    @Override
    public void onBackPressed() {
        super.onBackPressed();
        backButtonPressed = true;
    }

    public static boolean isBackButtonPressed() {
        return backButtonPressed;
    }

    public static void setBackButtonPressed(boolean backButtonPressed) {
        MainActivity.backButtonPressed = backButtonPressed;
    }

    //this runs whenever the app is closed
    @Override
    protected void onStop(){
        super.onStop();
        //stop music
        stopService(themeSong);
        setLengthOfSong(0);

        //pause game, this will also reset sound upon start
        final View v = new View(this);
        pauseFlag = false;
        pauseGame(v);
    }

}

FishView (имеет дело с созданием игрока, правилами и ОБРАЩЕНИЕМ с началом игры на экране)

public class FishView extends View {

    private final Bitmap[] fish = new Bitmap[3];
    private final Bitmap gameBackground;
    private final Bitmap[] lifePoints = new Bitmap[2];

    private int selectedFish;

    private final Paint scorePaint = new Paint();
    private int score, fishLives;

    private static final int fishY = 1200;
    private int fishX = 400;
    private int speedX = 0;

    private boolean leftPressed = false;
    private boolean rightPressed = false;

    public FishView(Context context) {
        super(context);

        //set background
        gameBackground = BitmapFactory.decodeResource(getResources(),R.drawable.underwater);

        //set default/start fish animations
        leftFishAnimation();

        //set selected fish animation to default start on 0
        selectedFish = 0;

        //set life points
        lifePoints[1] = BitmapFactory.decodeResource(getResources(),R.drawable.lifepoint);
        lifePoints[0] = BitmapFactory.decodeResource(getResources(),R.drawable.deadlife);

        //set score
        scorePaint.setColor(Color.WHITE);
        scorePaint.setTextSize(80);
       // scorePaint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD)); //??
        scorePaint.setAntiAlias(true); //(graphic improvement) this removes the staircase effect aka make smoother
        scorePaint.setTypeface(Typeface.SERIF);
        score = 0;

        //set fish lives
        fishLives = 3;

    }

    //in a View, the onDraw method is called whenever:
    //the view is initially drawn or whenever invalidate() is called on the view
    //in our case we call on the constructor which initially the View
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //should maybe be canvas.getWidth() here
        int canvasWidth=getWidth();

        //set game boundaries
        int minFishX = 0; //should not be able to go of screen (to the left)
        int maxFishX = canvasWidth-fish[0].getWidth(); //furthers you can go to the right (to the right)

        //check boundaries
        if (fishX < minFishX) {
            fishX = minFishX;
        }
        if (fishX > maxFishX) {
            fishX = maxFishX;
        }

        //set position dependent on speed
        fishX += speedX;

        //draw background
        canvas.drawBitmap(gameBackground, 0, 0, null);

        //this draws the bitmap we decoded from the image
        if (leftPressed){
            speedX -= 15;
        }
        else if (rightPressed){
            speedX += 15;
        }

        if (speedX != 0){
        while (speedX != 0){
            if (leftPressed){
                fishX -= 1;
                speedX += 1;
                canvas.drawBitmap(fish[selectedFish],fishX,fishY,null);
                invalidate();

            }
            else if (rightPressed){
                fishX += 1;
                speedX -= 1;
                canvas.drawBitmap(fish[selectedFish],fishX,fishY,null);
                invalidate();
            }
        }}
        else{ //if nothing happens when we stay here
            canvas.drawBitmap(fish[selectedFish],fishX,fishY, null);
        }

        leftPressed=false;
        rightPressed=false;

        //draw score
        canvas.drawText("Score: " + score, 20 , 90, scorePaint);

        //draw life points and life point we have lost
        for (int lives = 0; lives < 3 ; lives++) {
            int lifeX = 650 + 140*lives;
            int lifeY = 10;

            if (lives < fishLives){
                canvas.drawBitmap(lifePoints[1],lifeX,lifeY,null);
            }
            else{
                canvas.drawBitmap(lifePoints[0],lifeX,lifeY,null);
            }
        }
    }

    public boolean hitWasteChecker(int x, int y, String garbageType){
         switch (garbageType){
             //define hit boxes
             //first check is how far above, second how much underneath, third how much to the left, and fourth how much to the right
            case "smallGarbage":
                return  fishY <= y + 80  && fishY + fish[selectedFish].getHeight() >= y + 75 && fishX <= x + 75 && x + 20 <= (fishX + fish[selectedFish].getWidth());
            case "bigGarbage":
                return  fishY <= y + 170  && fishY + fish[selectedFish].getHeight() >= y + 75 && fishX <= x + 180 && x + 20 <= (fishX + fish[selectedFish].getWidth());
             case "lifePoint":
                 if (fishY <= y + 25  && fishY + fish[selectedFish].getHeight() >= y + 60 && fishX <= x + 110 && x + 35 <= (fishX + fish[selectedFish].getWidth())){
                     if (fishLives<3){fishLives++;
                     return true;} //if not full life gain a life
                     if (fishLives==3){score+=40; //if already full life then gain 40 points
                     return true;}}
                 return false;
            default:
                return false;
        }}


    public void loseLife(){
        fishLives--;
        if (fishLives<=0){
            //stop theme song from playing
            getContext().stopService(MainActivity.themeSong);

            //through these lines a new Activity can be created from a View
            Intent gameOverIntent = new Intent(getContext(), GameOverActivity.class);
            gameOverIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); //not possible to go back from game over screen
            gameOverIntent.putExtra("final score", score); // send data to game over activity
            getContext().startActivity(gameOverIntent);
            ((MainActivity) getContext()).overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
            ((MainActivity) getContext()).finish(); //TIMERS is till running

        }
    }

    public void leftFishAnimation(){
        fish[0] = BitmapFactory.decodeResource(getResources(),R.drawable.leftfish1);
        fish[1] = BitmapFactory.decodeResource(getResources(),R.drawable.leftfish2);

    }

    public void rightFishAnimation(){
        fish[0] = BitmapFactory.decodeResource(getResources(),R.drawable.rightfish1);
        fish[1] = BitmapFactory.decodeResource(getResources(),R.drawable.rightfish2);
    }

    public void setLeftPressed(boolean leftPressed) {
        this.leftPressed = leftPressed;
    }

    public void setRightPressed(boolean rightPressed) {
        this.rightPressed = rightPressed;
    }

    public int getSelectedFish() {
        return selectedFish;
    }

    public void setSelectedFish(int selectedFish) {
        this.selectedFish = selectedFish;
    }

    public void avoidedGarbage(String avoidedGarbage){
        switch (avoidedGarbage){
            case "smallGarbage":
                score += 10;
                break;
            case "bigGarbage":
                score += 25;
                break;
        }
    }

}

GameOver: (отображение игры поверх экрана)

public class GameOverActivity extends AppCompatActivity {
    //create instance of database
    private DatabaseHelper db;
    private EditText usernameInput;
    private int score;
    private MediaPlayer gameOverSound;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_game_over);
        score = Objects.requireNonNull(getIntent().getExtras()).getInt("final score");
        usernameInput = findViewById(R.id.addUsername);
        db = new DatabaseHelper(this);
        //easier way of doing it
        gameOverSound = MediaPlayer.create(this, R.raw.gameoversound);
        gameOverSound.setVolume(0.2f,0.2f);
        gameOverSound.start();

        String yourFinalScore = "Your final score: " + score;
        TextView finalScore = findViewById(R.id.finalScore);
        finalScore.setText(yourFinalScore);

    }

    public void restartGame(View v){
        Intent restartIntent = new Intent(this, MainActivity.class);
        restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); //so we can't go back to game over
        startActivity(restartIntent);
        overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); //transition between activities
        finish(); //end this activity, MainActivity is already ended so can't only call on finish here to go back
    }

    public void backToStartMenu(View view) {
        Intent startMenuIntent = new Intent(this, MenuActivity.class);
        startActivity(startMenuIntent);
        overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
        finish(); //end this activity
    }

    public void addHighscore(View view) {
        String writtenUsername = usernameInput.getText().toString();
        if (!writtenUsername.equals("") && score != 0){
            //insert writtenUsername and score into database
            boolean insertedData = db.insertData(writtenUsername, score);
            if (insertedData){
                Toast.makeText(this, "Highscore was added", Toast.LENGTH_SHORT).show();
                Intent startMenuIntent = new Intent(this, MenuActivity.class);
                startActivity(startMenuIntent);
                overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
                finish();}
            else{
                Toast.makeText(this, "Highscore couldn't be added", Toast.LENGTH_SHORT).show();
            }
        }
    }

    //this runs whenever the app is closed, mobile arrow is pressed or we switch activity
    @Override
    protected void onStop(){
        super.onStop();
        gameOverSound.stop();
        gameOverSound.release(); //solve error: if run twice the app will close because we cant release it twice
    }

    //if pressed mobile back button go back to start menu
    @Override
    public void onBackPressed() {
        super.onBackPressed();
        View v = new View(this);
        backToStartMenu(v);
    }
}

Что я нахожу странным в этой проблеме, так это то, что MainActivity с проигрывателем animation и всеми объектами падающего вида работает нормально.Однако что-то такое маленькое, как игра на экране, отстает.Это оставляет меня верить, что я как-то не прекращаю свою деятельность, как следовало бы, что приводит к тому, что основной поток не может ее обработать.В любом случае спасибо за ваше время!:)

Обновление: я проверил это, и кажется, что есть некоторая реальная проблема с переходом от MainActivity к GameOver.Используя справку Android -> find-action -> profiler, я смог увидеть, что при использовании MainActivity объем используемой памяти составлял около 110 МБ, и как только я перешел к игре через экран, она достигла 400 МБ.Но я все еще не могу определить, почему это происходит.

1 Ответ

0 голосов
/ 18 мая 2019

Я понял это. Проблема заключалась в четком фоновом изображении, которое я использовал на своем экране GameOver, по-видимому, это было слишком много для игры. Поэтому, если у вас возникла подобная проблема, попробуйте изменить изображения и фон на экране.

(я бы принял это как ответ, но мне пришлось бы ждать 2 дня, пока я смогу это сделать)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...