吃豆豆游戏又叫什么(吃豆豆的小游戏叫什么)
这个吃豆豆小游戏项目分为单机游戏版和双人游戏版,在单机游戏版中将有两个怪物追赶玩家,玩家在不被追赶上的情况下吃完盘面的豆豆则获胜。双人游戏版则是利用分布式数据库和分布式调度的功能,实现两人联机进行吃豆豆。 游戏封面如下:
把地图以二维数组的形式存储,其中1是墙,2是玩家,3是怪物,0是豆豆,如下:
以下代码分别绘制了四个方向的按钮,并在按钮按下时设置了回调方法,入按下向做的按钮,则把玩家的方向设置成向左,以此类推
publicvoiddrawButton(){ShapeElementbackground=newShapeElement();background.setRgbColor(newRgbColor(174,158,143));background.setCornerRadius(100);Buttonbutton1=newButton(this);button1.setText("←");button1.setTextAlignment(TextAlignment.CENTER);button1.setTextColor(Color.WHITE);button1.setTextSize(100);button1.setMarginTop(1500);button1.setMarginLeft(180);button1.setPadding(10,0,10,0);button1.setBackground(background);button1.setClickedListener(newComponent.ClickedListener(){@OverridepublicvoidonClick(Componentcomponent){player.left();//把玩家的方向设置成向左}});layout.addComponent(button1);Buttonbutton2=newButton(this);button2.setText("↑");button2.setTextAlignment(TextAlignment.CENTER);button2.setTextColor(Color.WHITE);button2.setTextSize(100);button2.setMarginLeft(470);button2.setMarginTop(-330);button2.setPadding(10,0,10,0);button2.setBackground(background);button2.setClickedListener(newComponent.ClickedListener(){@OverridepublicvoidonClick(Componentcomponent){player.up();//把玩家的方向设置成向上}});layout.addComponent(button2);Buttonbutton3=newButton(this);button3.setText("→");button3.setTextAlignment(TextAlignment.CENTER);button3.setTextColor(Color.WHITE);button3.setTextSize(100);button3.setMarginLeft(760);button3.setMarginTop(55);button3.setPadding(10,0,10,0);button3.setBackground(background);button3.setClickedListener(newComponent.ClickedListener(){@OverridepublicvoidonClick(Componentcomponent){player.right();把玩家的方向设置成向右}});layout.addComponent(button3);Buttonbutton=newButton(this);button.setText("↓");button.setTextSize(100);button.setTextAlignment(TextAlignment.CENTER);button.setTextColor(Color.WHITE);button.setMarginTop(100);button.setMarginLeft(470);button.setPadding(10,10,10,10);button.setBackground(background);button.setClickedListener(newComponent.ClickedListener(){@OverridepublicvoidonClick(Componentcomponent){player.down();把玩家的方向设置成向下}});layout.addComponent(button);}
玩家类的属性有方向,玩家的x,y坐标,玩家得分。玩家类的方法有:player_run(),left(),right(),up(),down()等。player_run()将根据玩家实例的方向,在判断玩家实例下一步是否是wall,若不是wall,则行进下一步。而left()等方法则是用于在按下方向键时改变方向时使用。 下面仅贴出部分关键代码:
怪物类主要实现的是追赶玩家,这里采用的方法是:计算怪物当前坐标与玩家当前坐标的差,以此来做出追赶。追赶是这个游戏里最复杂的地方,需要注意的地方有 1.需要防止两个怪物重叠在一起 2.怪物经过有食物的格子时不能够把食物吃掉,因此要判断当前格子是否说食物格。为此设置了布尔类型的标志isfood; Monster类的属性和构造方法如下
追赶方法:计算当前怪物与玩家的x,y方向上的距离,选择其中距离更大的一个方向优先追赶,如若在这个方向上怪物无法行进(两边都是wall),那么则转成朝另一个方向进行追赶。
publicint[][]chase1(int[][]grids,Playerplayer){int[][]grid=grids;this.x_difference=monster_x-player.getPlayer_x();this.y_difference=monster_y-player.getPlayer_y();this.x_distance=Math.abs(x_difference);this.y_distance=Math.abs(y_difference);//判断当前位置是否是食物,若是食物,则先将当前位置置成是食物,再走下一步//若不是食物,则把当前位置置空,然后走下一步if(isfood==true)grid[monster_y][monster_x]=food;elseif(isfood==false)grid[monster_y][monster_x]=empty;if(y_distance 0&&grid[monster_y][monster_x-1]!=wall&&grid[monster_y][monster_x-1]!=monster){monster_x--;if(grid[monster_y][monster_x]==food)//如果下一步是食物,设置标志{isfood=true;grid[monster_y][monster_x]=monster;}elseif(grids[monster_y][monster_x]==4){isfood=false;grid[monster_y][monster_x]=monster;}}elseif(x_difference<0&&grid[monster_y][monster_x+1]!=wall&&grid[monster_y][monster_x-1]!=monster){monster_x++;if(grid[monster_y][monster_x]==0)//判断下一步要走的格子有没有食物,若有则把标志位置true;反之,置false{isfood=true;grid[monster_y][monster_x]=monster;}elseif(grids[monster_y][monster_x]==4){isfood=false;grid[monster_y][monster_x]=monster;}}else//如果x的方向走不通,则转而向y方向进行追赶{if(y_difference>0&&grid[monster_y-1][monster_x]!=wall&&grid[monster_y-1][monster_x]!=monster){monster_y--;if(grid[monster_y][monster_x]==0){isfood=true;grid[monster_y][monster_x]=monster;}elseif(grids[monster_y][monster_x]==4){isfood=false;grid[monster_y][monster_x]=monster;}}elseif(y_difference<0&&grid[monster_y+1][monster_x]!=wall&&grid[monster_y+1][monster_x]!=monster){monster_y++;if(grid[monster_y][monster_x]==0){isfood=true;grid[monster_y][monster_x]=monster;}elseif(grids[monster_y][monster_x]==4){isfood=false;grid[monster_y][monster_x]=monster;}}}}else//x方向上的距离更小或二者相等,则先在y方向上进行追赶{if(y_difference>0&&grid[monster_y-1][monster_x]!=wall&&grid[monster_y-1][monster_x]!=monster){monster_y--;if(grid[monster_y][monster_x]==0){isfood=true;grid[monster_y][monster_x]=monster;}elseif(grids[monster_y][monster_x]==4){isfood=false;grid[monster_y][monster_x]=monster;}}elseif(y_difference<0&&grid[monster_y+1][monster_x]!=wall&&grid[monster_y+1][monster_x]!=monster){monster_y++;if(grid[monster_y][monster_x]==0){isfood=true;grid[monster_y][monster_x]=monster;}elseif(grids[monster_y][monster_x]==4){isfood=false;grid[monster_y][monster_x]=monster;}}else{if(x_difference>0&&grid[monster_y][x_distance-1]!=wall&&grid[monster_y][monster_x-1]!=monster){monster_x--;if(grid[monster_y][monster_x]==0){isfood=true;grid[monster_y][monster_x]=monster;}elseif(grids[monster_y][monster_x]==4){isfood=false;grid[monster_y][monster_x]=monster;}}elseif(x_difference<0&&grid[monster_y][monster_x+1]!=wall&&grid[monster_y][monster_x+1]!=monster){monster_x++;if(grid[monster_y][monster_x]==0){isfood=true;grid[monster_y][monster_x]=monster;}elseif(grids[monster_y][monster_x]==4){isfood=false;grid[monster_y][monster_x]=monster;}}}}returngrid;}
首先需要判断游戏结束或成功,我们定义被怪物追上为游戏结束,即玩家的x,y坐标同时与其中一个怪物相同的情况。定义当盘面所有豆豆都被吃完的情况为游戏成功。判断游戏结束和游戏成功的代码如下:
双人游戏模式下,我设置了一个设备选择的页面。选择设备A的一方将操纵player1,而同时另一方将自动地设置为B方,操纵player2。更重要的是,我希望在两台设备的其中之一做出选择之后,两台设备将自动地同时跳转到游戏页面。于是我采用了带有参数的分布式任务调度来操纵另一台机器的任务拉起,采用带有参数的同Page页面跳转(也就是present语句)来实现本机的跳转。具体代码如下:
首先需要在config.json文件中声明所需要的权限,在"module"代码块中添加
下面使用分布式调度的方法完成对另一个设备的拉起,利用页面跳转来完成本地设备的跳转,这两个跳转都为带参数的跳转,目的是方便后面对设备操作对象的绑定。 分布式调度有两个地方需要注意: 1.BUNDLE_NAME和ABILITY_NAME的设置 可以在config文件的app代码块中找到BUNDLE_NAME
2.分布式调度只能够调度Ability类而不能够调度AbilitySlice类,若想要调度AbilitySlice类,可先调度Ability类再由里面的super.setMainRoute()方法跳转到AbilitySlice类。笔者在一开始时就反了这个错误,导致调度失败。 、 分布式调度与页面调度的代码如下:
publicclassChooseAB_AbilityextendsAbilitySlice{privatestaticfinalStringBUNDLE_NAME="com.example.test";privatestaticfinalStringABILITY_NAME="com.example.test.Test";publicvoidonStart(Intentintent){super.onStart(intent);super.setUIContent(ResourceTable.Layout_ability_third);//获取联网设备List deviceInfoList=DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);if(deviceInfoList.size()==0)return;StringdeviceID=deviceInfoList.get(0).getDeviceId();ButtonbuttonA=(Button)findComponentById(ResourceTable.Id_A_btn);buttonA.setClickedListener(component->{//远程启动FAIntentremoteIntent=newIntent();Operationoperation=newIntent.OperationBuilder().withDeviceId(deviceID).withBundleName(BUNDLE_NAME).withAbilityName(ABILITY_NAME).withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE).build();remoteIntent.setOperation(operation);remoteIntent.setParam("device","B");//设值跳转参数,"device"是键,"B"是值try{//目标设备是否包含指定FAList abilityInfoList=getBundleManager().queryAbilityByIntent(remoteIntent,0,0);if(abilityInfoList!=null&&!abilityInfoList.isEmpty()){startAbility(remoteIntent);//调度另一台设备}}catch(RemoteExceptione){//处理错误}//带参数的页面跳转Intentintent1=newIntent();intent1.setParam("device","A");//设置参数present(newMultiGameAbility_3(),intent1);页面跳转});//另一个按键的代码与上面基本相同,只是传递的参数不同ButtonbuttonB=(Button)findComponentById(ResourceTable.Id_B_btn);buttonB.setClickedListener(newComponent.ClickedListener(){@OverridepublicvoidonClick(Componentcomponent){IntentremoteIntent=newIntent();Operationoperation=newIntent.OperationBuilder().withDeviceId(deviceID).withBundleName(BUNDLE_NAME).withAbilityName(ABILITY_NAME).withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE).build();remoteIntent.setOperation(operation);remoteIntent.setParam("device","A");try{//目标设备是否包含指定FAList
阅读文档我们会发现,分布式数据库的插入和修改操作是同一个语句,若关键字未存在于数据库中,则作插入操作;若数据库存在于数据库中,则作更新操作。这样在一定程度上会显得更智能和便利,但是我更倾向于把这两个功能分开,这样会更加清晰。在学习的过程中,我们曾犯过的错是:先入为主,默认查询是成功的,导致程序的闪退;事实上,它是一个Throws类,其中会抛出的一个错误就是关键字不存在的错误,所以对于所有的Throws类都严格地采用try+catch形式是很重要的。 字符串的增、改、查如下:
//字符串的插入privatevoidinputData_String(Stringkey,Stringvalue){if(key==null||key.isEmpty()||value==null||value.isEmpty());//如果是关键字是空的话,则不做操作else{//当关键字不是空,且这个关键字没有值的时候,才进行putString操作if(queryContact_String(key)==null){singleKvStore.putString(key,value);}}}//字符串的修改privatevoidupdate_String(Stringkey,Stringvalue){if(key==null||key.isEmpty()||value==null||value.isEmpty()||queryContact_String(key)==null);//如果关键字是空的,不做操作elsesingleKvStore.putString(key,value);}//字符串的查询privateStringqueryContact_String(Stringkey){try{returnsingleKvStore.getString(key);}catch(KvStoreExceptionexception){returnnull;//如果出问题了,返回一个null,以便后续的操作}}
privateintqueryContact_Int(Stringkey){try{returnsingleKvStore.getInt(key);}catch(KvStoreExceptionexception){returnerror;//此处error我定义为程序中不会出现的负整数(-1)}}privatevoidupdate_Int(Stringkey,intvalue){if(key==null||key.isEmpty()||queryContact_Int(key)==error);elsesingleKvStore.putInt(key,value);}privatevoidinputData_Int(Stringkey,intvalue){if(queryContact_Int(key)==error){singleKvStore.putInt(key,value);}else;}
对ID进行绑定,我们的方法是借助数据库来进行绑定,往数据库中传入设备ID与对应的值 (”A“或”B“)。因为这两个设备运行的是同一个程序,可以说是镜像的,如果仅仅在本地进行操作则或产生”覆盖“的情况,所以需要一个第三地方的公共的区域来进行绑定操作,因此我们选择数据库。 首先需要获取本地设备ID以及联网的另一设备的ID
privateStringself;//全部变量privateStringother;//全局变量publicvoidgetId(){self=KvManagerFactory.getInstance().createKvManager(newKvManagerConfig(this)).getLocalDeviceInfo().getId();//获取自己的idList onlineDevices=DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE);if(onlineDevices.size()==0)return;other=onlineDevices.get(0).getDeviceId();//获取联网中另一设备的id}
利用上面分布式调度和页面跳转中intent类所带有的参数,并配合分布式数据库进行设备的绑定,代码如下:
至此,我们就完成了ID的同步了。
相较于单机模式,双人模式按钮与操作对象绑定,按钮的回调方法改变如下(以其中一个按钮为例):
同步游戏的方法有多种,说白了就是往数据库传什么类型的值,一下是我们想到的三种方法。 1.最直接的想法是传byte类型的数组,由于只能够传一维数组,可以把二维数组的行下标作为关键字,再把列作用一维数组传入。但是经过我的尝试,传数组容易造成程序闪退的问题。我们猜测由于数据的读取与写入是需要时间的,设备难以在小游戏高刷新速度的情况下完成数组的传递。 2.把二维数组先转化成字符串,再把字符串传到分布式数据库。在本地完成数组和字符串之间的转换,速度应该是很快的,这样只需要向数据库读写一次,设备能够完成。 3,传递玩家的坐标和方向,这种方法不需要进行字符串和数组之间的转换,且对数据库的读取量也较少,设备能够完成。 那么下面仅放出第二种实现代码,第三种的实现代码将在后续的完整代码中放出。 以下为数组和字符串之间转换的方法
publicvoidinitialize(){layout=newDirectionalLayout(this);inputData_Int("player1_d",direction1);//分别把两个玩家的初始方向上传数据库inputData_Int("player2_d",direction2);inputData_Int("A_score",0);inputData_Int("B_Score",0);array=Array_to_String(grids);//把数组转化成字符串inputData_String("array",array);//把字符串写入数据库*/drawButton();drawtable();}
视觉上的同步 想要把分布式数据库上的数据同步体现在视觉上,就需要创建一个以较高速度回调的时间对象,不断地调用画图方法以达到视觉上的同步。同时,在单机版的时候我们本来也有一个时间对象来回调player_run()。
本次学习笔记记录了吃豆豆的游戏实现,最近学习到的UI设置,页面跳转,分布式数据库,分布式任务调度等内容。项目或多或少会有小问题和一些还没有解决的问题。例如在双人模式下取消了怪物的设定就是因为在怪物存在的情况下,常常会出现”怪物分身的问题“,我尝试过去解决但是都没有很好的效果,猜测是与数据库的读写有关。完整代码会在稍后作一些修改后放出,希望届时大家多加指证其中的错误。我当然也会继续去尝试解决本项目中未解决的问题。