这一分类,我打算自己做一些简单易做小游戏。一方面可以巩固自己学的知识,一方面做出来也可以自己玩。自己玩自己做的游戏相信别有一番风味~想到什么游戏里的好点子可以立即更新游戏,这种感觉想必是pangpang的!
游戏简介
2048我就不多说了,大家应该都知道。
项目流程
我打算先实现游戏功能,界面什么的先一切从简~所以大体的界面流程就是一开始的点击开始游戏之后直接跳转到游戏界面。
开始行动
新建一个项目,在里面新建两个Activity,分别为StartActivity和GameActivity
开始界面
在activity_start里面就只放一个Button用来跳转到GameActivity
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="start"
android:text="开始游戏"
android:textSize="20sp" />
</LinearLayout>
然后在StartActivity把start方法写好:
package com.example.a2048;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class StartActivity extends AppCompatActivity {
@Override
public void onCreate( Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_start);
}
public void start(View view)
{
startActivity(new Intent(this,GameActivity.class));
}
}
那么这样子我们就成功写好了两个界面之间的跳转。到这一步我们应该没有什么问题。下面我们来分析分析GameActivity应该怎么写。
游戏界面
一般的2048应该是由16个格子构成,一开始游戏随机两个格子出现两个数字,随着手指的滑动,这两个格子会向屏幕一侧偏移直至边缘,如果两数字相同且随着手指移动方向摆放则把两个数字相加。
我们先把上述的功能实现吧。
layout
用最笨的方法就是在布局中使用GridLayout(网格布局),里面写16个TextView。
由于原生的GridLayout只支持API21以上的手机,所以我们这里为了兼容性就用v7包里面的GridLayout,在gradle里面加一句compile 'com.android.support:gridlayout-v7:25.3.1'
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:columnCount="4"
app:rowCount="4">
<TextView
android:id="@+id/tv1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:textSize="20sp"
android:gravity="center"
android:text="1" />
<TextView
android:id="@+id/tv2"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:textSize="20sp"
android:gravity="center"
android:text="1" />
<TextView
android:id="@+id/tv3"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:textSize="20sp"
android:gravity="center"
android:text="1" />
<TextView
android:id="@+id/tv4"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:textSize="20sp"
android:gravity="center"
android:text="1" />
<TextView
android:id="@+id/tv5"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:textSize="20sp"
android:gravity="center"
android:text="1" />
<TextView
android:id="@+id/tv6"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:textSize="20sp"
android:gravity="center"
android:text="1" />
<TextView
android:id="@+id/tv7"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:textSize="20sp"
android:gravity="center"
android:text="1" />
<TextView
android:id="@+id/tv8"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:textSize="20sp"
android:gravity="center"
android:text="1" />
<TextView
android:id="@+id/tv9"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:textSize="20sp"
android:gravity="center"
android:text="1" />
<TextView
android:id="@+id/tv10"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:textSize="20sp"
android:gravity="center"
android:text="1" />
<TextView
android:id="@+id/tv11"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:textSize="20sp"
android:gravity="center"
android:text="1" />
<TextView
android:id="@+id/tv12"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:textSize="20sp"
android:gravity="center"
android:text="1" />
<TextView
android:id="@+id/tv13"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:textSize="20sp"
android:gravity="center"
android:text="1" />
<TextView
android:id="@+id/tv14"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:textSize="20sp"
android:gravity="center"
android:text="1" />
<TextView
android:id="@+id/tv15"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:textSize="20sp"
android:gravity="center"
android:text="1" />
<TextView
android:id="@+id/tv16"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:textSize="20sp"
android:gravity="center"
android:text="1" />
</android.support.v7.widget.GridLayout>
效果应该是这样子的:
在这篇博文里,这个layout我们就不需要修改了,虽然难看了点,也没有积分之类的功能~但对于游戏核心功能这个layout已经足够了?
activity实现过程
1.游戏刚开始的初始化
GridLayout里面指定了总共有4行4列,每个TextView里面指定了在行或者列中的比重都为1。这样就确保每个TextView都是一样大。然后我们在GameActivity把这些TextView的id存在数组里,并且初始化一个4x4矩阵用来表示游戏中的数值。
package com.example.a2048;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class GameActivity extends AppCompatActivity {
private int[][] tvNum= new int[4][4];//数值
private int[] tvId = {R.id.tv1,R.id.tv2,R.id.tv3,R.id.tv4,R.id.tv5,R.id.tv6,R.id.tv7,R.id.tv8,R.id.tv9,R.id.tv10,R.id.tv11,R.id.tv12,R.id.tv13,R.id.tv14,R.id.tv15,R.id.tv16};;//16个TextView的id
private TextView[] textViews = new TextView[16];//16个TextView
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
for (int i = 0; i < 16; i++) {//绑定所有TextView
textViews[i] = (TextView) findViewById(tvId[i]);
textViews[i].setText("");//验证是否绑定成功
}
}
}
因为是最笨的方法,我们就只能用代码量来弥补~写到这里我们把界面上所有的数字全部清空了。
下一步实现的是随机生成两个数字,并在num矩阵里面做相应的记录。
根据传入的位置和数值来给TextView和num矩阵赋值
private boolean setXY(int num, int value) {//根据生成的随机数来解析出应该放置的xy坐标,val为应该放入的数值
int x,y;
x = num / 4;//几行
y = num % 4;//几列
if (tvNum[x][y]!=0)//若原来格子里面已经有数字则放入失败
{
return false;
}else{
textViews[num].setText(value + "");//给相应的格子赋值
tvNum[x][y] = value;//记录相应位置的数值
return true;
}
}
在onCreate()里面初始化两个0~15的随机数然后调用setXY()解析出具体的坐标值,由于是游戏初始化时赋值,即目前16个位置都是空的,那么只需要保证两个随机数不同就可以保证两个数的放置一定是正确的,所以我们可以这么写:
在onCreate()里面加上下面的代码
调用setXY()设置初始值
int initNum1, initNum2;//初始化位置
initNum1 = (int) Math.round(Math.random() * 15);//生成0~15随机整数
initNum2 = (int) Math.round(Math.random() * 15);
while (initNum1==initNum2) {
initNum1 = (int) Math.round(Math.random() * 15);
initNum2 = (int) Math.round(Math.random() * 15);
}
setXY(initNum1, 2);
setXY(initNum2, 2);
2.游戏中的滑动手势
通过onTouchEvent来监听手指触摸屏幕的事件,event.getRawX()表示获取事件发生时手指在屏幕上的X坐标,Y坐标类似。在每一次按下的时候,我们都把按下时的XY坐标记录下来,在放开的时候记录手指抬起时的XY坐标。两两相减可以得出此次滑动动作的偏向。如|X的差值|>2*|Y的差值|说明我更倾向于的是横向滑动,此外我们也应该限制一下滑动的最小距离,防止玩家在误触的时候游戏响应。具体大家看下面的代码:
滑动的监听大概动作
int downX = 0;//按下时候的坐标
int downY = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {//监听触摸事件
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = (int) event.getRawX();
downY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
int upX = (int) event.getRawX();
int upY = (int) event.getRawY();
if (Math.abs(upX - downX) > Math.max(2 * Math.abs(upY - downY), 100)) {
Log.d("111", "x方向滑动");
} else if (Math.abs(upY - downY) > Math.max(2 * Math.abs(upX - downX), 100)) {
Log.d("111", "y方向滑动");
} else {
Log.d("111", "不滑动");
}
break;
}
return true;//表示消耗这一系列点击事件,不懂的可以先这么写着。
}
滑动的监听具体实现
那么下面我们就只需要在我们写的Log那里加入实际的游戏变化就可以。为了方便起见,我们先不加入数字滑动的动画。对了在判断出是x方向还是y方向上的滑动之后,我们需要接着判断是左滑还是右滑或者说是上滑还是下滑。上述代码修改为:
if (Math.abs(upX - downX) > Math.max(2 * Math.abs(upY - downY), 100)) {
if(upX>downX)
{
Log.d("111","右滑");
}else{
Log.d("111","左滑");
}
} else if (Math.abs(upY - downY) > Math.max(2 * Math.abs(upX - downX), 100)) {
if(upY>downY)
{
Log.d("111","下滑");
}else{
Log.d("111","上滑");
}
} else {
Log.d("111", "不滑动");
}
在手机上跑一遍,发现与手势滑动方向一致,那么我们接着编写代码。其实无论是哪个方向上的滑动,其实说起来也都一样,都是先对矩阵tvNum进行操作,然后再把矩阵上的值表示在16个TextView里面。那么我们先开始研究左滑应该怎么实现吧。
1.左滑
当手机检测到手势为左滑时,分析tvNum矩阵,看看当前的矩阵能否左滑。那么有哪些情况是能左滑的呢?
- 检测矩阵横向是否存在两个相邻的相同值(不包括0),若有则可以左滑,并且把这两个相同的值加起来赋给左边那个格子
- 检测同一行的两个数值(不包括0)或者是数字与左边界之间是否存在0,若存在则可滑动,滑完以后也要考虑两数相加的情况。
不考虑两数相加左滑
private void moveToLeft() {//左滑
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
int n = 0;//若0后面全为0则下面的while会死循环,设置n表示顶多一行只有4个0
while (tvNum[i][j] == 0 && n < 4) {//若检测到哪一位为0则把这一位放到最右边那一列
n++;
for (int k = j; (k + 1) < 4; k++) {//简单的冒泡,把0沉下去
int temp = 0;
temp = tvNum[i][k];
tvNum[i][k] = tvNum[i][k + 1];
tvNum[i][k + 1] = temp;
}
}
}
}
for (int i = 0; i < 4; i++) {//把更新之后的tvNum在游戏界面上显示出来
for (int j = 0; j < 4; j++) {
if (tvNum[i][j] == 0) {//0则不显示数值
textViews[4 * i + j].setText("");
} else {
textViews[4 * i + j].setText(tvNum[i][j] + "");
}
}
}
}
必要的注释我都写在上面了,其实我这么写多做了很多次不必要的循环,不过为了方便我们先这么写着,之后再考虑游戏的优化。
我们下一步应该添加两数相加的功能。这里也要分成几种情况考虑:
- 两个相同的数则直接相加放在左边格子。
- 三个相同的数则把左边两个加起来
- 四个相同的数则把左边两个加起来,右边两个加起来
在更新textViews之前加上如下代码:
两数相加
for (int i = 0; i < 4; i++) {//相同数相加
for (int j = 0; (j + 1) < 4; j++) {
if (tvNum[i][j] == tvNum[i][j + 1] && tvNum[i][j] != 0) {//两数相同且不为0
tvNum[i][j] *= 2;
tvNum[i][j + 1] = 0;
for (int k = j + 1; (k + 1) < 4; k++) {//把第二个数置0冒泡法移到最后
int temp = 0;
temp = tvNum[i][k];
tvNum[i][k] = tvNum[i][k + 1];
tvNum[i][k + 1] = temp;
}
}
}
}
代码也很简单,大家可以通过修改刚开始的setXY(initNum1, 2);setXY(initNum2, 2);
来测试我们代码是否写的有问题。比如改成setXY(0, 2);setXY(2, 2);setXY(3, 2);
那么进入游戏后会在第一行的0,2,3三个坐标点出现2。然后大家试着往左滑动,看看有没有效果。
那么到现在我们游戏还差最后一个功能。就是每次在左滑以后随机位置生成一个值(2或者4)我们连滑动都解决了,这个随机生成值还有什么难的~~
随机生成2或者4
boolean ifEmpty = false;//判断目前矩阵是否还有位置为0
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (tvNum[i][j]==0)
{
ifEmpty = true;
}
}
}
if (ifEmpty)//如果有则开始随机生成
{
int randomXY;
randomXY = (int) Math.round(Math.random() * 15);
while (tvNum[randomXY / 4][randomXY % 4] != 0) {
randomXY = (int) Math.round(Math.random() * 15);
}
tvNum[randomXY / 4][randomXY % 4] = ((int) Math.round(Math.random()+1)) * 2;
}
到这里,左滑的功能应该已经差不多实现了。那么根据左滑,我们也可以依样画葫芦写出右滑,上滑,下滑的代码,剩下的核心功能还有游戏结束的判定。这篇写的已经够多了,剩下的我放到下一篇核心功能实现(2)吧。当然大家可以自己先写下去,相信也难不倒大家。
对了,最后我把2048做出来之后会把源码放到github上,大家可以自行下载参考呦~