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>

