安卓小游戏 2048 新手练手项目 完整代码(含注释)

    科技2022-07-14  143

    看了极客的安卓2048的开发教程,大概了解了一下思路,然后自己就开始写了。后来发现这个设计思路不是太好,不方便加移动动画,就只加了创建卡片和合并的动画,不过用来练手还可以。

    游戏截图如下: 如果想拷贝到本地运行的话,注意修改和包名相关的地方 或者在创建工程的时候按照以下命名:

    项目名: Game2048 包名: pers.hurric.game2048

    AndroidManifest.xml

    仅需设置screenOrientation

    <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="pers.hurric.game2048"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:screenOrientation="portrait"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/textScore" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/score" android:textColor="@color/colorPrimary" android:textSize="24sp" android:textStyle="bold" app:layout_constraintBottom_toTopOf="@+id/guideline4" app:layout_constraintEnd_toStartOf="@+id/guideline2" app:layout_constraintStart_toStartOf="@+id/guideline6" app:layout_constraintTop_toTopOf="@+id/guideline3" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.5" /> <Button android:id="@+id/buttonReplay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/button_replay" android:textColor="@android:color/holo_blue_light" app:layout_constraintBottom_toTopOf="@+id/guideline4" app:layout_constraintEnd_toStartOf="@+id/guideline7" app:layout_constraintStart_toStartOf="@+id/guideline2" app:layout_constraintTop_toTopOf="@+id/guideline3" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.12" /> <TextView android:id="@+id/textHighestScore" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/highest_score" android:textColor="@android:color/holo_purple" android:textSize="18sp" android:textStyle="italic" app:layout_constraintBottom_toTopOf="@+id/guideline3" app:layout_constraintEnd_toStartOf="@+id/guideline7" app:layout_constraintStart_toStartOf="@+id/guideline2" app:layout_constraintTop_toTopOf="parent" /> <pers.hurric.game2048.GameView android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toTopOf="@+id/guideline5" app:layout_constraintEnd_toStartOf="@+id/guideline7" app:layout_constraintStart_toStartOf="@+id/guideline6" app:layout_constraintTop_toTopOf="@+id/guideline4" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.26" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.9" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.05109489" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline7" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.95" /> </androidx.constraintlayout.widget.ConstraintLayout>

    string.xml

    <resources> <string name="app_name">Game2048</string> <string name="score">Score : 0</string> <string name="highest_score">Highest Score : 0</string> <string name="button_replay">Restart</string> </resources>

    color.xml

    <?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#008577</color> <color name="colorPrimaryDark">#00574B</color> <color name="colorAccent">#D81B60</color> <color name="gameViewBackgroundColor">#D2B48C</color> <color name="backgroundColor0">#DEB887</color> <color name="textColor0">#00000000</color> <color name="textColor2">#333300</color> <color name="textColor4">#333300</color> <color name="textColorCommon">#FFFAFA</color> <color name="backgroundColor2">#FDF5E6</color> <color name="backgroundColor4">#FFE4B5</color> <color name="backgroundColor8">#FFE4C4</color> <color name="backgroundColor16">#FFDAB9</color> <color name="backgroundColor32">#FF7F50</color> <color name="backgroundColor64">#FF4500</color> <color name="backgroundColor128">#FF8C00</color> <color name="backgroundColor256">#FFD700</color> <color name="backgroundColor512">#9ACD32</color> <color name="backgroundColor1024">#483D8B</color> <color name="backgroundColor2048">#4B0082</color> <color name="backgroundColorBiggerThan2048">#000000</color> </resources>

    MainActivity.java

    package pers.hurric.game2048; import androidx.appcompat.app.AppCompatActivity; import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.KeyEvent; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private final String FILE_NAME = "MyData"; private final String HIGHEST_SCORE = "highest_score"; private TextView textScore; private TextView textHighestScore; private Button buttonReplay; private int score = 0; private int highestScore = 0; public static MainActivity mainActivity; public MainActivity(){ mainActivity = this; } public void addScore(int score) { this.score += score; textScore.setText("Score : " + this.score); //更新最高分 updateHighestScore(this.score); } private void updateHighestScore(int score){ if(score > highestScore){ highestScore = score; textHighestScore.setText("HighestScore : " + score); //存储最高分 SharedPreferences shp = getSharedPreferences(FILE_NAME, MODE_PRIVATE); SharedPreferences.Editor editor = shp.edit(); editor.putInt(HIGHEST_SCORE, highestScore); editor.apply(); } } public void clearScore(){ score = 0; textScore.setText("Score : " + 0); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textScore = findViewById(R.id.textScore); textHighestScore = findViewById(R.id.textHighestScore); buttonReplay = findViewById(R.id.buttonReplay); //读取最高分 SharedPreferences shp = getSharedPreferences(FILE_NAME, MODE_PRIVATE); highestScore = shp.getInt(HIGHEST_SCORE, 0); textHighestScore.setText("HighestScore : " + highestScore); buttonReplay.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { GameView.gameView.replayGame(); } }); } private boolean isExit = false; class ExitHandler extends Handler{ @Override public void handleMessage(Message msg) { super.handleMessage(msg); if(msg.what == 0){ isExit = false;//修改状态为退出 } } }; ExitHandler mHandler = new ExitHandler(); @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if(keyCode == KeyEvent.KEYCODE_BACK){ if(!isExit){ isExit = true; Toast.makeText(this, "再按一次退出游戏", Toast.LENGTH_SHORT).show(); //延迟更改状态信息 mHandler.sendEmptyMessageDelayed(0, 2000); } else{ finish(); } } return false; } }

    GameView.java

    自定义游戏面板

    package pers.hurric.game2048; import android.content.Context; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.MotionEvent; import android.view.View; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.LinearInterpolator; import android.view.animation.RotateAnimation; import android.view.animation.ScaleAnimation; import android.widget.GridLayout; import android.widget.Toast; import java.util.Random; public class GameView extends GridLayout { public static GameView gameView; private Card[][] cards = new Card[4][4]; public GameView(Context context) { super(context); gameView = this; initGame(); } public GameView(Context context, AttributeSet attrs) { super(context, attrs); gameView = this; initGame(); } public GameView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); gameView = this; initGame(); } public void initGame(){ this.setBackgroundColor(getResources().getColor(R.color.gameViewBackgroundColor)); setColumnCount(4); int cardWidth = GetCardWidth(); addCards(cardWidth, cardWidth); randomCreateCard(2); setListener(); } public void replayGame(){ MainActivity.mainActivity.clearScore(); for(int i = 0; i < 4; ++i){ for(int j = 0; j < 4; ++j){ cards[i][j].setNum(0); } } randomCreateCard(2); } /* * 监听Touch事件 */ private void setListener(){ setOnTouchListener(new OnTouchListener() { private float staX, staY, endX, endY; @Override public boolean onTouch(View v, MotionEvent event) { switch(event.getAction()){ case MotionEvent.ACTION_DOWN: staX = event.getX(); staY = event.getY(); break; case MotionEvent.ACTION_UP: endX = event.getX(); endY = event.getY(); boolean swiped = false;//记录是否有效滑动了 //水平移动更多 if(Math.abs(endX - staX) > Math.abs(endY - staY)){ if(endX - staX > 10){ if(swipeRight()){ swiped = true; } } else if(endX - staX < -10){ if(swipeLeft()){ swiped = true; } } } else{ if(endY - staY < -10){ if(swipeUp()){ swiped = true; } } else if(endY - staY > 10){ if(swipeDown()){ swiped = true; } } } //滑动后创建新块,并检查当前状态是否能滑动 if(swiped){ randomCreateCard(1); if(!canSwipe()){ gameOver(); } } break; } return true; } }); } /* * 返回该次滑动是否有效(有卡片移动或合并) */ private boolean swipeUp(){ boolean flag = false; for(int j = 0; j < 4; ++j){ int ind = 0; //从上往下依次处理 for(int i = 1; i < 4; ++i){ //如果是存在数字的,往上遍历 if(cards[i][j].getNum() != 0){ for(int ii = i - 1; ii >= ind; --ii){ //如果这块是空的,将数字上移 if(cards[ii][j].getNum() == 0){ cards[ii][j].setNum(cards[i][j].getNum()); cards[i][j].setNum(0); i--;//上移 flag = true; } //如果这块是相同的数,合并,合并的块不能一下合并两次,更新ind,不再遍历合并的块 else if(cards[ii][j].getNum() == cards[i][j].getNum()){ cards[ii][j].setNum((cards[i][j].getNum() * 2)); cards[i][j].setNum(0); flag = true; ind = ii + 1;//已经合过,该点不再合成 MainActivity.mainActivity.addScore(cards[ii][j].getNum() / 2); //播放合并动画 playMergeAnimation(ii, j); break; } //上面的块数字不同,退出循环 else break; } } } } return flag; } private boolean swipeDown(){ boolean flag = false; for(int j = 0; j < 4; ++j){ int ind = 4; for(int i = 2; i >= 0; --i){ if(cards[i][j].getNum() != 0){ for(int ii = i + 1; ii < ind; ++ii){ if(cards[ii][j].getNum() == 0){ cards[ii][j].setNum(cards[i][j].getNum()); cards[i][j].setNum(0); flag = true; i++; } else if(cards[ii][j].getNum() == cards[i][j].getNum()){ cards[ii][j].setNum((cards[i][j].getNum() * 2)); cards[i][j].setNum(0); flag = true; ind = ii; MainActivity.mainActivity.addScore(cards[ii][j].getNum() / 2); playMergeAnimation(ii, j); break; } else break; } } } } return flag; } private boolean swipeLeft(){ boolean flag = false; for(int i = 0; i < 4; ++i){ int ind = 0; for(int j = 1; j < 4; ++j){ if(cards[i][j].getNum() != 0){ for(int jj = j - 1; jj >= ind; --jj){ if(cards[i][jj].getNum() == 0){ cards[i][jj].setNum(cards[i][j].getNum()); cards[i][j].setNum(0); flag = true; j--; } else if(cards[i][jj].getNum() == cards[i][j].getNum()){ cards[i][jj].setNum((cards[i][j].getNum() * 2)); cards[i][j].setNum(0); flag = true; ind = jj + 1; MainActivity.mainActivity.addScore(cards[i][jj].getNum() / 2); playMergeAnimation(i, jj); break; } else break; } } } } return flag; } private boolean swipeRight(){ boolean flag = false; for(int i = 0; i < 4; ++i){ int ind = 4; for(int j = 2; j >= 0; --j){ if(cards[i][j].getNum() != 0){ for(int jj = j + 1; jj < ind; ++jj){ if(cards[i][jj].getNum() == 0){ cards[i][jj].setNum(cards[i][j].getNum()); cards[i][j].setNum(0); flag = true; j++; } else if(cards[i][jj].getNum() == cards[i][j].getNum()){ cards[i][jj].setNum((cards[i][j].getNum() * 2)); cards[i][j].setNum(0); flag = true; ind = jj; MainActivity.mainActivity.addScore(cards[i][jj].getNum() / 2); playMergeAnimation(i, jj); break; } else break; } } } } return flag; } /** *如果存在空白块,或者相邻的数字相同的块,则可以继续滑动 */ private boolean canSwipe(){ for(int i = 0; i < 4; ++i){ for(int j = 0; j < 4; ++j){ if(cards[i][j].getNum() == 0){ return true; } else if(i != 3 && cards[i][j].getNum() == cards[i + 1][j].getNum()){ return true; } else if(j != 3 && cards[i][j].getNum() == cards[i][j + 1].getNum()){ return true; } } } return false; } private void addCards(int width, int height){ Card c; for(int i = 0; i < 4; ++i){ for(int j = 0; j < 4; ++j){ c = new Card(getContext()); addView(c, width, height); cards[i][j] = c; } } } private void gameOver(){ Toast.makeText(getContext(), "游戏结束", Toast.LENGTH_SHORT).show(); } private int GetCardWidth() { //获取屏幕信息 DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); //根据布局,GameView是占屏幕宽度的90%,除以4就是卡片边长 return (int)((displayMetrics.widthPixels * 0.9f) / 4); } /* * 递归随机,玄学复杂度,期望递归次数小于 16 次 */ private void randomCreateCard(int cnt){ Random random = new Random(); int r = random.nextInt(4); int c = random.nextInt(4); //该处已经存在数字,重新随机r, c if(cards[r][c].getNum() != 0){ randomCreateCard(cnt); return; } int rand = random.nextInt(10); if(rand >= 2) rand = 2; else rand = 4; cards[r][c].setNum(rand); //播放创建动画 playCreateAnimation(r, c); if(cnt >= 2){ randomCreateCard(cnt - 1); } } /* * 播放创建新块动画 */ private void playCreateAnimation(int r, int c){ AnimationSet animationSet = new AnimationSet(true); //旋转 RotateAnimation anim = new RotateAnimation(0,360,RotateAnimation.RELATIVE_TO_SELF,0.5f, RotateAnimation.RELATIVE_TO_SELF,0.5f); anim.setDuration(250); anim.setRepeatCount(0); anim.setInterpolator(new LinearInterpolator()); //缩放 ScaleAnimation anim2 = new ScaleAnimation(0,1,0,1, Animation.RELATIVE_TO_SELF,0.5f, Animation.RELATIVE_TO_SELF,0.5f ); anim2.setDuration(250); anim2.setRepeatCount(0); animationSet.addAnimation(anim); animationSet.addAnimation(anim2); cards[r][c].startAnimation(animationSet); } /* * 播放合并动画 */ private void playMergeAnimation(int r, int c){ ScaleAnimation anim = new ScaleAnimation(1,1.2f,1,1.2f, Animation.RELATIVE_TO_SELF,0.5f, Animation.RELATIVE_TO_SELF,0.5f ); anim.setDuration(150); anim.setRepeatCount(0); anim.setRepeatMode(Animation.REVERSE); cards[r][c].startAnimation(anim); } }

    Card.java

    代表一个格子

    package pers.hurric.game2048; import android.content.Context; import android.view.Gravity; import android.widget.FrameLayout; import android.widget.TextView; import androidx.annotation.NonNull; import java.util.HashMap; import java.util.Map; public class Card extends FrameLayout { TextView textView; private int num; static Map<Integer, Integer> backgroundColorIdMap = new HashMap<>(); static Map<Integer, Integer> textColorIdMap = new HashMap<>(); static { textColorIdMap.put(0, R.color.textColor0); textColorIdMap.put(2, R.color.textColor2); textColorIdMap.put(4, R.color.textColor4); backgroundColorIdMap.put(0, R.color.backgroundColor0); backgroundColorIdMap.put(2, R.color.backgroundColor2); backgroundColorIdMap.put(4, R.color.backgroundColor4); backgroundColorIdMap.put(8, R.color.backgroundColor8); backgroundColorIdMap.put(16, R.color.backgroundColor16); backgroundColorIdMap.put(32, R.color.backgroundColor32); backgroundColorIdMap.put(64, R.color.backgroundColor64); backgroundColorIdMap.put(128, R.color.backgroundColor128); backgroundColorIdMap.put(256, R.color.backgroundColor256); backgroundColorIdMap.put(512, R.color.backgroundColor512); backgroundColorIdMap.put(1024, R.color.backgroundColor1024); } public Card(@NonNull Context context) { super(context); textView = new TextView(context); textView.setGravity(Gravity.CENTER); textView.setText(getNum() + ""); textView.setTextSize(50); setNum(0); LayoutParams lp = new LayoutParams(-1, -1); lp.setMargins(10, 10, 10, 10); addView(textView, lp); } public int getNum() { return num; } public void setNum(int num) { this.num = num; textView.setText(num + ""); //数字改变时,同时改变改变字体大小和颜色 changeColor(num); changeSize(num); } private void changeSize(int num){ if(num >= 1024){ textView.setTextSize(25); } else if(num >= 128){ textView.setTextSize(35); } else if(num >= 16){ textView.setTextSize(42); } else{ textView.setTextSize(50); } } private void changeColor(int num){ if(num >= 8){ textView.setTextColor(getResources().getColor(R.color.textColorCommon)); } else{ textView.setTextColor(getResources().getColor(textColorIdMap.get(num))); } if(num >= 2048){ textView.setBackgroundColor(getResources().getColor(R.color.backgroundColorBiggerThan2048)); } else{ textView.setBackgroundColor(getResources().getColor(backgroundColorIdMap.get(num))); } } }
    Processed: 0.014, SQL: 8