当前位置:   article > 正文

Andriod Studio小游戏_studio34游戏

studio34游戏

文章目录\n一、项目概述\n二、开发环境\n三、需求分析\n四、实现过程\n1、拼图游戏布局绘制\n2、拼图游戏时间计时\n3、拼图游戏打乱显示\n4、拼图游戏碎片位置切换\n5、拼图游戏成功的条件\n6、拼图游戏重新开始\n五、运行效果\n六、项目总结\n七、项目源码\n一、项目概述\n之前有不少粉丝私信我说,能不能用Android原生的语言开发一款在手机上运行的游戏呢?\n\n说实话,使用java语言直接开发游戏这个需求有点难,因为一些比较复杂的游戏都是通过cocos2D或者Unity3D等游戏引擎开发出来的,然后再移植到Android手机当中,使用完整的游戏引擎开发的过程比较简单,而且界面比较流畅,观感和体验度都很好。\n\n所以直接使用java开发的游戏并不多。当然,虽说不多但也有。简单些的比如:2048、拼图游戏、贪吃蛇、推箱子等,复杂点的比如:斗地主,这些都可以用java语言开发。因为这些游戏刷新界面次数比较少,是可以用java开发出来的。\n\n所以在这篇博客里面,我们就来开发一款简单的拼图游戏,这款拼图游戏就和我们小时候玩的游戏是一样的,这里面的涉及到的算法不多,可以很容易学会,是作为入门Android的一个非常好的实例。\n\n二、开发环境\n\n\n三、需求分析\n我们先来看下最终要实现的效果:\n\n可以看到游戏开始后,开始计时,然后下面是被打乱的九宫格图片,最后一块是空白的,因为要留出空间移动,中间是重新开始按钮,点击就会重新计时而且拼图碎片重新打乱,最底下是原图,方便大家对照着进行拼凑。当你拼图完成后,上面的第九块拼图会立刻显示出来补齐整张图片,然后弹出对话框,告诉你拼图成功,用时为多少多少秒,点击确认即可。\n\n所以我们分为六个步骤来实现:\n\n拼图游戏布局绘制\n拼图游戏时间计时\n拼图游戏打乱显示\n拼图游戏碎片位置切换\n拼图游戏成功的条件\n拼图游戏重新开始\n我们来看下需要准备的图片素材:\n\n这里先是一张小熊的样图,命名就是yangtu。然后就是将它按九宫格裁剪成的九张图片,命名格式我来解释下:我们看第八张我选中的图片,它的名字为img_xiaoxiong_02x01。这里解释下为什么是02x01,这就可以看做一个三行三列的二维数组,排列方式就和下面一样。数组行和列下标都是从0开始,所以第八张就是在第2行第1列,所以就是02x01,其他的也以此类推。\n大家可以自己选图片进行裁剪命名,当然也可以直接下载我的源码,里面就有这些图片。\n\n下面我们就一起来实现这个拼图游戏吧~\n\n四、实现过程\n1、拼图游戏布局绘制\n我们首先来分析下游戏的layout布局\n再来看下最终实现的效果图,先分析一下怎么绘制布局,实现一个项目的第一步是将布局按照自己期望的样子完成。\n\n因为这是一个上下结构,所以我们用一个线性布局(LinearLayout)来实现最合适,方向(orientation)设置为竖直方向(vertical)。可以看到这个拼图分为三行三列,所以我们直接将每一行分为一个小的LinearLayout,一共三个,然后在每个小的LinearLayout里面水平放三个图片按钮,这样就实现了,思路有了,我们来绘制吧。\n\n\n我们来绘制游戏的layout布局\n从上至下的第一个布局是显示时间的TextView,我们将它的id设置为pt_tv_time,layout_width和layout_height都设置为wrap_content,就是适应内容大小,然后text文本内容设为“时间:0”,这个是方便测试写上文本的,因为边写代码可以边看旁边的效果变化。\n然后layout_gravity设置为"center",就是设置自己在父容器(顶层的LinearLayout)中居中,这里补充下知识点:\n\ngravity是设置自身内部元素的对齐方式。比如一个TextView,则是设置内部文字的对齐方式。如果是ViewGroup组件如LinearLayout的话,则为设置它内部view组件的对齐方式。\n\nlayout_gravity是设置自身相当于父容器的对齐方式。比如,一个TextView设置layout_gravity属性,则表示这TextView相对于父容器的对齐方式。\n\n再来改变下字体大小,设置textSize为20sp,sp是像素,补充下单位的知识点:\n\ndp: device independent pixels(设备独立像素),不同设备有不同的显示效果,和设备硬件有关。\npx: pixels(像素).,不同设备显示效果相同,这个用的比较多。\npt: point,是一个标准的长度单位,1pt=1/72英寸,用于印刷业,非常简单易用。\nsp: scaled pixels(放大像素),主要用于字体显示best for textsize。\n最后设置字体颜色为#FF0000,即红色。一般是通过colors.xml资源来引用,这里因为红色比较好表示就直接设置了。\n\nTextView代码如下:\n\n\u003CTextView\n android:id="@+id/pt_tv_time"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:text=“时间 : 0”\n android:layout_gravity=“center”\n android:textSize=“20sp”\n android:textColor="#FF0000"/>\n1\n2\n3\n4\n5\n6\n7\n8\n设置完成后,我们来看下效果图:\n\n接着我们来绘制九宫格拼图,先设置第一行这三个小图片的外布局,依然是LinearLayout,设置它的id=“@+id/pt_line1”,就表示第一行。\n\norientation选择的是水平方向,因为每一行是水平放置的,layout_gravity设置为"center",表示居中,代码如下。\n\n\u003CLinearLayout\n android:id="@+id/pt_line1"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:orientation=“horizontal”\n android:layout_gravity=“center”>\n \n\u003C/LinearLayout>\n1\n2\n3\n4\n5\n6\n7\n8\n设置第一张图片,选择的控件是ImageButton,顾名思义:图片按钮,正常按钮就规规矩矩的,而图片按钮就很好看,一张图片也可以进行点击,这里设置它的id=“@+id/pt_ib_00x00”,方便在MainActivity里面调用。\n\n00x00不用我多说了吧,上面解释过了,将九宫格看成3X3的二维数组,那么行列下标就是0行0列,这里每行数和列数都用2位数字表示而已。\n\n设置src=“@mipmap/img_xiaoxiong_00x00”,就是将我们刚刚准备的图片资源复制到这个mipmap文件夹中进行引用,每个id编号和图片的名称是对应的。\n\n\n再设置个onClick方法,方法名为"onClick",我们后面会在MainActivity里面进行编写点击事件。第一张图片的代码如下:\n\n\u003CImageButton\n android:id="@+id/pt_ib_00x00"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:src="@mipmap/img_xiaoxiong_00x00"\n android:padding=“0dp”\n android:onClick=“onClick”/>\n1\n2\n3\n4\n5\n6\n7\n依次类推,第二张和第三张图片,我只要改下id和src就可以了,所以直接放上第一个小LinearLayout的代码:\n\n\u003CLinearLayout\n android:id="@+id/pt_line1"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:orientation=“horizontal”\n android:layout_gravity=“center”>\n \u003CImageButton\n android:id="@+id/pt_ib_00x00"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:src="@mipmap/img_xiaoxiong_00x00"\n android:padding=“0dp”\n android:onClick=“onClick”/>\n \u003CImageButton\n android:id="@+id/pt_ib_00x01"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:src="@mipmap/img_xiaoxiong_00x01"\n android:padding=“0dp”\n android:onClick=“onClick”/>\n \u003CImageButton\n android:id="@+id/pt_ib_00x02"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:src="@mipmap/img_xiaoxiong_00x02"\n android:padding=“0dp”\n android:onClick=“onClick”/>\n \u003C/LinearLayout>\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n来看下显示效果:\n\n那第二行和第三行是不是也一样照葫芦画瓢,没错,直接复制第一行的代码,然后修改id和src就行。这里直接给出三个LinearLayout的代码:\n\n\u003CLinearLayout\n android:id="@+id/pt_line1"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:orientation=“horizontal”\n android:layout_gravity=“center”>\n \u003CImageButton\n android:id="@+id/pt_ib_00x00"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:src="@mipmap/img_xiaoxiong_00x00"\n android:padding=“0dp”\n android:onClick=“onClick”/>\n \u003CImageButton\n android:id="@+id/pt_ib_00x01"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:src="@mipmap/img_xiaoxiong_00x01"\n android:padding=“0dp”\n android:onClick=“onClick”/>\n \u003CImageButton\n android:id="@+id/pt_ib_00x02"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:src="@mipmap/img_xiaoxiong_00x02"\n android:padding=“0dp”\n android:onClick=“onClick”/>\n \u003C/LinearLayout>\n\n \u003CLinearLayout\n android:id="@+id/pt_line2"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:orientation=“horizontal”\n android:layout_gravity=“center”>\n \u003CImageButton\n android:id="@+id/pt_ib_01x00"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:src="@mipmap/img_xiaoxiong_01x00"\n android:padding=“0dp”\n android:onClick=“onClick”/>\n \u003CImageButton\n android:id="@+id/pt_ib_01x01"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:src="@mipmap/img_xiaoxiong_01x01"\n android:padding=“0dp”\n android:onClick=“onClick”/>\n \u003CImageButton\n android:id="@+id/pt_ib_01x02"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:src="@mipmap/img_xiaoxiong_01x02"\n android:padding=“0dp”\n android:onClick=“onClick”/>\n \u003C/LinearLayout>\n\n \u003CLinearLayout\n android:id="@+id/pt_line3"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:orientation=“horizontal”\n android:layout_gravity=“center”>\n \u003CImageButton\n android:id="@+id/pt_ib_02x00"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:src="@mipmap/img_xiaoxiong_02x00"\n android:padding=“0dp”\n android:onClick=“onClick”/>\n \u003CImageButton\n android:id="@+id/pt_ib_02x01"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:src="@mipmap/img_xiaoxiong_02x01"\n android:padding=“0dp”\n android:onClick=“onClick”/>\n \u003CImageButton\n android:id="@+id/pt_ib_02x02"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:src="@mipmap/img_xiaoxiong_02x02"\n android:padding=“0dp”\n android:onClick=“onClick”\n android:visibility=“invisible”/>\n \u003C/LinearLayout>\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n58\n59\n60\n61\n62\n63\n64\n65\n66\n67\n68\n69\n70\n71\n72\n73\n74\n75\n76\n77\n78\n79\n80\n81\n82\n83\n84\n85\n86\n87\n有一点需要注意的,不知道有没有同学发现——第三行的第三张图片,也就是右下角的那张图片,它有个属性,其他的图片都没有:visibility=“invisible”,这是干什么的呢?\n\n这个其实就是设置控件是否可见,默认情况下控件都是可见的(visible),只有设置visibility=“invisible"后,这个控件才不显示出来,我们来看下整体效果:\n\nOK,九宫格完成后,下面是一个重新开始的Button。\n\n这个比较简单了,主要设置了onClick=“restart”,这个后面会在MainActivity里面编写重新开始游戏的逻辑,还设置了android:layout_marginTop=“20dp”,这是设置此控件与上面控件边距相隔20dp,为了和九宫格保持一定间距,代码如下:\n\n\u003CButton\n android:id=”@+id/pt_btn_restart"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:onClick=“restart”\n android:layout_gravity=“center”\n android:text=“重新开始”\n android:layout_marginTop=“20dp”/>\n1\n2\n3\n4\n5\n6\n7\n8\n显示效果:\n\n最后就是我们的样图了,有了我们上面的经验,这个应该很容易就画出来了,放置图片的控件我们一般使用ImageView,然后设置src=“@mipmap/yangtu”,就显示了我们的样图,最后为了保持距离美,设置layout_marginTop=“20dp”,代码如下:\n\n \u003CImageView\n android:id="@+id/pt_iv"\n android:layout_width=“wrap_content”\n android:layout_height=“wrap_content”\n android:layout_gravity=“center”\n android:src="@mipmap/yangtu"\n android:layout_marginTop=“20dp”/>\n1\n2\n3\n4\n5\n6\n7\n好了,我们来看下效果图:\n\n至此,我们的布局就绘制完成了!\n\n我们来编写下MainActivity的基本框架\n可以先来看下什么都没有的MainActivity。里面只有onClick()和restart()两个新的方法,这是在上面布局中设置的方法,onClick是图片按钮的点击事件,restart是重新开始按钮的点击事件,这两个方法的具体实现逻辑会在下面讲到。\npublic class MainActivity extends AppCompatActivity {\n\t@Override\n\tprotected void onCreate(Bundle savedInstanceState) {\n\t\t super.onCreate(savedInstanceState);\n\t\t// 设置要显示的视图\n\t\t setContentView(R.layout.activity_main);\n\t\t}\n\t// 图片按钮的点击事件\t \n\tpublic void onClick(View view) {\n\t\n\t}\n\t/* 重新开始按钮的点击事件*/\n public void restart(View view) {\n \n \t}\n}\t \t\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n这里我们要做的是把所有在布局中用到的控件定义好,然后初始化这些控件\n\n先来定义九个图片按钮,命名方法也是00,01这样的横纵坐标,一个重启按钮和一个显示时间的文本框\n\n// 定义九个图片按钮,命名方法也是00,01这样的横纵坐标\n ImageButton ib00,ib01,ib02,ib10,ib11,ib12,ib20,ib21,ib22;\n// 一个重启按钮\n Button restartBtn;\n// 一个显示时间的文本框\n TextView timeTv;\n1\n2\n3\n4\n5\n6\n然后我们在onCreate中定义一个initView()方法,这个方法是用来初始化控件的\n\n// 初始化layout控件的方法\n initView();\n1\n2\n然后创建该方法,在该方法里面初始化定义的控件,通过findViewById()进行绑定控件,将声明的变量和layout中对应的控件进行绑定,实现引用的效果,代码如下:\n\n/* 初始化控件:绑定9个图片按钮,1个显示时间的文本框,1个重启按钮*/\n private void initView() {\n ib00 = findViewById(R.id.pt_ib_00x00);\n ib01 = findViewById(R.id.pt_ib_00x01);\n ib02 = findViewById(R.id.pt_ib_00x02);\n ib10 = findViewById(R.id.pt_ib_01x00);\n ib11 = findViewById(R.id.pt_ib_01x01);\n ib12 = findViewById(R.id.pt_ib_01x02);\n ib20 = findViewById(R.id.pt_ib_02x00);\n ib21 = findViewById(R.id.pt_ib_02x01);\n ib22 = findViewById(R.id.pt_ib_02x02);\n timeTv = findViewById(R.id.pt_tv_time);\n restartBtn = findViewById(R.id.pt_btn_restart);\n }\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n初始化的完整代码,可以作为模板:\n\npublic class MainActivity extends AppCompatActivity {\n// 定义九个图片按钮,命名方法也是00,01这样的横纵坐标\n ImageButton ib00,ib01,ib02,ib10,ib11,ib12,ib20,ib21,ib22;\n// 一个重启按钮\n Button restartBtn;\n// 一个显示时间的文本框\n TextView timeTv;\n\t@Override\n\tprotected void onCreate(Bundle savedInstanceState) {\n\t\t super.onCreate(savedInstanceState);\n\t\t// 设置要显示的视图\n\t\t setContentView(R.layout.activity_main);\n\t\t initView();\n\t\t}\n\tprivate void initView() {\n ib00 = findViewById(R.id.pt_ib_00x00);\n ib01 = findViewById(R.id.pt_ib_00x01);\n ib02 = findViewById(R.id.pt_ib_00x02);\n ib10 = findViewById(R.id.pt_ib_01x00);\n ib11 = findViewById(R.id.pt_ib_01x01);\n ib12 = findViewById(R.id.pt_ib_01x02);\n ib20 = findViewById(R.id.pt_ib_02x00);\n ib21 = findViewById(R.id.pt_ib_02x01);\n ib22 = findViewById(R.id.pt_ib_02x02);\n timeTv = findViewById(R.id.pt_tv_time);\n restartBtn = findViewById(R.id.pt_btn_restart);\n }\n\t// 图片按钮的点击事件\t \n\tpublic void onClick(View view) {\n\t\n\t}\n\t/* 重新开始按钮的点击事件*/\n public void restart(View view) {\n \n \t}\n}\t \t\n\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n2、拼图游戏时间计时\n完成基本工作后,我们思考下——如何实现时间的计时操作,这就相当于计时器的功能。这里我们可以用Handler消息机制来实现,补充下知识点:\n\nHandler:作用就是发送与处理信息\nMessage:Handler接收与处理的消息对象\n当我们的子线程想修改Activity中的UI组件时,我们可以新建一个Handler对象,通过这个对象向主线程发送信息;而我们发送的信息会先到主线程的MessageQueue进行等待,由Looper按先入先出顺序取出,再根据message对象的what属性分发给对应的Handler进行处理!\n\n简单来说:Handler就是用来发送消息和处理消息的一种机制,上面这段话可能听起来有些懵,不过没关系,其实没有这么深奥,下面会让大家明白怎么使用它来实现计时的。\n\n先定义个时间变量,初值为0,因为从0开始计时\n\n// 定义计数时间的变量\n int time = 0;\n1\n2\n然后定义发送和处理消息的对象handler,我们来重写handleMessage方法,在方法里面我们进行了if判断,如果这条消息的what值为1,那么时间time就+1,然后timeTv显示时间为time秒,然后继续向自己发送消息。\n\nhandler.sendEmptyMessageDelayed(1,1000)这句话的意思就是:延时1000毫秒后发送参数what为1的空信息,这样它自己就能循环接收自己发的消息,实现计时的功能了,就这么简单。\n\n当然最开始要发送它一条消息,让它这个方法运转起来,我们在onCreate这个方法里面加上了一条\nhandler.sendEmptyMessageDelayed(1,1000); 这样在游戏一开始过了1s,handler就发送了一条what为1的空消息。然后它自己又立马接收到了,进行时间加1,又自己发送给自己消息,实现计时!\n\n这是定义的handler的代码:\n\n// 定义发送和处理消息的对象handler\n Handler handler = new Handler(){\n @Override\n// 重写handleMessage方法,根据msg中what的值判断是否执行后续操作\n public void handleMessage(Message msg) {\n if (msg.what1) {\n time++;\n timeTv.setText(“时间 : “+time+” 秒”);\n// 指定延时1000毫秒后发送参数what为1的空信息\n handler.sendEmptyMessageDelayed(1,1000);\n }\n\n }\n };\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n这是在onCreate方法里面定义的一条消息\n\nhandler.sendEmptyMessageDelayed(1,1000);\n1\n我们来看下运行效果:\n\n除此之外,我们还需要在重新开始游戏后进行重新计时,这里又要怎么实现呢?\n\n这里我们只需要在restart方法里面先停止handler的消息发送,保证时间不会再继续+1了,然后将时间重新归0,显示当前时间,最后每隔1s发送参数what为1的消息msg,这样就实现了重新开始计时,代码如下:\n\n/* 重新开始按钮的点击事件*/\n public void restart(View view) {\n// 停止handler的消息发送\n handler.removeMessages(1);\n// 将时间重新归0,并且重新开始计时\n time = 0;\n timeTv.setText(“时间 : “+time+” 秒”);\n// 每隔1s发送参数what为1的消息msg\n handler.sendEmptyMessageDelayed(1,1000);\n }\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n点击重新开始后的实现效果:\n\n至此,我们的计时功能就实现了!\n\n3、拼图游戏打乱显示\n首先定义一个image数组,里面存放每张碎片(九宫格图片)的id,int型数组是可以存放图片的id的,但是不能存放图片,注意这个区别。\n\n// 将每张碎片的id存放到数组中,便于进行统一的管理,int型数组存放的肯定是int型变量\n private int[]image = {R.mipmap.img_xiaoxiong_00x00,R.mipmap.img_xiaoxiong_00x01,R.mipmap.img_xiaoxiong_00x02,\n R.mipmap.img_xiaoxiong_01x00,R.mipmap.img_xiaoxiong_01x01,R.mipmap.img_xiaoxiong_01x02,\n R.mipmap.img_xiaoxiong_02x00,R.mipmap.img_xiaoxiong_02x01,R.mipmap.img_xiaoxiong_02x02};\n1\n2\n3\n4\n再声明一个imageIndex数组,它来存放上面图片数组的下标,一共九张图片,所以下标为0-8,它存储的也就是0-8。我们为了让上面九张图片被打乱,所以,这里的下标等下会被打乱。\n\n// 声明上面图片数组下标的数组,随机排列这个数组,九张图片,下标为0-8\n private int[]imageIndex = new int[image.length];\n1\n2\n|下面我们写一个函数disruptRandom( ),来实现进入游戏拼图就打乱显示的效果|\n|-------------------------------------------|–|\n\n先给下标数组每个元素赋值,下标是i,值就为i,就是imageIndex[i] = i。\n\n// 给下标数组每个元素赋值,下标是i,值就为i\n for (int i = 0; i \u003C imageIndex.length; i++) {\n imageIndex[i] = i;\n }\n1\n2\n3\n4\n然后进行20次for循环,随机选择两个角标对应的值进行交换。先定义两个角标rand1和rand2,\nrand1 = (int)(Math.random()(imageIndex.length-1));这里我来重点解释一下:\nMath.random()产生的随机数为0~1之间的小数 此处说的0~1是包含左不包含右,即包含0不包含1!\n\nps:我在这里卡了2h至少,因为这个小细节点没注意到,所以一定不能想当然,要查资料以求准确。\n\nMath.random()的值域为[0,1),然后imageIndex.length-1就是8其实,8那就是[0,8),再int取整最终值域为{0,1,2,3,4,5,6,7},因为int取整只会取整数位,不会四舍五入!\n\n再用do-while循环实现了rand2的生成,之所以在do-while里面生成rand2,是为了判断二次生成的角标和第一次是否相同,不同则break立刻跳出循环,执行swap交换;若第二次生成的与第一次相同,则重新进入do-while循环生成rand2,这部分代码如下:\n\n// 规定20次,随机选择两个角标对应的值进行交换\n int rand1,rand2;\n for (int j = 0; j \u003C 20; j++) {\n// 随机生成第一个角标\n// Math.random()产生的随机数为0~1之间的小数 此处说的0~1是包含左不包含右,即包含0不包含1\n// Math.random()的值域为[0,1),然后8就是[0,8),再int取整最终值域为{0,1,2,3,4,5,6,7}\n rand1 = (int)(Math.random()(imageIndex.length-1));\n// 第二次随机生成的角标,不能和第一次随机生成的角标相同,如果相同,就不方便交换了\n do {\n rand2 = (int)(Math.random()(imageIndex.length-1));\n// 判断第一次和第二次生成的角标是否相同,不同则break立刻跳出循环,执行swap交换\n if (rand1!=rand2) {\n break;\n }\n// 若第二次生成的与第一次相同,则重新进入do-while循环生成rand2\n }while (true);\n swap(rand1,rand2);\n }\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n这里的swap方法很简单,就是交换两个数的值,只不过这里参数是数组的下标:\n\n// 交换数组指定角标(0-7这八个自然数)上的数据\n private void swap(int rand1, int rand2) {\n int temp = imageIndex[rand1];\n imageIndex[rand1] = imageIndex[rand2];\n imageIndex[rand2] = temp;\n }\n1\n2\n3\n4\n5\n6\n这里有个整个游戏的一个核心点:我们打乱的拼图下标是{0,1,2,3,4,5,6,7}这八个,第九张拼图的下标是不参与打乱的,有同学问为什么?是因为第九张图片是不显示出来的,而且不会参与到拼图中,所以我们是将第九个图片按钮就设置成第九张图片,然后invisible。\n\n最后我们将每个图片按钮设置图片,这时候 imageIndex[i]就是被打乱的下标,有可能是这样的顺序:{2,6,5,4,1,7,0,3,8},也有可能是这样的顺序{1,3,0,5,2,7,4,6,8}等等,不管怎么样, imageIndex[8]一直是8,上面解释过。代码如下:\n\n// ib00是绑定的第一块图片按钮,设置图片资源,\n// imageIndex[i]就是被打乱的下标,然后image[x]就表示对应下标为x的图片的id\n ib00.setImageResource(image[imageIndex[0]]);\n ib01.setImageResource(image[imageIndex[1]]);\n ib02.setImageResource(image[imageIndex[2]]);\n ib10.setImageResource(image[imageIndex[3]]);\n ib11.setImageResource(image[imageIndex[4]]);\n ib12.setImageResource(image[imageIndex[5]]);\n ib20.setImageResource(image[imageIndex[6]]);\n ib21.setImageResource(image[imageIndex[7]]);\n ib22.setImageResource(image[imageIndex[8]]);\n\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n综上,disruptRandom()的整体逻辑代码如下:\n\n// 随机打乱数组当中元素,以不规则的形式进行图片显示\n private void disruptRandom() {\n// 给下标数组每个元素赋值,下标是i,值就为i\n for (int i = 0; i \u003C imageIndex.length; i++) {\n imageIndex[i] = i;\n }\n// 规定20次,随机选择两个角标对应的值进行交换\n int rand1,rand2;\n for (int j = 0; j \u003C 20; j++) {\n// 随机生成第一个角标\n// Math.random()产生的随机数为0~1之间的小数 此处说的0~1是包含左不包含右,即包含0不包含1\n// Math.random()的值域为[0,1),然后8就是[0,8),再int取整最终值域为{0,1,2,3,4,5,6,7}\n rand1 = (int)(Math.random()(imageIndex.length-1));\n// 第二次随机生成的角标,不能和第一次随机生成的角标相同,如果相同,就不方便交换了\n do {\n rand2 = (int)(Math.random()(imageIndex.length-1));\n// 判断第一次和第二次生成的角标是否相同,不同则break立刻跳出循环,执行swap交换\n if (rand1!=rand2) {\n break;\n }\n// 若第二次生成的与第一次相同,则重新进入do-while循环生成rand2\n }while (true);\n// 交换两个角标上对应的值\n swap(rand1,rand2);\n }\n// 随机排列到指定的控件上\n// ib00是绑定的第一块图片按钮,设置图片资源,imageIndex[i]就是被打乱的图片数组下标,然后image[x]就表示对应下标为x的图片的id\n ib00.setImageResource(image[imageIndex[0]]);\n ib01.setImageResource(image[imageIndex[1]]);\n ib02.setImageResource(image[imageIndex[2]]);\n ib10.setImageResource(image[imageIndex[3]]);\n ib11.setImageResource(image[imageIndex[4]]);\n ib12.setImageResource(image[imageIndex[5]]);\n ib20.setImageResource(image[imageIndex[6]]);\n ib21.setImageResource(image[imageIndex[7]]);\n ib22.setImageResource(image[imageIndex[8]]);\n\n }\n\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n实现效果:\n\n\n4、拼图游戏碎片位置切换\n我们完成乱序后,这时候拼图碎片还不能移动,所以我们要设置点击事件,来移动拼图。\n\n拼图移动的规则也要注意一下:只有和空白区域在同一行或者同一列相邻的拼图才能移动,只要知道了这个逻辑,实现起来就不难了。\n|我们来编写九个图片按钮的onClick()方法 |\n|–|–|\n这里因为九个id不同的imagebutton点击事件的逻辑相同,所以我们使用switch 语句来编写,根据它们的id来执行移动,按照从左到右、从上到下的顺序进行了case设置。移动我们定义了move()函数,将它单独封装成了一个方法,下面就会讲到。点击事件的代码如下:\n\n public void onClick(View view) {\n int id = view.getId();\n// 九个按钮执行的点击事件的逻辑应该是相同的,如果有空格在周围,可以改变图片显示的位置,否则点击事件不响应\n switch (id) {\n case R.id.pt_ib_00x00:\n move(R.id.pt_ib_00x00,0);\n break;\n case R.id.pt_ib_00x01:\n move(R.id.pt_ib_00x01,1);\n break;\n case R.id.pt_ib_00x02:\n move(R.id.pt_ib_00x02,2);\n break;\n case R.id.pt_ib_01x00:\n move(R.id.pt_ib_01x00,3);\n break;\n case R.id.pt_ib_01x01:\n move(R.id.pt_ib_01x01,4);\n break;\n case R.id.pt_ib_01x02:\n move(R.id.pt_ib_01x02,5);\n break;\n case R.id.pt_ib_02x00:\n move(R.id.pt_ib_02x00,6);\n break;\n case R.id.pt_ib_02x01:\n move(R.id.pt_ib_02x01,7);\n break;\n case R.id.pt_ib_02x02:\n move(R.id.pt_ib_02x02,8);\n break;\n }\n }\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n|我们来编写九个图片按钮的move()方法 |\n|–|–|\n先定义变量,imageX是每行的图片个数,imageY是每列的图片个数,imgCount是图片的总数目,也就是9个。blankSwap是空白区域的位置,就是8,这里的位置我们还是按照从左到右、从上到下的顺序排列的,第一张图片的位置是0,对照九宫格应该理解了吧。\n\nblankImgid就是空白区域的按钮id,我们这里直接固定了R.id.pt_ib_02x02,就是第九个图片按钮,它一直是空白区域!\n\n// 每行的图片个数\n private int imageX = 3;\n// 每列的图片个数\n private int imageY = 3;\n\n// 图片的总数目\n private int imgCount = imageX*imageY;\n// 空白区域的位置\n private int blankSwap = imgCount-1;\n// 初始化空白区域的按钮id\n private int blankImgid = R.id.pt_ib_02x02;\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n定义完要用到的变量,我们来写move方法,这里我每句都写上了注释,这里就不再赘述了。\n强调几点:\n\n可以移动的条件有两个:\n1.在同一行,列数相减,绝对值为1,可移动\n2.在同一列,行数相减,绝对值为1,可以移动\n两个参数: imagebuttonId是被选中的图片的id,site是该图片在9宫格的位置(0-8)\n将移动后的图片按钮设为不可见的,即显示为空白区域\n移动之前是不可见的,移动之后将图标按钮设置为可见\n进行移动后将改变角标的过程记录到存储图片位置的数组当中\n /表示移动指定位置的按钮的函数,将图片和空白区域进行交换/\n //imagebuttonId是被选中的图片的id,site是该图片在9宫格的位置(0-8)\n private void move(int imagebuttonId, int site) {\n// 判断选中的图片在第几行,imageX为3,所以进行取整运算\n int sitex = site / imageX;\n// 判断选中的图片在第几列,imageY为3,所以进行取模运算\n int sitey = site % imageY;\n// 获取空白区域的坐标,blankx为行坐标,blanky为列坐标\n int blankx = blankSwap / imageX;\n int blanky = blankSwap % imageY;\n// 可以移动的条件有两个\n// 1.在同一行,列数相减,绝对值为1,可移动 2.在同一列,行数相减,绝对值为1,可以移动\n int x = Math.abs(sitex-blankx);\n int y = Math.abs(sitey-blanky);\n if ((x0&&y1)||(y0&&x==1)){\n// 通过id,查找到这个可以移动的按钮\n ImageButton clickButton = findViewById(imagebuttonId);\n// 将这个选中的图片设为不可见的,即显示为空白区域\n clickButton.setVisibility(View.INVISIBLE);\n// 查找到空白区域的按钮\n ImageButton blankButton = findViewById(blankImgid);\n// 将空白区域的按钮设置为图片,image[imageIndex[site]就是刚刚选中的图片,因为这在上面disruptRandom()设置过\n blankButton.setImageResource(image[imageIndex[site]]);\n// 移动之前是不可见的,移动之后将控件设置为可见\n blankButton.setVisibility(View.VISIBLE);\n// 将改变角标的过程记录到存储图片位置的数组当中\n swap(site,blankSwap);\n// 新的空白区域位置更新等于传入的点击按钮的位置\n blankSwap = site;\n// 新的空白图片id更新等于传入的点击按钮的id\n blankImgid = imagebuttonId;\n }\n\n }\n\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n运行效果:\n\n\n\n\n5、拼图游戏成功的条件\n上面我们已经实现了拼图碎片进行移动的效果,但是并没有拼图游戏成功的效果和提示,所以,我们要在刚刚的move方法的最后加上一个判断的方法judgeGameOver();顾名思义:判断游戏结束。\n|我们来实现一下判断游戏结束的逻辑 |\n|–|–|\n在方法里面先定义一个loop标志位,然后要遍历下标数组,判断是否它的imageIndex[i]==i,就是说所有拼图的下标全部对应正确的位置。比如:第1张图片的下标是0,imageIndex[0]的值也是0,显示第一张图片。所有图片都满足,也就是说此时拼图成功。如果一个不满足,则未成功,所有loop置为false,继续判断。\n\n\tboolean loop = true; //定义标志位loop\n for (int i = 0; i \u003C imageIndex.length; i++) {\n if (imageIndex[i]!=i) {\n loop = false;\n break;\n }\n }\n1\n2\n3\n4\n5\n6\n7\n如果拼图成功了,则handler.removeMessages(1)进行停止计时,\n而且设置ib00.setClickable(false)禁止玩家继续移动按钮,\n还有就是第九块空白区域显示出图片,即下标为8的第九张拼图。\n\n if (loop) {\n// 拼图成功了\n// 停止计时\n handler.removeMessages(1);\n// 拼图成功后,禁止玩家继续移动按钮\n ib00.setClickable(false);\n ib01.setClickable(false);\n ib02.setClickable(false);\n ib10.setClickable(false);\n ib11.setClickable(false);\n ib12.setClickable(false);\n ib20.setClickable(false);\n ib21.setClickable(false);\n ib22.setClickable(false);\n// 拼图成功后,第九块空白显示出图片,即下标为8的第九张图片\n ib22.setImageResource(image[8]);\n ib22.setVisibility(View.VISIBLE);\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n|我们再来实现一下游戏结束时的对话框 |\n|–|–|\n对话框要用到AlertDialog.Builder对象,它的使用就是固定套路,我来补充知识点:\n\n第一步:创建AlertDialog.Builder对象\n第二步:设置对话框的内容:setMessage()方法来指定显示的内容\n第三步:调用setPositive/Negative/NeutralButton()设置:确定,取消,中立按钮\n第四歩:调用create()方法创建这个对象\n第五歩:调用show()方法来显示我们的AlertDialog对话框\n非常简单,按照上面的流程,我们来设置下对话框:\n\n// 弹出提示用户成功的对话框,并且设置确实的按钮\n\n// 第一步:创建AlertDialog.Builder对象\n AlertDialog.Builder builder = new AlertDialog.Builder(this);\n// 调用setIcon()设置图标,setTitle()或setCustomTitle()设置标题\n// 第二步:设置对话框的内容:setMessage()方法来指定显示的内容\n builder.setMessage(“恭喜,拼图成功!您用的时间为”+time+“秒”)\n// 第三步:调用setPositive/Negative/NeutralButton()设置:确定,取消,中立按钮\n .setPositiveButton(“确认”,null);\n// 第四歩:调用create()方法创建这个对象\n AlertDialog dialog = builder.create();\n// 第五歩:调用show()方法来显示我们的AlertDialog对话框\n dialog.show();\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n实现效果:\n\n\n6、拼图游戏重新开始\n我们在上面实现了拼图游戏成功的条件和提示了,现在到了最后一步——如何让游戏重新开始?\n\n我们来看下拼图成功后,点击重新开始,目前只能重新计时,拼图并没有打乱,而且第九块还没有隐藏,所以,接下来我们的思路很明确,在重新开始的restart方法中编写打乱和隐藏图片的逻辑。\n\n|我们来实现重新开始游戏时的按钮状态还原 |\n|–|–|\n首先,这些按钮已经被设置成不可点击了,所以我们先要将它们设置为可以点击,就是设置ib00.setClickable(true),因为这部分代码都是一样的,所以我们将它单独封装成一个restore方法。\n\n另外,还要还原被点击的图片按钮变成初始化的模样, ImageButton clickBtn = findViewById(blankImgid)其实就是绑定最后一次被隐藏的那块拼图,然后clickBtn.setVisibility(View.VISIBLE)将它显示出来。ImageButton blankBtn = findViewById(R.id.pt_ib_02x02)就是绑定的第九块拼图,blankBtn.setVisibility(View.INVISIBLE)设置为不可见。最后blankImgid = R.id.pt_ib_02x02来初始化空白区域的按钮id。\n\nrestore()的代码如下:\n\n// 状态还原函数,我们把它封装起来\n private void restore() {\n // 拼图游戏重新开始,允许移动碎片按钮\n ib00.setClickable(true);\n ib01.setClickable(true);\n ib02.setClickable(true);\n ib10.setClickable(true);\n ib11.setClickable(true);\n ib12.setClickable(true);\n ib20.setClickable(true);\n ib21.setClickable(true);\n ib22.setClickable(true);\n// 还原被点击的图片按钮变成初始化的模样\n ImageButton clickBtn = findViewById(blankImgid);\n clickBtn.setVisibility(View.VISIBLE);\n// 默认隐藏第九张图片\n ImageButton blankBtn = findViewById(R.id.pt_ib_02x02);\n blankBtn.setVisibility(View.INVISIBLE);\n// 初始化空白区域的按钮id\n blankImgid = R.id.pt_ib_02x02;\n blankSwap = imgCount - 1;\n }\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n|最后,我们在restart()中实现重新开始的逻辑|\n|–|–|\n\n将状态还原 将拼图重新打乱\n停止handler的消息发送\n将时间重新归0,并且重新开始计时\n每隔1s发送参数what为1的消息msg\n /* 重新开始按钮的点击事件*/\n public void restart(View view) {\n// 将状态还原\n restore();\n// 将拼图重新打乱\n disruptRandom();\n// 停止handler的消息发送\n handler.removeMessages(1);\n// 将时间重新归0,并且重新开始计时\n time = 0;\n timeTv.setText(“时间 : “+time+” 秒”);\n// 每隔1s发送参数what为1的消息msg\n handler.sendEmptyMessageDelayed(1,1000);\n }\n\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n重新开始游戏后的效果:\n\n至此,拼图游戏的所有功能已经实现完毕,我先休息下,手腕已经打酸了。如果你看到这里,我真的很欣慰,说明你是个很有耐心而且热爱Android的学生,有热情有耐心,再困难的东西都可以学会。\n\n五、运行效果\n\nAndroid Studio实现拼图游戏\n\n六、项目总结\n这次实现的拼图游戏,说它简单,其实它实现起来也并不是那么简单,还是会有很多比较难的逻辑点,需要思考才能写出来;说它难,其实也不算难,比起来我前面发的那些项目【天气预报】、【饮食搭配】来说逻辑实现还是比较简单的,毕竟它只有一个MainActivity和一个layout。所以,说一个项目的难易得看你选的参照物了。\n\n这篇文章一共25000多个字,820行,我写这篇文章,不连上写代码时间,前后一共11个小时,前面构思和注释了4个小时,然后具体写了7个小时,中间只有喝水and上厕所。可以说我完全是按照开发这款拼图游戏的逻辑顺序来写下这篇教程。就是我们平时怎么开发Android项目,这篇博客就是怎么写的。\n\n我之所以写的这么详细,也是因为现在网上缺少一个从头到尾讲实现过程的Android项目的教程,因为这实在太花时间了,我深有体会,极少有人一步一步地去把实现过程写出来,但是我还是决定写下这篇教程,为了让更多的人喜欢上Android,让更多的人对Android不再陌生,让小白们不再望而却步,让小白们有个很好的实现案例,这是我的想法。\n\n当然,我也是正在学习Android的选手之一,才疏学浅,知识浅薄,文章中难免会有纰漏和错误,还希望大佬们批评指正。\n\n七、项目源码\n这次的拼图游戏项目是一个非常好的Android实现案例,涉及到很多常用的控件和知识点,希望大家拿到源码后,能对照着教程和注释好好学习掌握。\n\n源码几乎每条语句我都加上了注释,这么良心的博主,点个三连支持下吧,源码就送你啦,祝大家身体健康,学习愉快~

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/264689
推荐阅读
相关标签
  

闽ICP备14008679号