【木棉花】学习笔记--分布式数字华容道(中) 原创 精华
前言
嘿嘿,我又来啦。看过我的学习笔记–分布式数字华容道(上)的同学,有没有感觉到自己写一个游戏不再是那么可望而不可及的事,是不是感觉自己写一个游戏的难度不再是不可想象的了。那我们上一篇学习笔记就是给这个分布式数字华容道写了一个主要的框架,创建了一些页面还有完成一些前期的工作。那我们今天就在上一篇学习笔记的基础之上,再继续编写个人游戏的游戏界面。也请下载学习笔记–分布式数字华容道(上)完之后跟着我一步一步来编写我们的游戏吧。
那为了大家更好地熟练掌握鸿蒙手机应用开发,为了供大家更方便的学习鸿蒙手机的应用开发,我会将所有的笔记都整理到Awesome-HarmonyOS_木棉花,更多详细学习资料请查看Awesome-HarmonyOS_木棉花。也请关注我们这个项目,我们会持续更新的,会帮大家把资料都整理好更便于大家的学习。
那现在就先开始我们这星期的西之旅吧!
正文
那我们这个单人模式除了游戏功能还需要包括什么功能呢,那暂停开始键是必不可少的吧,那完成之后想继续玩的话是不是就需要一个重新开始的功能,那肯定是还包括一个返回按钮的吧,那既然是分布式游戏,是不是就需要整点和分布式相关的功能,没错我们还要实现一个游戏的迁移功能,还有一个计时和保存记录的功能。咋一看有没有感觉这功能有点多,不知道从何下手呀,没事我们一步一步来。
游戏功能
那我们最开始就需要来完成我们的游戏功能了,这是后面那些功能的基础吧。
我们一开始是需要先画一个画布出来,把画布的背景设置成赤红色的,如果再根据传输的阶数的值来画出方格来。那我们就先是需要定义一些变量了。
private static int single;
private DirectionalLayout layout;
private static int length;
private static final int interval = 5;
private static final int left = 32;
private static final int top = 300;
private static final int margin = 15;
private static int[][] grids;
private float startX;
private float startY;
private static int row_0;
private static int column_0;
private static final HiLogLabel TAG = new HiLogLabel(HiLog.LOG_APP, 0x12345, "signal");
接着我们就需要写一个初始化的函数,用于初始化我们的二维数组,还有打乱二维数组中的值,那我这里就是产生一个随机数,0、1、2、3分别代表向上、下、左、右四个不同的方向打乱。那我们就先来完成这个打乱的函数吧。
public void createGrids() {
int i = 0;
while(i < single * single * 6){
int random = (int)Math.floor(Math.random() *4 );
int temp_row = row_0;
int tem_column = column_0;
if(random == 0){
changeGrids(row_0 - 1, column_0);
}else if(random == 1){
changeGrids(row_0 + 1, column_0);
}else if(random == 2){
changeGrids(row_0, column_0 - 1);
}else if(random == 3){
changeGrids(row_0, column_0 + 1);
}
if(temp_row != row_0 || tem_column != column_0){
i++;
}
}
}
那在这个打乱函数中就涉及根据那个随机数来交换数组的内容,那交换的函数代码如下:
public void changeGrids(int i, int j) {
int temp;
if(0 <= i && i <= single - 1 && 0 <= j && j <= single - 1 ){
if((j - 1 == column_0 && i == row_0) || (j + 1 == column_0 && i == row_0) || (i - 1 == row_0 && j == column_0) || (i + 1 == row_0 && j == column_0)){
temp = grids[i][j];
grids[i][j] = grids[row_0][column_0];
grids[row_0][column_0] = temp;
row_0 = i;
column_0 = j;
}
}
}
那接下来就是写一个根据这个数组的内容在画布上把这些数字画出来了。
public void drawGrids() {
layout.setLayoutConfig((new ComponentContainer.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_PARENT)));
Component.DrawTask task = new Component.DrawTask() {
@Override
public void onDraw(Component component, Canvas canvas) {
Paint paint = new Paint();
Color backcolor = new Color(Color.rgb(151,75,49));
paint.setColor(backcolor);
RectFloat rect = new RectFloat(left - margin, top - margin, length * single + interval * (single - 1) + left + margin, length * single + interval * (single - 1) + top + margin);
canvas.drawRect(rect, paint);
for (int row = 0; row < single; row++) {
for (int column = 0; column < single; column++) {
Color backgroundcolor = new Color(Color.rgb(229,188,132));
if (grids[row][column] != 0) {
paint.setColor(backgroundcolor);
RectFloat rectFloat = new RectFloat(left + column * (length + interval), top + row * (length + interval), left + length + column * (length + interval), top + length + row * (length + interval));
canvas.drawRect(rectFloat, paint);
}
Color numbercolor = new Color(Color.rgb(140,85,47));
paint.setColor(numbercolor);
paint.setTextSize(length / 2);
if(grids[row][column] != 0){
if(grids[row][column] < 10){
canvas.drawText(paint,Integer.toString(grids[row][column]),left + column * (length + interval) + length / 12 * 5,top + row * (length + interval) + length / 3 * 2);
}else{
canvas.drawText(paint,Integer.toString(grids[row][column]),left + column * (length + interval) + length / 12 * 3,top + row * (length + interval) + length / 3 * 2);
}
}
}
}
}
};
layout.addDrawTask(task);
setUIContent(layout);
}
然后写完这几个函数,我们就在初始化函数中给数组开空间和设置画布中每个小方格的长度,并且调用这两个打乱还有画出数字的函数。
private void initialize() {
initDbManager();
if(!isMigrate) {
grids = new int[single][single];
getrecord();
for (int row = 0; row < single; row++) {
for (int column = 0; column < single; column++) {
grids[row][column] = single * row + column + 1;
}
}
grids[single - 1][single - 1] = 0;
row_0 = single - 1;
column_0 = single - 1;
hour = 0;
min = 0;
sec = 0;
msec = 0;
}
layout = new DirectionalLayout(this);
length = 1020 / single - interval;
if (!isMigrate)
createGrids();
drawGrids();
draw();
swipeGrids();
isMigrate = false;
}
然后只要在onStart中调用这这个初始化函数就OK啦,那接下来就运行一下来查看我们的成果吧。
那接下来我们就把按钮和时间给加上去,那还是一样的,先定义时间还有按钮的那些变量。
private static Text timetext;
private static Text maxtext;
private static int hour = 0;
private static int min = 0;
private static int sec = 0;
private static int msec = 0;
private static int maxhour = 23;
private static int maxmin = 59;
private static int maxsec = 59;
private static int maxmsec = 99;
private static String text = "暂停";
private static Button button_moveback;
private static String strhour = "";
private static String strmin = "";
private static String strsec = "";
private static String strmsec = "";
private static Timer timer;
private static final String str = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`a";
private static boolean isMigrate = false;
然后为了让时间显示得更加好看,我们就让一位数的时间前面多加一个0,那我们就来写一个settime的函数吧。
private void settime(int msec, int sec, int min, int hour) {
if (msec < 10) {
strmsec = "0" + msec;
} else if (msec >= 10) {
strmsec = Integer.toString(msec);
}
if (sec < 10){
strsec = "0" + sec;
} else if (sec >= 10) {
strsec = Integer.toString(sec);
}
if (min < 10){
strmin = "0" + min;
} else if (min >= 10) {
strmin = Integer.toString(min);
}
if (hour < 10){
strhour = "0" + hour;
} else if (hour >= 10) {
strhour = Integer.toString(hour);
}
}
然后再写一个函数可以把那些按钮和时间给显示出来。
public void draw(){
maxtext = new Text(this);
settime(maxmsec, maxsec, maxmin, maxhour);
maxtext.setText("最短时间: "+ strhour + ":" + strmin + ":" + strsec + ":" + strmsec);
maxtext.setTextSize(80);
maxtext.setMarginTop(40);
maxtext.setMarginLeft(140);
layout.addComponent(maxtext);
timetext = new Text(this);
timetext.setText("time: " + strhour + ":" + strmin + ":" + strsec + ":" + strmsec);
timetext.setTextSize(80);
timetext.setMarginTop(10);
timetext.setMarginLeft(230);
layout.addComponent(timetext);
ShapeElement background = new ShapeElement();
background.setRgbColor(new RgbColor(138, 70, 50));
background.setCornerRadius(100);
Button button_again = new Button(this);
button_again.setText("重新开始");
button_again.setTextAlignment(TextAlignment.CENTER);
button_again.setTextColor(Color.WHITE);
button_again.setTextSize(100);
button_again.setMarginTop(1200);
button_again.setMarginLeft(160);
button_again.setPadding(30, 0, 30, 0);
button_again.setBackground(background);
layout.addComponent(button_again);
Button button_back = new Button(this);
button_back.setText("返回");
button_back.setTextAlignment(TextAlignment.CENTER);
button_back.setTextColor(Color.WHITE);
button_back.setTextSize(100);
button_back.setMarginLeft(700);
button_back.setMarginTop(-130);
button_back.setPadding(30, 0, 30, 0);
button_back.setBackground(background);
layout.addComponent(button_back);
Button button_migrate = new Button(this);
button_migrate.setText("迁移");
button_migrate.setTextAlignment(TextAlignment.CENTER);
button_migrate.setTextColor(Color.WHITE);
button_migrate.setTextSize(100);
button_migrate.setMarginLeft(260);
button_migrate.setMarginTop(100);
button_migrate.setPadding(30, 0, 30, 0);
button_migrate.setBackground(background);
layout.addComponent(button_migrate);
button_moveback = new Button(this);
button_moveback.setText(text);
button_moveback.setTextAlignment(TextAlignment.CENTER);
button_moveback.setTextColor(Color.WHITE);
button_moveback.setTextSize(100);
button_moveback.setMarginLeft(700);
button_moveback.setMarginTop(-130);
button_moveback.setPadding(30, 0, 30, 0);
button_moveback.setBackground(background);
layout.addComponent(button_moveback);
setUIContent(layout);
}
那接下去就是在初始化函数中调用我们的刚刚完成的draw函数。那这部分的工作就完成了,来运行一下查看效果吧。
那我们接下去就来真正的实现游戏功能吧,就是点击数字,然后实现数组的相应数据的交换。
那在完成这个之前,就需要先完成一个判断是否游戏成功的函数了。
public boolean gamesuccess() {
int[][] Grids = new int[single][single];
for (int row = 0; row < single; row++){
for (int column = 0; column < single; column++){
Grids[row][column] = single * row + column + 1;
}
}
Grids[single - 1][single - 1] = 0;
for (int row = 0; row < single; row++){
for (int column = 0; column < single; column++){
if(grids[row][column] != Grids[row][column]){
return false;
}
}
}
return true;
}
那我们点击的时候要是已经游戏成功了或者是暂停了,是不可以改变数据的,所以我们在交换数据之前需要判断是否已经游戏成功或者是已经暂停。
public void swipeGrids(){
layout.setTouchEventListener(new Component.TouchEventListener() {
@Override
public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
MmiPoint point = touchEvent.getPointerScreenPosition(0);
switch (touchEvent.getAction()) {
case TouchEvent.PRIMARY_POINT_DOWN:
startX = point.getX();
startY = point.getY();
if (!gamesuccess() && text.equals("暂停")) {
changeGrids((int)Math.floor((startY - top - 120) / (length + interval)), (int)Math.floor((startX - left) / (length + interval)));
drawGrids();
}
break;
}
return true;
}
});
}
游戏功能完成了,接下来就是我们的计时了吧。我们需要写一个时间函数来计时,然后点击暂停的时候时间停止,点击开始的时候函数继续。
public void runing(){
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
getUITaskDispatcher().asyncDispatch(()->{
if (text.equals("开始")) {
button_moveback.setText(text);
timer.cancel();
}
msec++;
if (msec >= 100){
sec++;
msec = msec % 100;
if (sec >= 60) {
min++;
sec = sec % 60;
if (min >= 60) {
hour++;
min = min % 60;
}
}
}
settime(msec, sec, min, hour);
timetext.setText("time: "+ strhour +":"+strmin+":"+strsec+":"+strmsec);
});
}
},0,10);
}
接下来在暂停按钮那加一个点击事件,暂停的时候点击时间函数停止然后把文本设置为开始,开始的时候反过来。
button_moveback.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
if (!gamesuccess()) {
if (text.equals("暂停")) {
timer.cancel();
text = "开始";
} else if (text.equals("开始")) {
text = "暂停";
runing();
}
button_moveback.setText(text);
}
}
});
那时间都开始了,那离记录最近记录是不是就不远了。我们接下来就创建一个轻量级偏好数据库,来让记下来我们的最快记录。
private void initDbManager() {
context = getContext();
databaseHelper = new DatabaseHelper(context);
preferences = databaseHelper.getPreferences(filename);
}
public void getrecord() {
maxhour = preferences.getInt("hour" + single,23);
maxmin = preferences.getInt("min" + single, 59);
maxsec = preferences.getInt("sec" + single, 59);
maxmsec = preferences.getInt("msec" + single , 99);
}
public void Write() {
if (!compare()) {
preferences.putInt("hour" + single, maxhour);
preferences.putInt("min" + single, maxmin);
preferences.putInt("sec" + single, maxsec);
preferences.putInt("msec" + single, maxmsec);
}
}
public boolean compare() {
int nowtime = hour * 36000 + min * 6000 + sec * 100 + msec;
int maxtime = maxhour * 36000 + maxmin * 6000 + maxsec * 100 + maxmsec;
return nowtime > maxtime;
}
然后就是在初始化函数中调用初始化数据库还有获取数据库中的记录。然后在实现游戏功能的函数中判断成功之后,我们在那里面需要再判断是否破纪录,如果破纪录了我们需要把新的记录写到数据库里并且把最短时间的文本给换了。
if(gamesuccess()) {
timer.cancel();
if (!compare()) {
maxhour = hour;
maxmin = min;
maxsec = sec;
maxmsec = msec;
Write();
settime(maxmsec, maxsec, maxmin, maxhour);
maxtext.setText("最短时间: "+ strhour +":"+strmin+":"+strsec+":"+strmsec);
setUIContent(layout);
}
}
那这个计时的功能完成了之后就已经算是完成一大半了。我们接下来就是把返回按钮的功能给实现了,给返回按钮加一个点击事件,让时间函数停止还有销毁页面。
button_back.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
timer.cancel();
terminateAbility();
}
});
那我们接下来就是来实现重新开始的按钮的功能,这个点击事件里面需要实现一个重新打乱还有计时重新开始的功能。
button_again.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
timer.cancel();
text = "暂停";
initialize();
runing();
}
});
那这个之后呢,我们就来现实最后的这个迁移的功能了。不知道大家还记不记得写迁移第一步是在干什么,没错第一步就是加一个IAbilityContinuation的接口函数。接下来我们就在onSaveData接口中将我们的所有的数据都写入intentParams。
intentParams.setParam("single",single);
for (int row = 0;row < single;row++) {
for (int column = 0;column < single; column++) {
string += str.charAt(grids[row][column]);
}
}
HiLog.info(TAG,string);
intentParams.setParam("string",string);
intentParams.setParam("text",text);
intentParams.setParam("hour",hour);
intentParams.setParam("min",min);
intentParams.setParam("sec",sec);
intentParams.setParam("msec",msec);
intentParams.setParam("maxhour",maxhour);
intentParams.setParam("maxmin",maxmin);
intentParams.setParam("maxsec",maxsec);
intentParams.setParam("maxmsec",maxmsec);
intentParams.setParam("row_0",row_0);
intentParams.setParam("column_0",column_0);
intentParams.setParam("isMigrate",true);
string = "";
return true;
那接下来就是在目标设备读取数据了。
single = (int)intentParams.getParam("single");
string = (String)intentParams.getParam("string");
grids = new int[single][single];
StringtoArray();
string = "";
text = (String)intentParams.getParam("text");
hour = (int)intentParams.getParam("hour");
min = (int)intentParams.getParam("min");
sec = (int)intentParams.getParam("sec");
msec = (int)intentParams.getParam("msec");
maxhour = (int)intentParams.getParam("maxhour");
maxmin = (int)intentParams.getParam("maxmin");
maxsec = (int)intentParams.getParam("maxsec");
maxmsec = (int)intentParams.getParam("maxmsec");
row_0 = (int)intentParams.getParam("row_0");
column_0 = (int)intentParams.getParam("column_0");
isMigrate = (boolean)intentParams.getParam("isMigrate");
return true;
那这里面我是把二维数组变成字符串作为数据传输到另外的设备的,那就还涉及到了一个从字符串转化为二维数组的函数。
public void StringtoArray() {
for (int row = 0;row < single; row++) {
for(int column = 0;column < single; column++) {
grids[row][column] = string.charAt(row * single + column) - 33;
}
}
}
并且在迁移完成后,我们需要把原页面给销毁。
public void onCompleteContinuation(int i) {
terminateAbility();
}
那最后就是给迁移按钮来一个点击事件来完成页面的迁移了。
button_migrate.setClickedListener(new Component.ClickedListener() {
@Override
public void onClick(Component component) {
continueAbility();
}
});
至此我们的迁移也就完成了,同时我们的单人模式也就完成了,是不是没有想象中的那么难。
最后就在附上迁移的效果图了。
预告
那我们今天的单人模式的学习就结束啦,在下一篇中,我会写双人模式的游戏界面,那双人模式就涉及到了双人数据的同步,还有重新开始和返回的功能。那让我们一起期待下一篇学习笔记的到来吧。
结语
源码我会放到附件中的,有需求的可以自行下载自行学习,大家有什么看不懂的地方可以私信问我或者对照源码进行学习。
更多资料请关注我们的项目 :Awesome-Harmony_木棉花
本项目会长期更新 ,希望继续关注,我们也会加油做得更好的。明年3月,深大校园内的木棉花会盛开,那时,鸿蒙也会变的更好,愿这花开,有你我的一份。
期待楼主的双人模式。
会尽快发出来的,谢谢支持