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>