[英]Save and recover state of game object in Android
編輯:主要問題,處理按下后退按鈕的保存狀態。 然后在 onCreate 上重新加載包。 不確定,如何正確地做到這一點。 沒有,到目前為止我嘗試過的工作正常。
我正在嘗試保存和恢復我在 Android 中制作的簡單游戲的狀態。 我正在利用以下事件: onSaveInstanceState()
、 onBackPressed()
、 onCreate()
和onPostCreate()
。 我正在使我的對象Serializable
和Parcelable
。
但是,在按下后退按鈕之前或只是切換應用程序時,它不會成功記住或恢復狀態。 當然,我在這里做錯了什么。 這是一個行為錯誤,即沒有發生真正的錯誤。
骰子.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.