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

移动HTML5 专栏收录该内容
3 篇文章 0 订阅

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

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

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


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


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

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

  1. window.AStar = {};
  2. (function(aStar){
  3. //p1:起始节点[i,j] , p2:最终节点[i,j] p3:地图数据(2d)arr,p4:可以通过的标识
  4. aStar.find_path = function(start,end,map,marker){
  5. var open = [];
  6. var close = [];
  7. var s_p = start;
  8. var e_p = end;
  9. var map_arr = map;
  10. var tra_marker = marker;
  11. var G = 0;
  12. var H = 0;
  13. var F = 0;
  14. //加入起始节点 [x, y , G ,F ,father]
  15. 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]);
  16. return function(obj){
  17. //重拍,取最小的一个
  18. var count = 0;
  19. for(var i = obj[0]-1,ilen = i+3 ; i < ilen ; i++){
  20. for(var j = obj[1]-1,jlen = j+3 ;j < jlen; j++){
  21. //遍历周围八节点,排除自己
  22. if(i == obj[0] && j == obj[1])
  23. continue;
  24. //排除穿越的情况
  25. 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))
  26. continue;
  27. if(i == e_p[0] && j == e_p[1]){
  28. open.push([i,j,G,F,obj]);
  29. var ways = [];
  30. var ele = obj;
  31. do{
  32. ways.unshift(ele);
  33. ele = ele[4];
  34. }while(ele[4] != null);
  35. return ways;
  36. }
  37. 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){
  38. G = ( i == obj[0] ) || ( j == obj[1] ) ? obj[2]+1.0 : obj[2]+1.4 ;
  39. H = Math.sqrt((e_p[0] - i)*(e_p[0] - i) + (e_p[1] - j)*(e_p[1] - j));
  40. F = G + H;
  41. //var td = document.getElementById(i+"-"+j);
  42. //td.innerHTML = "G:" + G.toFixed(2) + "\nH:" + H.toFixed(2) + "\nF:" + F.toFixed(2);
  43. open.push([i,j,G,F,obj]);
  44. count++;
  45. }
  46. }
  47. }
  48. close.push(open.shift());
  49. var o;
  50. if(open[0] && open[0][4] == obj[4]){
  51. o = count == 0 ? get_brother(open,obj) : (arr_sort(open),open[0]);
  52. }else{
  53. o = (arr_sort(open),open[0]);
  54. }
  55. if(o){
  56. return arguments.callee(o);
  57. }else{
  58. return [];
  59. }
  60. }(open[0])
  61. }
  62. var get_brother = function(arr,o){
  63. var a = [];
  64. for(var i = 0 ; i < arr.length ; i++){
  65. if(o && arr[i][4] == o[4]){
  66. return arr[i];
  67. }
  68. }
  69. if(o[4]){
  70. return arguments.callee(o[4]);
  71. }else{
  72. return null;
  73. }
  74. }
  75. var arr_sort = function(){
  76. function s(a,b){
  77. return a[3] - b[3];
  78. }
  79. return function(arr){
  80. arr.sort(s);
  81. }
  82. }();
  83. var is_exist = function(arr,p){
  84. for(var i = 0 ; i < arr.length ; i++){
  85. if(arr[i][0] == p[0] && arr[i][1] == p[1]){
  86. return i;
  87. }
  88. }
  89. return -1;
  90. }
  91. })(window.AStar)
下面就是具体实现,分为loading和main两个场景

  1. <html>
  2. <head>
  3. <title>simple rpg a* star</title>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0">
  6. <style type="text/css">
  7. html,body{
  8. padding: 0;
  9. margin: 0;
  10. }
  11. </style>
  12. <script type="text/javascript" src="a_star.js"></script>
  13. </head>
  14. <body>
  15. <div id="game"></div>
  16. <script type="text/javascript" src="crafty.js"></script>
  17. <script type="text/javascript">
  18. var stageWidth = screen.availWidth; //获取设备屏幕尺寸
  19. var stageHeight = screen.availHeight;
  20. var cellWidth = 16; //地图网格尺寸
  21. var map_arr = [];
  22. Crafty.init(stageWidth, stageHeight, "game");
  23. Crafty.background("#000000");
  24. //待加载的资源,参考craftyjs.com的API文档
  25. var assets = {
  26. sprites: {
  27. "bb_sprite.png": {
  28. tile: cellWidth,
  29. tileh: cellWidth,
  30. map: {
  31. grass1: [0,0],
  32. grass2: [1,0],
  33. grass3: [2,0],
  34. grass4: [3,0],
  35. flower: [0,1],
  36. bush1: [0,2],
  37. bush2: [1,2],
  38. player: [0,3]
  39. }
  40. }
  41. }
  42. };
  43. Crafty.scene("loading", function(){
  44. //资源加载完成,进入主场景
  45. Crafty.load(assets, function(){
  46. Crafty.scene("main");
  47. }, function(e){
  48. //资源加载进度
  49. if(this.loadText){
  50. this.loadText = this.loadText.text + e.percent + "%";
  51. }
  52. }, function(e){
  53. console.log("资源加载失败!");
  54. });
  55. });
  56. //显示正在加载场景
  57. this.loadText = Crafty.e("2D, DOM, Text").attr({x:0, y:200, w:400, h:30}).text("loading").css({"text-align":"center"});
  58. Crafty.scene("loading");
  59. //制作主场景
  60. Crafty.scene("main", function(){
  61. var xn = Math.ceil(stageWidth/cellWidth);
  62. var yn = Math.ceil(stageHeight/cellWidth);
  63. var xs = stageWidth%cellWidth;
  64. var ys = stageHeight%cellWidth;
  65. //构建游戏地图
  66. for(var i=0; i<yn; i++){
  67. for(var j=0; j<xn; j++){
  68. var grass = Crafty.e("2D, Canvas, grass"+Crafty.math.randomInt(1, 4)+",Mouse").attr({x: cellWidth*j, y:cellWidth*i});
  69. //处理点击寻路事件
  70. grass.bind("MouseUp", function(e){
  71. if( e.mouseButton == Crafty.mouseButtons.LEFT ){
  72. var startX,endX,startY,endY;
  73. //计算开始和结束点(换算为地图网格坐标)
  74. if(this.x > player.x){
  75. startX = Math.floor(player.y/cellWidth);
  76. endX = Math.ceil(this.y/cellWidth);
  77. }else{
  78. startX = Math.ceil(player.y/cellWidth);
  79. endX = Math.floor(this.y/cellWidth);
  80. }
  81. if(this.y > player.y){
  82. startY = Math.floor(player.x/cellWidth);
  83. endY = Math.ceil(this.x/cellWidth)
  84. }else{
  85. startY = Math.ceil(player.x/cellWidth);
  86. endY = Math.floor(this.x/cellWidth)
  87. }
  88. var end = [endX, endY];
  89. var start = [startX, startY];
  90. //A*寻路
  91. var ways = AStar.find_path(start,end,map_arr,1);
  92. //console.log(ways);
  93. if(ways.length == 0 ){
  94. alert("没有可行的路!");
  95. return ;
  96. }
  97. //清除路标(花)
  98. if(player.flowers){
  99. for(var i=0; i<player.flowers.length; i++){
  100. player.flowers[i].destroy();
  101. }
  102. }
  103. player.ways = ways;
  104. var flowers = [];
  105. for(var i = 0;i < ways.length; i++){
  106. var cellX = ways[i][1];
  107. var cellY = ways[i][0];
  108. var flower = Crafty.e("2D, Canvas, flower, SpriteAnimation").attr({x: cellWidth*cellX, y:cellWidth*cellY});
  109. flowers.push(flower);
  110. }
  111. player.flowers = flowers;
  112. player.wayN = 0;
  113. if(player.walkTimer){
  114. //清除原有定时器
  115. clearTimeout(player.walkTimer);
  116. }
  117. //动画模拟走路
  118. walkWay(player);
  119. }
  120. });
  121. if(!map_arr[i]) map_arr[i] = [];
  122. //填充寻路网格,暂设定1为可行路块,0为不可行
  123. map_arr[i][j] = 1;
  124. if (j > 0 && j < xn-1 && i > 0 && i < yn-1 && Crafty.math.randomInt(0, 50) > 36) {
  125. Crafty.e("2D, Canvas, bush"+Crafty.math.randomInt(1, 2)+", SpriteAnimation").attr({x: cellWidth*j, y:cellWidth*i});
  126. map_arr[i][j] = 0;
  127. }
  128. }
  129. }
  130. //构建地图周围的灌木围栏,人物不可以越出围栏
  131. for(var i=0; i<xn; i++){
  132. //上下
  133. if(xs/2+i*cellWidth+cellWidth>stageWidth){
  134. break;
  135. }
  136. Crafty.e("2D, Canvas, wall_top, bush"+Crafty.math.randomInt(1, 2)).attr({x:xs/2+i*cellWidth,y:0});
  137. Crafty.e("2D, Canvas, wall_bottom, bush"+Crafty.math.randomInt(1, 2)).attr({x:xs/2+i*cellWidth,y:stageHeight-cellWidth});
  138. if(!map_arr[0]) map_arr[0] = [];
  139. map_arr[0][i] = 0;
  140. if(!map_arr[yn-1]) map_arr[yn-1] = [];
  141. map_arr[yn-1][i] = 0;
  142. }
  143. for(var i=1; i<yn-1; i++){
  144. //左右
  145. if(ys/2+i*cellWidth+2*cellWidth>stageHeight){
  146. break;
  147. }
  148. Crafty.e("2D, Canvas, wall_left, bush"+Crafty.math.randomInt(1, 2)).attr({x:0, y:ys/2+i*cellWidth});
  149. Crafty.e("2D, Canvas, wall_right, bush"+Crafty.math.randomInt(1, 2)).attr({x:stageWidth-cellWidth,y:ys/2+i*cellWidth});
  150. if(!map_arr[i]) map_arr[i] = [];
  151. map_arr[i][0] = 0;
  152. map_arr[i][xn-1] = 0;
  153. }
  154. //人物,构建四向行走动画
  155. var player = Crafty.e("2D, Canvas, player, SpriteAnimation, Collision, Tween").attr({x:stageWidth/2, y:stageHeight/5})
  156. .reel("walk_left", 300, 6, 3, 3)
  157. .reel("walk_right", 300, 9, 3, 3)
  158. .reel("walk_up", 300, 3, 3, 3)
  159. .reel("walk_down", 300, 0, 3, 3)
  160. .reel("idle_left", 300, 6, 3, 1)
  161. .reel("idle_right", 300, 9, 3, 1)
  162. .reel("idle_up", 300, 3, 3, 1)
  163. .reel("idle_down", 300, 0, 3, 1);
  164. //碰撞检测(与四周灌木围栏,路标)
  165. player.collision().onHit("wall_left", function(e) {
  166. var tile = e[0].obj;
  167. this.x = tile.x + tile.w;
  168. }).onHit("wall_right", function(e) {
  169. var tile = e[0].obj;
  170. this.x = tile.x - tile.w;
  171. }).onHit("wall_bottom", function(e) {
  172. var tile = e[0].obj;
  173. this.y = tile.y - tile.h;
  174. }).onHit("wall_top", function(e) {
  175. var tile = e[0].obj;
  176. this.y = tile.y + tile.h;
  177. }).onHit("flower", function(e){
  178. var flower = e[0].obj;
  179. flower.destroy();
  180. });
  181. Crafty.bind("EnterFrame", function(){
  182. });
  183. });
  184. //模拟行走到目的地的方法
  185. function walkWay(player){
  186. //计时器执行方法,每次从ways中取一个坐标作为人物移动的目的地网格点
  187. if(!player.wayN){
  188. player.wayN = 0;
  189. }
  190. if(!player.ways){
  191. return;
  192. }
  193. var ways = player.ways;
  194. var dest = ways[player.wayN];
  195. //计算人物所在地图网格坐标
  196. var playerX = Math.round(player.x/cellWidth);
  197. var playerY = Math.round(player.y/cellWidth);
  198. var destX = dest[1];
  199. var destY = dest[0];
  200. //首选取消x, y方向的动作
  201. player.cancelTween("x");
  202. player.cancelTween("y");
  203. //以下是根据目标网格坐标判断人物行走动画
  204. //-->右下
  205. if(playerX < destX && playerY < destY){
  206. if(!player.isPlaying("walk_right"))
  207. player.animate("walk_right", 100);
  208. }
  209. //-->左上
  210. if(playerX > destX && playerY > destY){
  211. if(!player.isPlaying("walk_left"))
  212. player.animate("walk_left", 100);
  213. }
  214. //-->右上
  215. if(playerX < destX && playerY > destY){
  216. if(!player.isPlaying("walk_right"))
  217. player.animate("walk_right", 100);
  218. }
  219. //-->左下
  220. if(playerX > destX && playerY < destY){
  221. if(!player.isPlaying("walk_left"))
  222. player.animate("walk_left", 100);
  223. }
  224. if(playerX == destX && playerY < destY){
  225. if(!player.isPlaying("walk_down"))
  226. player.animate("walk_down", 100);
  227. }
  228. if(playerX == destX && playerY > destY){
  229. if(!player.isPlaying("walk_up"))
  230. player.animate("walk_up", 100);
  231. }
  232. if(playerY == destY && playerX < destX){
  233. if(!player.isPlaying("walk_right"))
  234. player.animate("walk_right", 100);
  235. }
  236. if(playerY == destY && playerX > destX){
  237. if(!player.isPlaying("walk_left"))
  238. player.animate("walk_left", 100);
  239. }
  240. //动画位移
  241. player.tween({x: destX * cellWidth, y: destY * cellWidth}, 700);
  242. //console.log("wayN", player.wayN);
  243. if(player.wayN >= ways.length-1){
  244. console.log("arrived!");
  245. //到达最后一个目标网格坐标,延迟停止行走动画,为了是等tween完成
  246. setTimeout(function(){
  247. var reels = ["left", "right", "up", "down"];
  248. for(var i=0; i<reels.length; i++){
  249. if(player.isPlaying("walk_"+reels[i])){
  250. player.animate("idle_"+reels[i]);
  251. }
  252. }
  253. }, 800)
  254. }
  255. if(player.wayN < ways.length-1){
  256. //递增取坐标点
  257. player.wayN += 1;
  258. }else{
  259. //销毁变量
  260. player.ways = undefined;
  261. player.wayN = undefined;
  262. }
  263. //定时器迭代执行走路方法
  264. player.walkTimer = setTimeout(function(){
  265. walkWay(player);
  266. },500);
  267. }
  268. </script>
  269. </body>
  270. </html>



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

抵扣说明:

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

余额充值

举报

选择你想要举报的内容(必选)
  • 内容涉黄
  • 政治相关
  • 内容抄袭
  • 涉嫌广告
  • 内容侵权
  • 侮辱谩骂
  • 样式问题
  • 其他
新手
引导
客服 举报 返回
顶部