Android游戏开发学习第3节——不再路痴

来源:互联网 发布:sql数据库管理系统 编辑:程序博客网 时间:2024/06/03 23:47

让怪物动起来容易,可是想让他不会向路痴一样乱跑,而是聪明地动起来,还是有一点难度的。本节中通过一个演示程序,对游戏中能够让怪物聪明地运动的各种算法进行详细介绍。

1.路径搜索示例基本框架的搭建

在正式介绍搜索算法之前,需要将示例的框架搭建出来,这样在介绍各个搜索算法时才能够看到算法的运行效果。

GameView类:

package wyf.ytl;import java.util.ArrayList;import java.util.HashMap;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Paint.Style;import android.os.Handler;import android.os.Message;import android.util.AttributeSet;import android.view.View;import android.widget.Spinner;import android.widget.TextView;public class GameView extends View{private static final int VIEW_WIDTH = 320;//此View的宽度private static final int VIEW_HEIGHT = 340;//此View的高度Game game;Spinner mySpinner;TextView CDTextView;int span = 13;Bitmap source = BitmapFactory.decodeResource(getResources(), R.drawable.source);Bitmap target = BitmapFactory.decodeResource(getResources(), R.drawable.target);Paint paint = new Paint();private Handler myHandler = new Handler(){//用来更新UI线程的控件        public void handleMessage(Message msg) {        if(msg.what == 1){//改变长度的TextView的值         CDTextView.setText("路径长度:" + (Integer)msg.obj);        }        }};public GameView(Context context, AttributeSet attrs) {//构造器super(context, attrs);}protected void onDraw(Canvas canvas) {//重写的绘制方法try{onMyDraw(canvas);//调用自己的绘制方法}catch(Exception e){}}protected void onMyDraw(Canvas canvas){//自己的绘制方法super.onDraw(canvas);canvas.drawColor(Color.GRAY);//背景色//省略绘制方法,随后完善}protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){//重新的方法,返回的是此View的大小        setMeasuredDimension(VIEW_WIDTH,VIEW_HEIGHT);    }}



res/layout下的布局文件如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="vertical"    android:layout_width="fill_parent"    android:layout_height="fill_parent"    >   <com.ga.GameView        android:id="@+id/GameView"       android:layout_width="fill_parent"       android:layout_height="wrap_content"       />   <AbsoluteLayout        android:layout_width="fill_parent"       android:layout_height="fill_parent"       > <!-- 坐标布局 -->       <Spinner            android:id="@+id/mySpinner"           android:layout_width="150px"           android:layout_height="wrap_content"           android:layout_x="5px"           android:layout_y="0px"           /> <!-- 在指定位置添加一个下拉列表Spinner -->        <Spinner            android:id="@+id/target"           android:layout_width="wrap_content"           android:layout_height="wrap_content"           android:layout_x="155px"           android:layout_y="0px"           />        <Button             android:id="@+id/go"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="开始"            android:layout_x="260px"            android:layout_y="0px"            />        <TextView             android:id="@+id/bushu"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="使用步数:"            android:layout_x="20px"            android:layout_y="50px"            />        <TextView             android:id="@+id/changdu"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:text="路径长度:"            android:layout_x="150px"            android:layout_y="50px"            />     </AbsoluteLayout></LinearLayout>

 

注释掉报错部分,此时运行项目效果如图:

 

2.路径搜索示例的控制面板

基本框架搭建完成,但是控件内还没有内容,接下来继续对框架进行完善。

地图类MapList:

package wyf.ytl;public class MapList {static int[] source = { 2, 2 }; // 出发点坐标static int[][] target = { { 13, 2 }, { 4, 22 }, { 0, 11 }, { 9, 10 },{ 21, 22 } }; // 目标点坐标数组static int[][][] map = new int[][][]// 地图数组{ {{ 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },{ 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },{ 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 },{ 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 },{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0 },{ 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 },{ 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 },{ 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },{ 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },{ 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0 },{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0 },{ 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0 },{ 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0 },{ 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0 },{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },{ 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0 },{ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0 },{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0 },{ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0 },{ 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },{ 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },{ 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } };}
  • 二维数组target为目标点的位置数组,这样可以添加多个目标点,运行时通过选择不同的目标点来测试同一种搜索算法在不同情况下的运行效果。
  • map为地图数组,使用三维数组表示的,以后如需添加新地图,只需在三维数组中再添加一个二维数组即可。

算法框架Game类:

package wyf.ytl;import java.util.ArrayList;import java.util.HashMap;import java.util.LinkedList;import java.util.PriorityQueue;import java.util.Stack;import android.os.Handler;//引入相关类import android.os.Message;//引入相关类import android.widget.Button;//引入相关类import android.widget.TextView;//引入相关类public class Game {//算法类int algorithmId=0;//算法代号 0--深度优先int mapId = 0;//地图编号int[][] map = MapList.map[mapId];int[] source = MapList.source;//出发点int[] target = MapList.target[0];//目标点GameView gameView;//gameView的引用Button goButton;//goButton的引用TextView BSTextView;//BSTextView的引用ArrayList<int[][]> searchProcess=new ArrayList<int[][]>();//搜索过程Stack<int[][]> stack=new Stack<int[][]>();//深度优先所用栈HashMap<String,int[][]> hm=new HashMap<String,int[][]>();//结果路径记录LinkedList<int[][]> queue=new LinkedList<int[][]>();//广度优先所用队列//A*用优先级队列PriorityQueue<int[][]> astarQueue=new PriorityQueue<int[][]>(100,new AStarComparator(this));//记录到每个点的最短路径 for DijkstraHashMap<String,ArrayList<int[][]>> hmPath=new HashMap<String,ArrayList<int[][]>>();//记录路径长度 for Dijkstraint[][] length=new int[MapList.map[mapId].length][MapList.map[mapId][0].length];int[][] visited=new int[MapList.map[0].length][MapList.map[0][0].length];//0 未去过 1 去过int[][] sequence={{0,1},{0,-1},{-1,0},{1,0},{-1,1},{-1,-1},{1,-1},{1,1}};boolean pathFlag=false;//true 找到了路径int timeSpan=10;//时间间隔private Handler myHandler = new Handler(){//用来更新UI线程的控件        public void handleMessage(Message msg){        if(msg.what == 1){//改变按钮状态        goButton.setEnabled(true);        }        else if(msg.what == 2){//改变步数的TextView的值        BSTextView.setText("使用步数:" + (Integer)msg.obj);        }        }};public void clearState(){//清空所有状态与列表pathFlag=false;searchProcess.clear();stack.clear();queue.clear();astarQueue.clear();hm.clear();visited=new int[MapList.map[mapId].length][MapList.map[mapId][0].length];hmPath.clear();for(int i=0;i<length.length;i++){for(int j=0;j<length[0].length;j++){length[i][j]=9999;//初始路径长度为最大距离都不可能的那么大}}}public void runAlgorithm(){//运行算法clearState();switch(algorithmId){//此处代码省略,稍后补全}}}
  • sequence数组表示相对于当前点的8个方向的点。

Activity类:

package wyf.ytl;import android.app.Activity;import android.os.Bundle;import android.view.View;import android.widget.AdapterView;import android.widget.ArrayAdapter;import android.widget.Button;import android.widget.Spinner;import android.widget.TextView;public class MainActivity extends Activity {private static final String[] mySpinner_str = {//搜索下拉列表的内容"深度优先","广度优先","广度优先A*","Dijkstra","Dijkstra A*"}; Spinner mySpinner;//搜索下拉列表框Spinner targetSpinner;//目标下拉列表框Button goButton;//开始按钮GameView gameView;//自己实现的地图ViewTextView BSTextView;//使用步数的文本TextView CDTextView;//路径长度的文本Game game;private ArrayAdapter<String> adapter;//搜索下拉列表的模型private ArrayAdapter<String> adapter2;//目标下拉列表的模型    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);//设置当前显示的用户界面        mySpinner = (Spinner)findViewById(R.id.mySpinner);//得到搜索下拉列表的引用        targetSpinner = (Spinner)findViewById(R.id.target);//得到目标下拉列表的引用        gameView = (GameView) findViewById(R.id.gameView);//得到GameView的引用        BSTextView = (TextView)findViewById(R.id.bushu);//得到使用步数的文本控件的引用        CDTextView = (TextView)findViewById(R.id.changdu);//得到路径长度的文本控件的引用        goButton = (Button) findViewById(R.id.go);//得到开始按钮的引用        game = new Game();//初始化算法类        //创建搜索下拉列表的模型        adapter = new ArrayAdapter<String>(this,android.R.layout.simple_spinner_item, mySpinner_str);        String[] target_str = new String[MapList.target.length];//根据目标点的个数创建一个数组        for(int i=0; i<MapList.target.length; i++){        target_str[i] = "目标"+i;        }        //创建目标下拉列表的模型        adapter2 = new ArrayAdapter<String>(this,android.R.layout.simple_spinner_item, target_str);        mySpinner.setAdapter(adapter);//设置模型        targetSpinner.setAdapter(adapter2);//设置模型        goButton.setOnClickListener(//按钮监听器        new Button.OnClickListener(){public void onClick(View v) {game.runAlgorithm();//调用运算方法goButton.setEnabled(false);}        }        );        targetSpinner.setOnItemSelectedListener(//目标选择的下拉列表监听        new Spinner.OnItemSelectedListener(){public void onItemSelected(AdapterView<?> a, View v,int arg2, long arg3){game.target = MapList.target[arg2];game.clearState();//将game的状态清空gameView.postInvalidate();//重写绘制gameView}public void onNothingSelected(AdapterView<?> arg0){}        }        );        mySpinner.setOnItemSelectedListener(//算法选择的下拉列表监听            new Spinner.OnItemSelectedListener(){    public void onItemSelected(AdapterView<?> ada, View v,int arg2, long arg3){    game.clearState();//将game的状态清空    game.algorithmId =  (int) ada.getSelectedItemId();//得到选择的算法ID    gameView.postInvalidate();//重写绘制gameView    }    public void onNothingSelected(AdapterView<?> arg0) {    }            }         );        this.initIoc();//调用依赖注入方法    }    public void initIoc()    {//依赖注入    gameView.game = this.game;    gameView.mySpinner = this.mySpinner;    gameView.CDTextView = this.CDTextView;    game.gameView = this.gameView;    game.goButton = this.goButton;    game.BSTextView = this.BSTextView;    }}


 

注释掉报错部分,此时运行项目效果如图:

3.继续完善GameView

GameView类onMyDraw方法补充如下:

protected void onMyDraw(Canvas canvas){//自己的绘制方法super.onDraw(canvas);canvas.drawColor(Color.GRAY);//背景色paint.setColor(Color.BLACK);//设置颜色paint.setStyle(Style.STROKE);//设置风格canvas.drawRect(5, 5, 313, 327, paint);//绘制矩形int[][] map = game.map;int row = map.length;int col = map[0].length;for(int i=0; i<row; i++){//绘制地图块for(int j=0; j<col; j++){if(map[i][j] == 0){//白色paint.setColor(Color.WHITE);paint.setStyle(Style.FILL);canvas.drawRect(6+j*(span+1), 6+i*(span+1), 6+j*(span+1)+span, 6+i*(span+1)+span, paint);}else if(map[i][j] == 1){//黑色paint.setColor(Color.BLACK);paint.setStyle(Style.FILL);canvas.drawRect(6+j*(span+1), 6+i*(span+1), 6+j*(span+1)+span, 6+i*(span+1)+span, paint);}}}ArrayList<int[][]> searchProcess=game.searchProcess;for(int k=0;k<searchProcess.size();k++){//绘制寻找过程int[][] edge=searchProcess.get(k);  paint.setColor(Color.BLACK);paint.setStrokeWidth(1);canvas.drawLine(edge[0][0]*(span+1)+span/2+6,edge[0][1]*(span+1)+span/2+6,edge[1][0]*(span+1)+span/2+6,edge[1][1]*(span+1)+span/2+6,paint);}//绘制结果路径if(mySpinner.getSelectedItemId()==0||mySpinner.getSelectedItemId()==1||mySpinner.getSelectedItemId()==2){//"深度优先","广度优先","广度优先 A*" 绘制if(game.pathFlag){HashMap<String,int[][]> hm=game.hm;int[] temp=game.target;int count=0;//路径长度计数器while(true){int[][] tempA=hm.get(temp[0]+":"+temp[1]);paint.setColor(Color.BLACK);paint.setStyle(Style.STROKE);//加粗paint.setStrokeWidth(2);//设置画笔粗度为2pxcanvas.drawLine(tempA[0][0]*(span+1)+span/2+6,tempA[0][1]*(span+1)+span/2+6,tempA[1][0]*(span+1)+span/2+6,tempA[1][1]*(span+1)+span/2+6, paint);count++;if(tempA[1][0]==game.source[0]&&tempA[1][1]==game.source[1]){//判断有否到出发点break;}temp=tempA[1];}Message msg1 = myHandler.obtainMessage(1, count);//改变TextView文字myHandler.sendMessage(msg1);}}else if(mySpinner.getSelectedItemId()==3||mySpinner.getSelectedItemId()==4){//"Dijkstra"绘制    if(game.pathFlag){    HashMap<String,ArrayList<int[][]>> hmPath=game.hmPath;ArrayList<int[][]> alPath=hmPath.get(game.target[0]+":"+game.target[1]);for(int[][] tempA:alPath){paint.setColor(Color.BLACK);paint.setStyle(Style.STROKE);//加粗paint.setStrokeWidth(2);//设置画笔粗度为2px    canvas.drawLine(tempA[0][0]*(span+1)+span/2+6,tempA[0][1]*(span+1)+span/2+6,tempA[1][0]*(span+1)+span/2+6,tempA[1][1]*(span+1)+span/2+6, paint);}Message msg1 = myHandler.obtainMessage(1, alPath.size());//改变TextView文字myHandler.sendMessage(msg1);    }}//绘制出发点canvas.drawBitmap(source, 6+game.source[0]*(span+1), 6+game.source[1]*(span+1), paint);//绘制目标点canvas.drawBitmap(target, 6+game.target[0]*(span+1), 6+game.target[1]*(span+1), paint);}


此时运行项目效果如图:


 在前面代码Game类中出现AStarComparator类是A*算法所使用的比较器,下面来创建他的代码。

AStarComparator类:

package wyf.ytl;import java.util.Comparator;public class AStarComparator implements Comparator<int[][]> {Game game;public AStarComparator(Game game) {this.game=game;}@Overridepublic int compare(int[][] o1, int[][] o2) {int[] t1=o1[1];int[] t2=o2[2];int[] target=game.target; //得到目标点//直线物理距离(免开方)int a=(t1[0]-target[0])*(t1[0]-target[0])+(t1[1]-target[1])*(t1[1]-target[1]); int b=(t2[0]-target[0])*(t2[0]-target[0])+(t2[1]-target[1])*(t2[1]-target[1]);//门特卡罗距离//int a=game.visited[o2[0][1]][o2[0][0]]+Math.abs(t1[0]-target[0])+Math.abs(t1[1]-target[1]);//int b=game.visited[o2[0][1]][o2[0][0]]+Math.abs(t2[0]-target[0])+Math.abs(t2[1]-target[1]);return a-b;}@Overridepublic boolean equals(Object o) {return false;}}
  • 两点间距离的计算方法有多种,列出的门特卡罗距离就是一种,但本例中使用的是直线物理距离,所以将门特卡罗距离注释。

4.深度优先路径搜索DFS

深度优先搜索DFS在搜索过程中不考虑各个边的开销,只考虑路径的选择。其思路是站在一个连通图的节点上,然后尽可能地沿着一条边深入,当遇到死胡同时进行回溯,然后继续搜索,直到找到目标为止。

在Game类添加DFS方法如下:

public void DFS() {new Thread() {@Overridepublic void run() {boolean flag=true;int[][] start={{source[0],source[1]},{source[0],source[1]}};stack.push(start);int count=0; //步数计数器while(flag) {int[][] currentEdge=stack.pop(); //从栈顶取出边int[] tempTarget=currentEdge[1]; //取出此边的目的点//判断目的点是否去过,若去过则直接进入下次循环if(visited[tempTarget[1]][tempTarget[0]]==1) {continue;}count++;visited[tempTarget[1]][tempTarget[0]]=1;searchProcess.add(currentEdge); //将临时目的点加入搜索过程中hm.put(tempTarget[0]+":"+tempTarget[1],new int[][]{currentEdge[1],currentEdge[0]});gameView.postInvalidate();try {Thread.sleep(timeSpan);} catch (InterruptedException e) {e.printStackTrace();}//判断是否找到目的点if(tempTarget[0]==target[0]&&tempTarget[1]==target[1]) {break;}//将所有可能的边入栈int currCol=tempTarget[0];int currRow=tempTarget[1];for(int[] rc:sequence) {int i=rc[1];int j=rc[0];if(i==0&&j==0) {continue;}if(currRow+i>=0&&currRow+i<MapList.map[mapId].length&&currCol+j>=0   &&currCol+j<MapList.map[mapId][0].length   &&map[currRow+i][currCol+j]!=1) {int[][] tempEdge={{tempTarget[0],tempTarget[1]},{currCol+j,currRow+i}};stack.push(tempEdge);}}}pathFlag=true;gameView.postInvalidate();Message msg1=myHandler.obtainMessage(1);myHandler.sendMessage(msg1);Message msg2=myHandler.obtainMessage(2,count);myHandler.sendMessage(msg2);}}.start();}
  • 算法的开始之前将起始点到起始点作为第一条边入栈。

之后在Game类的runAlgorichm方法的switch代码块中添加几行代码来调用算法。

case 0:DFS();break;


此时运行项目,按开始按钮后目标0效果如图:

目标1效果如图:

  • 其中细线为搜索过程,粗线为搜索结果。

5.广度优先路径搜索BFS

广度优先搜索是在游戏中使用较多的一种搜索算法,其基本思路是站在一个节点上,先将所有连接到该节点的节点访问到,然后再继续访问下一层,直到找到目标为止。

广度优先在编程方面与深度优先基本相同,只是深度优先使用的是栈储存待访问的边,而广度优先使用的是队列。

在Game类添加BFS方法如下:

public void BFS(){//广度优先算法new Thread(){public void run(){int count=0;//步数计数器boolean flag=true;int[][] start={//开始状态{source[0],source[1]},{source[0],source[1]}};queue.offer(start);while(flag){int[][] currentEdge=queue.poll();//从队首取出边int[] tempTarget=currentEdge[1];//取出此边的目的点//判断目的点是否去过,若去过则直接进入下次循环if(visited[tempTarget[1]][tempTarget[0]]==1){continue;}count++;visited[tempTarget[1]][tempTarget[0]]=1;//标识目的点为访问过searchProcess.add(currentEdge);//将临时目的点加入搜索过程中//记录此临时目的点的父节点hm.put(tempTarget[0]+":"+tempTarget[1],new int[][]{currentEdge[1],currentEdge[0]});gameView.postInvalidate();try{Thread.sleep(timeSpan);}catch(Exception e){e.printStackTrace();}//判断有否找到目的点if(tempTarget[0]==target[0]&&tempTarget[1]==target[1]){break;}//将所有可能的边入队列int currCol=tempTarget[0];int currRow=tempTarget[1];for(int[] rc:sequence){int i=rc[1];int j=rc[0];if(i==0&&j==0){continue;}if(currRow+i>=0&&currRow+i<MapList.map[mapId].length&&currCol+j>=0&&currCol+j<MapList.map[mapId][0].length&&map[currRow+i][currCol+j]!=1){int[][] tempEdge={{tempTarget[0],tempTarget[1]},{currCol+j,currRow+i}};queue.offer(tempEdge);}}}pathFlag=true;gameView.postInvalidate();Message msg1 = myHandler.obtainMessage(1);myHandler.sendMessage(msg1);//设置按钮的可用性Message msg2 = myHandler.obtainMessage(2, count);myHandler.sendMessage(msg2);//改变TextView文字}}.start();}

之后在Game类的runAlgorichm方法的switch代码块中添加几行代码来调用算法。

case 1:BFS();break;

此时运行项目,按开始按钮后目标0效果如图:

目标3效果如图:


6.路径搜索算法——Dijkstra

Dijkstras算法时典型的最短路径算法,一般用于求出从一点触发到达另一点的最优路径,但是,因为其遍历运算的节点较多,所以运行效率较低。

在Game类添加Dijkstra方法如下:

public void Dijkstra(){//Dijkstra算法new Thread(){public void run(){int count=0;//步数计数器boolean flag=true;//搜索循环控制int[] start={source[0],source[1]};//开始点col,rowvisited[source[1]][source[0]]=1;for(int[] rowcol:sequence){//计算此点所有可以到达点的路径及长度int trow=start[1]+rowcol[1];int tcol=start[0]+rowcol[0];if(trow<0||trow>18||tcol<0||tcol>18)continue;if(map[trow][tcol]!=0)continue;//记录路径长度length[trow][tcol]=1;//计算路径String key=tcol+":"+trow;ArrayList<int[][]> al=new ArrayList<int[][]>();al.add(new int[][]{{start[0],start[1]},{tcol,trow}});hmPath.put(key,al);//将去过的点记录searchProcess.add(new int[][]{{start[0],start[1]},{tcol,trow}});count++;}gameView.postInvalidate();outer:while(flag){//找到当前扩展点K 要求扩展点K为从开始点到此点目前路径最短,且此点未考察过int[] k=new int[2];int minLen=9999;for(int i=0;i<visited.length;i++){for(int j=0;j<visited[0].length;j++){if(visited[i][j]==0){if(minLen>length[i][j]){minLen=length[i][j];k[0]=j;//colk[1]=i;//row}}}}visited[k[1]][k[0]]=1;//设置去过的点gameView.postInvalidate();//重绘int dk=length[k[1]][k[0]];//取出开始点到K的路径长度ArrayList<int[][]> al=hmPath.get(k[0]+":"+k[1]);//取出开始点到K的路径//循环计算所有K点能直接到的点到开始点的路径长度for(int[] rowcol:sequence){int trow=k[1]+rowcol[1];//计算出新的要计算的点的坐标int tcol=k[0]+rowcol[0];//若要计算的点超出地图边界或地图上此位置为障碍物则舍弃考察此点if(trow<0||trow>MapList.map[mapId].length-1||tcol<0||tcol>MapList.map[mapId][0].length-1)continue;if(map[trow][tcol]!=0)continue;int dj=length[trow][tcol];//取出开始点到此点的路径长度int dkPluskj=dk+1;//计算经K点到此点的路径长度//若经K点到此点的路径长度比原来的小则修改到此点的路径if(dj>dkPluskj){String key=tcol+":"+trow;//克隆开始点到K的路径ArrayList<int[][]> tempal=(ArrayList<int[][]>)al.clone();//将路径中加上一步从K到此点tempal.add(new int[][]{{k[0],k[1]},{tcol,trow}});//将此路径设置为从开始点到此点的路径hmPath.put(key,tempal);//修改到从开始点到此点的路径长度length[trow][tcol]=dkPluskj;//若此点从未计算过路径长度则将此点加入考察过程记录if(dj==9999){//将去过的点记录searchProcess.add(new int[][]{{k[0],k[1]},{tcol,trow}});count++;}}//看是否找到目的点if(tcol==target[0]&&trow==target[1]){pathFlag=true;Message msg1 = myHandler.obtainMessage(1);myHandler.sendMessage(msg1);//设置按钮的可用性Message msg2 = myHandler.obtainMessage(2, count);myHandler.sendMessage(msg2);//改变TextView文字break outer;}}try{Thread.sleep(timeSpan);}catch(Exception e){e.printStackTrace();}}}}.start();}

之后在Game类的runAlgorichm方法的switch代码块中添加几行代码来调用算法。

case 2:Dijkstra();break;

此时运行项目,按开始按钮后效果如图:


7.用A*算法优化搜索

A*算法时一种启发式搜索,所谓启发式搜索,就是利用一个启发因子评估每次寻找的路线的优劣,再决定往哪个节点走。实际上,A*只是一种思想,可以运用这种思想对之前实现的搜索算法进行优化。

之前开发出启发因子AStarComparator类,为优先级队列设置比较器AStarComparator即可。

7.1用A*算法优化广度优先算法

在Game类添加BFSAStar方法如下:

public void BFSAStar(){//广度优先 A*算法new Thread(){public void run(){int count=0;//步数计数器boolean flag=true;int[][] start={//开始状态{source[0],source[1]},{source[0],source[1]}};astarQueue.offer(start);while(flag){int[][] currentEdge=astarQueue.poll();//从队首取出边int[] tempTarget=currentEdge[1];//取出此边的目的点//判断目的点是否去过,若去过则直接进入下次循环if(visited[tempTarget[1]][tempTarget[0]]==1){continue;}count++;visited[tempTarget[1]][tempTarget[0]]=1;//标识目的点为访问过searchProcess.add(currentEdge);//将临时目的点加入搜索过程中//记录此临时目的点的父节点hm.put(tempTarget[0]+":"+tempTarget[1],new int[][]{currentEdge[1],currentEdge[0]});gameView.postInvalidate();try{Thread.sleep(timeSpan);}catch(Exception e){e.printStackTrace();}//判断有否找到目的点if(tempTarget[0]==target[0]&&tempTarget[1]==target[1]){break;}//将所有可能的边入队列int currCol=tempTarget[0];int currRow=tempTarget[1];for(int[] rc:sequence){int i=rc[1];int j=rc[0];if(i==0&&j==0){continue;}if(currRow+i>=0&&currRow+i<MapList.map[mapId].length&&currCol+j>=0&&currCol+j<MapList.map[mapId][0].length&&map[currRow+i][currCol+j]!=1){int[][] tempEdge={{tempTarget[0],tempTarget[1]},{currCol+j,currRow+i}};astarQueue.offer(tempEdge);}}}pathFlag=true;gameView.postInvalidate();Message msg1 = myHandler.obtainMessage(1);myHandler.sendMessage(msg1);//设置按钮的可用性Message msg2 = myHandler.obtainMessage(2, count);myHandler.sendMessage(msg2);//改变TextView文字}}.start();}
  • 运用A*算法对广度优先算法优化很简单,只需要将广度优先中使用的广度优先队列改为A*优先级队列即可。

之后在Game类的runAlgorichm方法的switch代码块中添加几行代码来调用算法。

case 2:BFSAStar();break;

此时运行项目,效果如图:

  • 用A*优化的广度优先搜索算法得到的路径基本上是最优路径,但是并不能保证一定是最优路径,但执行效率较高,所以在游戏中应用较多。

7.1用A*算法优化Dijkstra算法

在Game类添加DijkstraAStar方法如下:

public void DijkstraAStar(){//Dijkstra A*算法new Thread(){public void run(){int count=0;//步数计数器boolean flag=true;//搜索循环控制int[] start={source[0],source[1]};//开始点col,rowvisited[source[1]][source[0]]=1;for(int[] rowcol:sequence){//计算此点所有可以到达点的路径及长度int trow=start[1]+rowcol[1];int tcol=start[0]+rowcol[0];if(trow<0||trow>18||tcol<0||tcol>18)continue;if(map[trow][tcol]!=0)continue;//记录路径长度length[trow][tcol]=1;//计算路径String key=tcol+":"+trow;ArrayList<int[][]> al=new ArrayList<int[][]>();al.add(new int[][]{{start[0],start[1]},{tcol,trow}});hmPath.put(key,al);//将去过的点记录searchProcess.add(new int[][]{{start[0],start[1]},{tcol,trow}});count++;}gameView.postInvalidate();outer:while(flag){//找到当前扩展点K 要求扩展点K为从开始点到此点目前路径最短,且此点未考察过int[] k=new int[2];int minLen=9999;boolean iniFlag=true;for(int i=0;i<visited.length;i++){for(int j=0;j<visited[0].length;j++){if(visited[i][j]==0){//与普通Dijkstra算法的区别部分=========begin=================================if(length[i][j]!=9999){if(iniFlag){//第一个找到的可能点minLen=length[i][j]+(int)Math.sqrt((j-target[0])*(j-target[0])+(i-target[1])*(i-target[1]));k[0]=j;//colk[1]=i;//rowiniFlag=!iniFlag;}else{int tempLen=length[i][j]+(int)Math.sqrt((j-target[0])*(j-target[0])+(i-target[1])*(i-target[1]));if(minLen>tempLen){minLen=tempLen;k[0]=j;//colk[1]=i;//row}}}//与普通Dijkstra算法的区别部分==========end==================================}}}visited[k[1]][k[0]]=1;//设置去过的点gameView.postInvalidate();//重绘int dk=length[k[1]][k[0]];//取出开始点到K的路径长度ArrayList<int[][]> al=hmPath.get(k[0]+":"+k[1]);//取出开始点到K的路径//循环计算所有K点能直接到的点到开始点的路径长度for(int[] rowcol:sequence){int trow=k[1]+rowcol[1];//计算出新的要计算的点的坐标int tcol=k[0]+rowcol[0];//若要计算的点超出地图边界或地图上此位置为障碍物则舍弃考察此点if(trow<0||trow>MapList.map[mapId].length-1||tcol<0||tcol>MapList.map[mapId][0].length-1)continue;if(map[trow][tcol]!=0)continue;int dj=length[trow][tcol];//取出开始点到此点的路径长度int dkPluskj=dk+1;//计算经K点到此点的路径长度//若经K点到此点的路径长度比原来的小则修改到此点的路径if(dj>dkPluskj){String key=tcol+":"+trow;//克隆开始点到K的路径ArrayList<int[][]> tempal=(ArrayList<int[][]>)al.clone();//将路径中加上一步从K到此点tempal.add(new int[][]{{k[0],k[1]},{tcol,trow}});//将此路径设置为从开始点到此点的路径hmPath.put(key,tempal);//修改到从开始点到此点的路径长度length[trow][tcol]=dkPluskj;//若此点从未计算过路径长度则将此点加入考察过程记录if(dj==9999){//将去过的点记录searchProcess.add(new int[][]{{k[0],k[1]},{tcol,trow}});count++;}}//看是否找到目的点if(tcol==target[0]&&trow==target[1]){pathFlag=true;Message msg1 = myHandler.obtainMessage(1);myHandler.sendMessage(msg1);//设置按钮的可用性Message msg2 = myHandler.obtainMessage(2, count);myHandler.sendMessage(msg2);//改变TextView文字break outer;}}try{Thread.sleep(timeSpan);}catch(Exception e){e.printStackTrace();}}}}.start();}

之后在Game类的runAlgorichm方法的switch代码块中添加几行代码来调用算法。

case 4:DijkstraAStar();break;

此时运行项目,效果如图:



  • 用A*优化的Dijkstra搜索算法得到的路径一定是最优路径,在真正的游戏开发中,应该该算法的次数很多。

8.四向走法

如果游戏中是四向走法而不是八向走法,只需将Game类中的成员变量sequence去掉四个斜方向。

即由

int[][] sequence={{0,1},{0,-1},{-1,0},{1,0},{-1,1},{-1,-1},{1,-1},{1,1}};

改为

int[][] sequence={{0,1},{0,-1},{-1,0},{1,0},};


此时两种优化算法运行效果如图:


原创粉丝点击