Html5游戏框架Craftyjs入门简单RPG及A*寻路

Crafty.js是一个比较简单轻量的Html5游戏框架,个人比较喜欢,是因为它足够简便(如果你只需要制作简单的小游戏,例如微信h5中的各种游戏)。

遗憾的是,Crafty.js的社区活跃的人越来越少,文档和新的版本也对不上号,所以有的API只能是从源码中获取使用方法了。

这次使用他自带的一个RPG示例加工升级而来,先看下大致的样子:


四周的灌木是防止人物越出屏幕,中间的没有碰撞检测,以下是图片素材:


原本示例中是使用按键控制人物四向行走,我将其改成人物自动寻路到点击坐标,这样手机上也能玩了。

首先需要一个A*算法实现,我在网上找了一个,基本可用

window.AStar = {};
(function(aStar){

	//p1:起始节点[i,j] , p2:最终节点[i,j]  p3:地图数据(2d)arr,p4:可以通过的标识
	aStar.find_path = function(start,end,map,marker){
		var open = [];
		var close = [];

		var s_p = start;
		var e_p = end;
		var map_arr = map;
		var tra_marker = marker;
	
		var G = 0;
		var H = 0;
		var F = 0;

		//加入起始节点  [x, y , G ,F ,father]
		open.push([s_p[0],s_p[1],0,(Math.abs(e_p[0]-s_p[0]) + Math.abs(e_p[1]-s_p[1])),null]);

		return function(obj){
			//重拍,取最小的一个
			var count = 0;
			for(var i = obj[0]-1,ilen = i+3 ; i < ilen ; i++){
				for(var j = obj[1]-1,jlen = j+3 ;j < jlen; j++){
					//遍历周围八节点,排除自己
					if(i == obj[0] && j == obj[1])
						continue;
					//排除穿越的情况
					if(!((i == obj[0] ) || ( j == obj[1])) && ( map_arr[i] && map_arr[obj[0]] && map_arr[i][obj[1]] != tra_marker && map_arr[obj[0]][j] != tra_marker))
						continue;
					if(i == e_p[0] && j == e_p[1]){
						open.push([i,j,G,F,obj]);
						var ways = [];
						var ele = obj;
						do{
							ways.unshift(ele);
							ele = ele[4];
						}while(ele[4] != null);
						return ways;
					}
					
					if(map_arr[i] && map_arr[i][j] && map_arr[i][j] == tra_marker && is_exist(open,[i,j]) == -1 && is_exist(close,[i,j]) == -1){
						G = ( i == obj[0] ) || ( j == obj[1] ) ? obj[2]+1.0 : obj[2]+1.4 ;				
						H = Math.sqrt((e_p[0] - i)*(e_p[0] - i) + (e_p[1] - j)*(e_p[1] - j));
						F = G + H;

						//var td = document.getElementById(i+"-"+j);
						//td.innerHTML = "G:" + G.toFixed(2) + "\nH:" + H.toFixed(2) + "\nF:" + F.toFixed(2);
						open.push([i,j,G,F,obj]);
						count++;
					}
				}
			}
			close.push(open.shift());
			var o;
			if(open[0] && open[0][4] == obj[4]){
				o = count == 0 ? get_brother(open,obj) : (arr_sort(open),open[0]);
			}else{
				o = (arr_sort(open),open[0]);
			}
			
			if(o){
				return arguments.callee(o);
			}else{
				return [];
			}
		}(open[0])
	}

	var get_brother = function(arr,o){
		var a = [];
		for(var i = 0 ; i < arr.length ; i++){
			if(o && arr[i][4] == o[4]){
				return arr[i];
			}
		}
		if(o[4]){
			return arguments.callee(o[4]);
		}else{
			return null;
		}
	}

	
	var arr_sort = function(){
		function s(a,b){
			return a[3] - b[3];
		}
		return function(arr){
			arr.sort(s);
		}
	}();

	var is_exist = function(arr,p){
		for(var i = 0 ; i < arr.length ; i++){
			if(arr[i][0] == p[0] && arr[i][1] == p[1]){
				return i;
			}
		}
		return -1;
	}
})(window.AStar)
下面就是具体实现,分为loading和main两个场景

<html>
<head>
	<title>simple rpg a* star</title>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0">
	<style type="text/css">
		html,body{
			padding: 0;
			margin: 0;
		}
	</style>
	<script type="text/javascript" src="a_star.js"></script>
</head>
<body>
	<div id="game"></div>
	<script type="text/javascript" src="crafty.js"></script>
	<script type="text/javascript">
		var stageWidth = screen.availWidth;		//获取设备屏幕尺寸
		var stageHeight = screen.availHeight;
		var cellWidth = 16;						//地图网格尺寸
		var map_arr = [];
		Crafty.init(stageWidth, stageHeight, "game");
		Crafty.background("#000000");
		//待加载的资源,参考craftyjs.com的API文档
		var assets = {
			sprites: {
				"bb_sprite.png": {
		            tile: cellWidth,
		            tileh: cellWidth,
		            map: {
		                grass1: [0,0],
					    grass2: [1,0],
					    grass3: [2,0],
					    grass4: [3,0],
					    flower: [0,1],
					    bush1:  [0,2],
					    bush2:  [1,2],
					    player: [0,3]
		            }
		        }
			}
		};
		Crafty.scene("loading", function(){
			//资源加载完成,进入主场景
			Crafty.load(assets, function(){
				Crafty.scene("main");
			}, function(e){
				//资源加载进度
				if(this.loadText){
					this.loadText = this.loadText.text + e.percent + "%";
				}
			}, function(e){
				console.log("资源加载失败!");
			});
		});

		//显示正在加载场景
		this.loadText = Crafty.e("2D, DOM, Text").attr({x:0, y:200, w:400, h:30}).text("loading").css({"text-align":"center"});
		Crafty.scene("loading");

		//制作主场景
		Crafty.scene("main", function(){
			var xn = Math.ceil(stageWidth/cellWidth);
			var yn = Math.ceil(stageHeight/cellWidth);
			var xs = stageWidth%cellWidth;
			var ys = stageHeight%cellWidth;
			//构建游戏地图
			for(var i=0; i<yn; i++){
				for(var j=0; j<xn; j++){
					var grass = Crafty.e("2D, Canvas, grass"+Crafty.math.randomInt(1, 4)+",Mouse").attr({x: cellWidth*j, y:cellWidth*i});	
					//处理点击寻路事件
					grass.bind("MouseUp", function(e){
						if( e.mouseButton == Crafty.mouseButtons.LEFT ){
							var startX,endX,startY,endY;
							//计算开始和结束点(换算为地图网格坐标)
							if(this.x > player.x){
								startX = Math.floor(player.y/cellWidth);
								endX = Math.ceil(this.y/cellWidth);
							}else{
								startX = Math.ceil(player.y/cellWidth);
								endX = Math.floor(this.y/cellWidth);
							}
							if(this.y > player.y){
								startY = Math.floor(player.x/cellWidth);
								endY = Math.ceil(this.x/cellWidth)
							}else{
								startY = Math.ceil(player.x/cellWidth);
								endY = Math.floor(this.x/cellWidth)
							}
							var end = [endX, endY];
							var start = [startX, startY];
							//A*寻路
							var ways = AStar.find_path(start,end,map_arr,1);
							//console.log(ways);
							if(ways.length == 0 ){
								alert("没有可行的路!");
								return ;
							}
							//清除路标(花)
							if(player.flowers){
								for(var i=0; i<player.flowers.length; i++){
									player.flowers[i].destroy();
								}
							}
							
							player.ways = ways;
							var flowers = [];
							for(var i = 0;i < ways.length; i++){
								var cellX = ways[i][1];
								var cellY = ways[i][0];	
								var flower = Crafty.e("2D, Canvas, flower, SpriteAnimation").attr({x: cellWidth*cellX, y:cellWidth*cellY});
								flowers.push(flower);
							}
							player.flowers = flowers;
							player.wayN = 0;
							if(player.walkTimer){
								//清除原有定时器
								clearTimeout(player.walkTimer);
							}
							//动画模拟走路
							walkWay(player);
							
						}
					});
					if(!map_arr[i]) map_arr[i] = [];
					//填充寻路网格,暂设定1为可行路块,0为不可行
					map_arr[i][j] = 1;

					if (j > 0 && j < xn-1 && i > 0 && i < yn-1 && Crafty.math.randomInt(0, 50) > 36) {
						Crafty.e("2D, Canvas, bush"+Crafty.math.randomInt(1, 2)+", SpriteAnimation").attr({x: cellWidth*j, y:cellWidth*i});
						map_arr[i][j] = 0;
					}

				}
			}
			//构建地图周围的灌木围栏,人物不可以越出围栏
			for(var i=0; i<xn; i++){
				//上下
				if(xs/2+i*cellWidth+cellWidth>stageWidth){
					break;
				}
				Crafty.e("2D, Canvas, wall_top, bush"+Crafty.math.randomInt(1, 2)).attr({x:xs/2+i*cellWidth,y:0});
				Crafty.e("2D, Canvas, wall_bottom, bush"+Crafty.math.randomInt(1, 2)).attr({x:xs/2+i*cellWidth,y:stageHeight-cellWidth});
				if(!map_arr[0]) map_arr[0] = [];
				map_arr[0][i] = 0;
				if(!map_arr[yn-1]) map_arr[yn-1] = [];
				map_arr[yn-1][i] = 0;
			}
			for(var i=1; i<yn-1; i++){
				//左右
				if(ys/2+i*cellWidth+2*cellWidth>stageHeight){
					break;
				}
				Crafty.e("2D, Canvas, wall_left, bush"+Crafty.math.randomInt(1, 2)).attr({x:0, y:ys/2+i*cellWidth});
				Crafty.e("2D, Canvas, wall_right, bush"+Crafty.math.randomInt(1, 2)).attr({x:stageWidth-cellWidth,y:ys/2+i*cellWidth});
				if(!map_arr[i]) map_arr[i] = [];
				map_arr[i][0] = 0;
				map_arr[i][xn-1] = 0;
			}

			//人物,构建四向行走动画
			var player = Crafty.e("2D, Canvas, player, SpriteAnimation, Collision, Tween").attr({x:stageWidth/2, y:stageHeight/5})
							.reel("walk_left", 300, 6, 3, 3)
							.reel("walk_right", 300, 9, 3, 3)
							.reel("walk_up", 300, 3, 3, 3)
							.reel("walk_down", 300, 0, 3, 3)
							.reel("idle_left", 300, 6, 3, 1)
							.reel("idle_right", 300, 9, 3, 1)
							.reel("idle_up", 300, 3, 3, 1)
							.reel("idle_down", 300, 0, 3, 1);
			//碰撞检测(与四周灌木围栏,路标)
			player.collision().onHit("wall_left", function(e) {
				var tile = e[0].obj;
				this.x = tile.x + tile.w;
			}).onHit("wall_right", function(e) {
				var tile = e[0].obj;
				this.x = tile.x - tile.w;
			}).onHit("wall_bottom", function(e) {
				var tile = e[0].obj;
				this.y = tile.y - tile.h;
			}).onHit("wall_top", function(e) {
				var tile = e[0].obj;
				this.y = tile.y + tile.h;
			}).onHit("flower", function(e){
				var flower = e[0].obj;
				flower.destroy();
			});
			Crafty.bind("EnterFrame", function(){
			});
		});
		//模拟行走到目的地的方法
		function walkWay(player){
			//计时器执行方法,每次从ways中取一个坐标作为人物移动的目的地网格点
			if(!player.wayN){
				player.wayN = 0;
			}
			if(!player.ways){
				return;
			}
			var ways = player.ways;
			var dest = ways[player.wayN];
			//计算人物所在地图网格坐标
			var playerX = Math.round(player.x/cellWidth);
			var playerY = Math.round(player.y/cellWidth); 
			var destX = dest[1];
			var destY = dest[0];
			//首选取消x, y方向的动作
			player.cancelTween("x");
			player.cancelTween("y");
			//以下是根据目标网格坐标判断人物行走动画
			//-->右下
			if(playerX < destX && playerY < destY){
				if(!player.isPlaying("walk_right"))
					player.animate("walk_right", 100);	
			}
			//-->左上
			if(playerX > destX && playerY > destY){
				if(!player.isPlaying("walk_left"))
					player.animate("walk_left", 100);	
			}
			//-->右上
			if(playerX < destX && playerY > destY){
				if(!player.isPlaying("walk_right"))
					player.animate("walk_right", 100);
			}
			//-->左下
			if(playerX > destX && playerY < destY){
				if(!player.isPlaying("walk_left"))
					player.animate("walk_left", 100);
			}

			if(playerX == destX && playerY < destY){
				if(!player.isPlaying("walk_down"))
					player.animate("walk_down", 100);
			}

			if(playerX == destX && playerY > destY){
				if(!player.isPlaying("walk_up"))
					player.animate("walk_up", 100);	
			}

			if(playerY == destY && playerX < destX){
				if(!player.isPlaying("walk_right"))
					player.animate("walk_right", 100);	
			}

			if(playerY == destY && playerX > destX){
				if(!player.isPlaying("walk_left"))
					player.animate("walk_left", 100);	
			}
			//动画位移
			player.tween({x: destX * cellWidth, y: destY * cellWidth}, 700);
			//console.log("wayN", player.wayN);
			if(player.wayN >= ways.length-1){
				console.log("arrived!");
				//到达最后一个目标网格坐标,延迟停止行走动画,为了是等tween完成
				setTimeout(function(){
					var reels = ["left", "right", "up", "down"];
					for(var i=0; i<reels.length; i++){
						if(player.isPlaying("walk_"+reels[i])){
							player.animate("idle_"+reels[i]);
						}
					}
				}, 800)
				
			}	
			if(player.wayN < ways.length-1){
				//递增取坐标点
				player.wayN += 1;	
			}else{
				//销毁变量
				player.ways = undefined;
				player.wayN = undefined;
			}
			//定时器迭代执行走路方法
			player.walkTimer = setTimeout(function(){
				walkWay(player);
			},500);
			
		}
	</script>
</body>
</html>



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
Cocos A*算法寻路是基于A*算法的一种路径搜索算法,用于在游戏中实现角色的自动寻路功能。A*算法是一种启发式搜索算法,它通过评估节点的启发式值来选择最优路径。在待探索列表里,A*算法会先预测哪个节点的路径可能会最短,并优先对该节点展开。这个预测是基于对未探索路径的假设和预测,假设所有路都能通行的前提下,总路径最短。 与传统的Dijkstra算法相比,A*算法在选择展开节点时有一定的优化思路。A*算法会尽量往可能最短的路径去展开寻找,减少不必要的分支,提高寻路的效率。例如,在图论中,假设找到一个中间节点B后,A*算法会计算经过B点从A到E的最短路径(AB)加上从B到E的路径(BE),并尽量展开这条可能最短的路径。这种优化思路可以应用于RPG游戏中,其中路径长度可以计算为从一个点到另一个点经过的格子数。 总之,Cocos A*算法寻路是基于A*算法的一种路径搜索算法,它通过优先选择可能最短的路径来实现自动寻路功能。这种算法可以在游戏开发中提高寻路效率并提供更好的游戏体验。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [cocos creator 实现 A* 算法](https://blog.csdn.net/weixin_41316824/article/details/86607911)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [cocos creator主程入门教程(十)—— A*寻路](https://blog.csdn.net/houjia159/article/details/108450617)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dyyaries

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值