簡體   English   中英

在 Android 中保存和恢復游戲對象的狀態

[英]Save and recover state of game object in Android

編輯:主要問題,處理按下后退按鈕的保存狀態。 然后在 onCreate 上重新加載包。 不確定,如何正確地做到這一點。 沒有,到目前為止我嘗試過的工作正常。

我正在嘗試保存和恢復我在 Android 中制作的簡單游戲的狀態。 我正在利用以下事件: onSaveInstanceState()onBackPressed()onCreate()onPostCreate() 我正在使我的對象SerializableParcelable

但是,在按下后退按鈕之前或只是切換應用程序時,它不會成功記住或恢復狀態。 當然,我在這里做錯了什么。 這是一個行為錯誤,即沒有發生真正的錯誤。

骰子.java

public class Dice implements Parcelable {
    private int value;
    private int currentImage;
    private boolean marked = false; 
    private boolean enabled = true;
    private final Random random = new Random();

    // Mapping of drawable resources:
    private final int[] defaultDiceImages = {0,
            R.drawable.white1, R.drawable.white2,
            R.drawable.white3, R.drawable.white4,
            R.drawable.white5, R.drawable.white6
    };

    private final int[] selectedDiceImages = { 0,
            R.drawable.grey1, R.drawable.grey2, R.drawable.grey3,
            R.drawable.grey4, R.drawable.grey5, R.drawable.grey6
    };

    private final int[] redDiceImages = { 0,
            R.drawable.red1, R.drawable.red2, R.drawable.red3,
            R.drawable.red4, R.drawable.red5, R.drawable.red6
    };

    // Constructor
    Dice() { }

    Dice(int value){
        this.value = value;
        this.currentImage = defaultDiceImages[this.value];
    }

    protected Dice(Parcel in) {
        value = in.readInt();
     /*   currentImage = in.readInt();
        marked = in.readByte() != 0;
        enabled = in.readByte() != 0;
        defaultDiceImages = in.createIntArray();
        selectedDiceImages = in.createIntArray();
        redDiceImages = in.createIntArray(); */
    }

    public static final Creator<Dice> CREATOR = new Creator<Dice>() {
        @Override
        public Dice createFromParcel(Parcel in) { return new Dice(in); }

        @Override
        public Dice[] newArray(int size) { return new Dice[size]; }
    };

    public boolean IsMarked() { return marked; }

    public int GetValue() { return value; }

    public void Toss() {
        if(enabled) {
            this.value = random.nextInt(6) + 1;
            this.currentImage = defaultDiceImages[this.value];
        }
    }

    public int GetCurrentImage(){ return currentImage; }


    public void ToggleMarked() {
        marked = !marked;
        currentImage = (this.marked) ? selectedDiceImages[this.value] : defaultDiceImages[this.value];
    }

    public void ToggleEnabled() { enabled = !enabled; }

    @NonNull
    @Override
    public String toString() {
        return "Dice{" +
                "   value=" + this.value +
                ", currentImage=" + this.currentImage +
                ", marked=" + this.marked +
                ", enabled=" + this.enabled +
                ", random=" + this.random +
                '}';
    }

    @Override
    public int describeContents() { return 0; }

    @Override
    public void writeToParcel(Parcel parcel, int i)
    {
        parcel.writeInt(this.value);
    }
} 

分數.java

public class Score implements Serializable {

    private int score = 0;
    private String choice;
    private static final int SCORE_LOW = 3;

    public Score() {}

    public Score(int score, String choice) {
        this.score = score;
        this.choice = choice;
    }

    public int getScore(){
        return this.score;
    }
    public void setScore(int score){
        this.score = score;
    }

    public String getChoice() {
        return choice;
    }

    public void setChoice(String choice) {
        choice = choice;
    }
}

GameRound.java

import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.Gravity;
import android.widget.Toast;

import java.io.Serializable;
import java.util.ArrayList;

public class GameRound implements Parcelable, Serializable {

    // Constants
    private final int NUMBER_OF_DICES = 6;
    private final int MAX_ALLOWED_THROWS = 3;

    private ArrayList<Dice> dices;

    public int totalDices;
    private int throwsLeft;
    private Score roundScore;
    
    public GameRound() {
        this.dices = GenerateNewDices();
        this.roundScore = new Score();
        this.throwsLeft = MAX_ALLOWED_THROWS;
    }

    protected GameRound(Parcel in) {
        this.totalDices = in.readInt();
        this.throwsLeft = in.readInt();
        this.dices = (ArrayList<Dice>)in.readSerializable();
        this.roundScore = (Score)in.readSerializable();
    }

    public ArrayList<Dice> GetDices() { return dices; }

    public ArrayList<Dice> GenerateNewDices() {
        ArrayList<Dice> tmp = new ArrayList<>(NUMBER_OF_DICES);
        if(totalDices == 0)
            totalDices = NUMBER_OF_DICES;

        for(int i = 0; i<totalDices; i++){
            tmp.add(new Dice(i));
        }
        return tmp;
    }

    public Score GetScore() { return roundScore; }

    public int GetThrowsLeftCount() { return throwsLeft; }

    public void SetRoundScore(Score score) {
        this.roundScore = score;
    }

    public void TossDices(Context context){
        if(ValidateAttempt(context)){
            int selectedCount = (int)this.dices.stream().filter(Dice::IsMarked).count();
            if(selectedCount > 0) {
                for (Dice d : this.dices) {
                    if(d.IsMarked()) {
                        d.Toss();
                        d.ToggleMarked(); // Reset
                    }
                }
            } else {
                for (Dice d : this.dices) {
                    d.Toss();
                }
            }
        }
    }

    private boolean ValidateAttempt(Context context) {
        if(throwsLeft > 0){
            --throwsLeft;
            return true;
        } else {
            Toast toast = Toast.makeText(context, "", Toast.LENGTH_SHORT);
            toast.setGravity(Gravity.TOP, 0, 200);
            toast.show();
            return false;
        }
    }

    public boolean CanPlay(){
        return throwsLeft != 0;
    }

    public void Reset() {
        for(Dice d: this.dices){
            d.Toss();
        }
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(totalDices);
        dest.writeInt(throwsLeft);
        dest.writeSerializable(dices);
        dest.writeSerializable(roundScore);
    }

    @Override
    public int describeContents() { return 0; }

    public static final Creator<GameRound> CREATOR = new Creator<GameRound>() {
        @Override
        public GameRound createFromParcel(Parcel in) { return new GameRound(in); }

        @Override
        public GameRound[] newArray(int size) { return new GameRound[size]; }
    };
}

30ThrowsGame.java:

import android.os.Parcel;
import android.os.Parcelable;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;

import androidx.core.util.Pair;

public class ThirtyThrowsGame implements Parcelable, Serializable {
    public enum ScoreChoice {
        LOW(3),
        FOUR(4),
        FIVE(5),
        SIX(6),
        SEVEN(7),
        EIGHT(8),
        NINE(9),
        TEN(10),
        ELEVEN(11),
        TWELVE(12),
        ;

        private final int value;
        ScoreChoice(int n) {
            value = n;
        }
        public int getValue() {
            return value;
        }
    }

    public final int MAX_ROUNDS; 
    private int currentRound;
    private GameRound round;
    private ArrayList<Score> scores = new ArrayList<>();
    private ArrayList<ScoreChoice> availableScoreChoices = new ArrayList<>();


    public ThirtyThrowsGame(){
        this.currentRound = 1;
        MAX_ROUNDS = 10;
        this.round = new GameRound();
        this.availableScoreChoices.addAll(Arrays.asList(ScoreChoice.values()));
        Collections.reverse(availableScoreChoices);
    }

    protected ThirtyThrowsGame(Parcel in) {
        this.scores = (ArrayList<Score>)in.readSerializable();
        MAX_ROUNDS = in.readInt();
        this.currentRound = in.readInt();
        this.round = in.readParcelable(GameRound.class.getClassLoader());
        this.availableScoreChoices.addAll(Arrays.asList(ScoreChoice.values()));
        Collections.reverse(availableScoreChoices);
    }

    private static int[] DiceValuesToArray(ArrayList<Dice> dices) {
        int[] a = new int[dices.size()];
        for (int i = 0; i < dices.size(); i++) {
            a[i] = dices.get(i).GetValue();
        }
        return a;
    }

    public int calculateScoreLow(ArrayList<Dice> dices, int value) {
        int sum = 0;
        int[] a = DiceValuesToArray(dices);
        for (int j : a) {
            if (j <= value) {
                sum += j;
            }
        }
        return sum;
    }

    public int calculateScore(ArrayList<Dice> dices, int value) {
        ArrayList<Integer> matches = new ArrayList<>();
        int[] a = DiceValuesToArray(dices);
        for(int i = 0; i<a.length; ++i)
            for(int j = i + 1; j<a.length; ++j)
                if(a[i] + a[j] == value){ // <- Pairs that match sum
                    matches.add(a[i]);
                    matches.add(a[j]);
                    break;
                } else if(a[i] == value){ // <- Single candidates that match sum
                    matches.add(a[i]); break;
                }
        return matches.stream().mapToInt(Integer::intValue).sum(); // <- Return the sum of ints
    }

    public void Restart(){
        Clear();
        this.round = new GameRound();
    }

    public ArrayList<Score> GetRegistredScores() { return scores; }
    public void SaveScore() { this.scores.add(round.GetScore()); }
    public int TotalScore() {
       return this.scores.stream().mapToInt(Score::getScore).sum();
    }
    public boolean NextRound() {
        if(currentRound < MAX_ROUNDS) {
            ++currentRound;
            SaveScore();
            this.round = null;
            this.round = new GameRound();
            return false;
        }

        SaveScore();
        return true;
    }

    public int GetCurrentRound() {
        return currentRound;
    }
    
    public GameRound GetCurrentGameRound() { return round; }

    public ArrayList<ScoreChoice> GetAvailableScoreChoices(){
        return new ArrayList<>(availableScoreChoices);
    }

    public void Clear(){
        this.currentRound = 1;
        //rounds.clear();
        this.scores.clear();
        this.round = null;
    }

   
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeSerializable((scores));
        dest.writeInt(MAX_ROUNDS);
        dest.writeInt(currentRound);
        dest.writeSerializable(round);
    }

    @Override
    public int describeContents() { return 0; }

    public static final Creator<ThirtyThrowsGame> CREATOR = new Creator<ThirtyThrowsGame>() {
        @Override
        public ThirtyThrowsGame createFromParcel(Parcel in) { return new ThirtyThrowsGame(in); }

        @Override
        public ThirtyThrowsGame[] newArray(int size) { return new ThirtyThrowsGame[size]; }
  

}; }

MainActivity.java:

 // Parcelable key
    private final String STATE_GAME = "STATE_GAME";

    // Classes
    private ThirtyThrowsGame game;

    // View components
    private Button rollBtn;
    private Button collectScoreBtn;
    private TextView roundText;
    private TextView currentScoreText;
    private Spinner scoreSelectionSpinner;
    private ArrayAdapter<ThirtyThrowsGame.ScoreChoice> adapter;
    private ArrayList<ImageView> diceViews;

    // Data
    int score = 0;

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

        // Find elements on the UI by id.
        rollBtn = findViewById(R.id.btnRoll);
        collectScoreBtn = findViewById(R.id.btnCollectScore);
        scoreSelectionSpinner = findViewById(R.id.spinner);
        roundText = findViewById(R.id.RoundText);
        currentScoreText = findViewById(R.id.CurrentScoreText);

        if (savedInstanceState != null) {
            this.game = (ThirtyThrowsGame) savedInstanceState.getSerializable(STATE_GAME);
        } else {
            this.game = new ThirtyThrowsGame();
        }

        rollBtn.setOnClickListener(e -> {
            if (game.GetCurrentGameRound().CanPlay()) {
                RefreshScene(this);
                SetScore();
            } else {
                Toast toast = Toast.makeText(
                        this,
                        "Please collect score to run next round.",
                        Toast.LENGTH_SHORT
                );
                toast.show();
            }
        });

        collectScoreBtn.setOnClickListener(e -> {
            scoreSelectionSpinner.setSelection(0);
            boolean _continue = game.NextRound();
            score = 0;
            SetDefaultDiceView();
            RefreshScene();

            if (_continue) {
                NextActivity();
            }
        });

        SetupDropDown();
        GetDiceViews();
        SetupDiceClickEventListeners();

        currentScoreText.setText(getString(R.string.score, 0));
        roundText.setText(getString(R.string.round, game.GetCurrentRound()));
        RefreshRollButtonText();
    }

    @Override
    protected void onPostCreate(@Nullable Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        if (savedInstanceState != null) {
            if (game != null) {
                score = game.GetCurrentGameRound().GetScore().getScore();
                if (score == 0) {
                    SetDefaultDiceView();
                } else {
                    UpdateImageViews();
                }
                RefreshScene();
            }
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        outState.putSerializable(STATE_GAME, game);
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onBackPressed() {
        moveTaskToBack(true);
    }

 private void SetDefaultDiceView() {
        diceViews.get(0).setImageResource(R.drawable.white1);
        diceViews.get(1).setImageResource(R.drawable.white2);
        diceViews.get(2).setImageResource(R.drawable.white3);
        diceViews.get(3).setImageResource(R.drawable.white4);
        diceViews.get(4).setImageResource(R.drawable.white5);
        diceViews.get(5).setImageResource(R.drawable.white6);
    }

private void SetupDiceClickEventListeners() {
        int index = 0;
        for (ImageView v : diceViews) {
            int finalIndex = index;
            v.setOnClickListener(e -> {
              this.game.GetCurrentGameRound().GetDices().get(finalIndex).ToggleMarked();
                diceViews.get(finalIndex).setImageResource(game.GetCurrentGameRound()
                        .GetDices().get(finalIndex).GetCurrentImage());
            });
            ++index;
        }
    }

 public void RefreshScene() {
        roundText.setText(getString(R.string.round, game.GetCurrentRound()));
        currentScoreText.setText(getString(R.string.score, game.GetCurrentGameRound().GetScore().getScore()));
        RefreshRollButtonText();
    }

    public void RefreshScene(Context context) {
        game.GetCurrentGameRound().TossDices(context);
        roundText.setText(getString(R.string.round, game.GetCurrentRound()));
        UpdateImageViews();
        RefreshRollButtonText();
    }

這幾乎是這里的所有代碼。 如果有人可以發現問題,請告訴我。 我已經為此苦苦掙扎了好幾個星期,而且越來越煩人。 我需要確保,當您將背面留在后台時它可以工作,以便它恢復游戲狀態並繼續在 UI 上反映這一點。

編輯:是的,我在調試模式下運行這個單步拋出代碼。

編輯 6/27/2022:我啟用了開發人員模式並啟用了“不保留活動”。

由於按下后退按鈕時已保存的實例狀態會被清除,因此您需要將游戲數據存儲在更持久的東西中。 一種常見的選擇是將其序列化為 Json 並將其保存在 SharedPreferences 中。 你可以很容易地用Moshi 庫做到這一點。

三十擲游戲

private static final Moshi moshi = new Moshi
        .Builder()
        .add(new ScoreArrayListMoshiAdapter())
        .add(new ScoreChoiceArrayListMoshiAdapter())
        .build();

private static final JsonAdapter<ThirtyThrowsGame> jsonAdapter = moshi.adapter(ThirtyThrowsGame.class);

static ThirtyThrowsGame fromJson(String json) {
    try {
        return jsonAdapter.fromJson(json);
    }
    catch(IOException e) {
        return null;
    }
}

String toJson() {
    return jsonAdapter.toJson(this);
}

// Moshi doesn't play nice with ArrayList, so you need to add
// these to convert your ArrayList<X> back and forth to List<X>
static class ScoreArrayListMoshiAdapter {
    @ToJson
    List<Score> arrayListToJson(ArrayList<Score> list) {
        return list;
    }

    @FromJson
    ArrayList<Score> arrayListFromJson(List<Score> list) {
        return new ArrayList<>(list);
    }
}

static class ScoreChoiceArrayListMoshiAdapter {
    @ToJson
    List<ScoreChoice> arrayListToJson(ArrayList<ScoreChoice> list) {
        return list;
    }

    @FromJson
    ArrayList<ScoreChoice> arrayListFromJson(List<ScoreChoice> list) {
        return new ArrayList<>(list);
    }
}

除了刪除 Parcelable 和 Serializable 接口之外,不需要對其他類進行任何更改。 然后在你的活動中你可以使用

private ThirtyThrowsGame game;
private SharedPreferences prefs;
private final String GAME = "SAVED_GAME";

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

    prefs = PreferenceManager.getDefaultSharedPreferences(this);
    
    if( prefs.contains(GAME) ) {
        String json = prefs.getString(GAME, "{}");
        game = ThirtyThrowsGame.fromJson(json);
    }
    else {
        game = new ThirtyThrowsGame();
    }
}

// When the game is "done" and you actually want to clear it, delete it
// from SharedPrefs yourself and exit without saving the state.
private boolean save_on_exit = true;
private void finishGame() {
    prefs.edit().remove(GAME).apply();
    save_on_exit = false;
    finish();
}

// save the game at some point in the lifecycle, either onPause
// or onStop - unless the game is finishing
@Override
protected void onPause() {
    super.onPause();
    if( save_on_exit ) {
        prefs.edit().putString(GAME, game.toJson()).apply();
    }
}

要使用 Moshi,只需將其添加到 Gradle 文件中

implementation 'com.squareup.moshi:moshi:1.13.0'

保存活動狀態覆蓋onSaveInstanceState方法和恢復狀態覆蓋onRestoreInstanceState方法。

class GameObj extends Serializable
{
  // game members

  public Obj() {}

  // getters and 
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
  super.onSaveInstanceState(savedInstanceState);
  savedInstanceState.putSerializable("my_obj", obj);
}

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
  super.onRestoreInstanceState(savedInstanceState);
  GameObj obj = savedInstanceState.getSerializable("my_obj", obj);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM