JS实现基于Sketch.js模拟成群游动的蝌蚪运动动画效果【附demo源码下载】


Posted in Javascript onAugust 18, 2017

本文实例讲述了JS实现基于Sketch.js模拟成群游动的蝌蚪运动动画效果。分享给大家供大家参考,具体如下:

基于Sketch.js,实现了物体触碰检测(蝌蚪会遇到障碍物以及聪明的躲避鼠标的点击),随机运动,聚集算法等。

已经具备了游戏的基本要素,扩展一下可以变成一个不错的 HTML5 游戏。

演示效果如下:

JS实现基于Sketch.js模拟成群游动的蝌蚪运动动画效果【附demo源码下载】

完整代码如下:

<!DOCTYPE html>
<html class=" -webkit- js flexbox canvas canvastext webgl no-touch geolocation postmessage websqldatabase indexeddb hashchange history draganddrop websockets rgba hsla multiplebgs backgroundsize borderimage borderradius boxshadow textshadow opacity cssanimations csscolumns cssgradients cssreflections csstransforms csstransforms3d csstransitions fontface generatedcontent video audio localstorage sessionstorage webworkers applicationcache svg inlinesvg smil svgclippaths">
<head>
<meta charset="UTF-8">
<title>HTML5 Preview Panel</title>
<script src="prefixfree.min.js"></script>
<script src="modernizr.js"></script>
<style>
body {
  background-color: #222;
}
</style>
</head>
<body>
<script src="sketch.min.js"></script>
<div id="container">
<canvas class="sketch" id="sketch-0" height="208" width="607"></canvas>
</div>
<script>
var calculateDistance = function(object1, object2) {
  x = Math.abs(object1.x - object2.x);
  y = Math.abs(object1.y - object2.y);
  return Math.sqrt((x * x) + (y * y));
};
var calcMagnitude = function(x, y) {
  return Math.sqrt((x * x) + (y * y));
};
var calcVectorAdd = function(v1, v2) {
  return {
    x: v1.x + v2.x,
    y: v1.y + v2.y
  };
};
var random = function(min, max) {
  return min + Math.random() * (max - min);
};
var getRandomItem = function(list, weight) {
  var total_weight = weight.reduce(function(prev, cur, i, arr) {
    return prev + cur;
  });
  var random_num = random(0, total_weight);
  var weight_sum = 0;
  //console.log(random_num)
  for (var i = 0; i < list.length; i++) {
    weight_sum += weight[i];
    weight_sum = +weight_sum.toFixed(2);
    if (random_num <= weight_sum) {
      return list[i];
    }
  }
  // end of function
};
/***********************
BOID
***********************/
function Boid(x, y) {
  this.init(x, y);
}
Boid.prototype = {
  init: function(x, y) {
    //body
    this.type = "boid";
    this.alive = true;
    this.health = 1;
    this.maturity = 4;
    this.speed = 6;
    this.size = 5;
    this.hungerLimit = 12000;
    this.hunger = 0;
    this.isFull = false;
    this.digestTime = 400;
    this.color = 'rgb(' + ~~random(0, 100) + ',' + ~~random(50, 220) + ',' + ~~random(50, 220) + ')';
    //brains
    this.eyesight = 100; //range for object dectection
    this.personalSpace = 20; //distance to avoid safe objects
    this.flightDistance = 60; //distance to avoid scary objects
    this.flockDistance = 100; //factor that determines how attracted the boid is to the center of the flock
    this.matchVelFactor = 6; //factor that determines how much the flock velocity affects the boid. less = more matching
    this.x = x || 0.0;
    this.y = y || 0.0;
    this.v = {
      x: random(-1, 1),
      y: random(-1, 1),
      mag: 0
    };
    this.unitV = {
      x: 0,
      y: 0,
    };
    this.v.mag = calcMagnitude(this.v.x, this.v.y);
    this.unitV.x = (this.v.x / this.v.mag);
    this.unitV.y = (this.v.y / this.v.mag);
  },
  wallAvoid: function(ctx) {
    var wallPad = 10;
    if (this.x < wallPad) {
      this.v.x = this.speed;
    } else if (this.x > ctx.width - wallPad) {
      this.v.x = -this.speed;
    }
    if (this.y < wallPad) {
      this.v.y = this.speed;
    } else if (this.y > ctx.height - wallPad) {
      this.v.y = -this.speed;
    }
  },
  ai: function(boids, index, ctx) {
    percievedCenter = {
      x: 0,
      y: 0,
      count: 0
    };
    percievedVelocity = {
      x: 0,
      y: 0,
      count: 0
    };
    mousePredator = {
      x: ((typeof ctx.touches[0] === "undefined") ? 0 : ctx.touches[0].x),
      y: ((typeof ctx.touches[0] === "undefined") ? 0 : ctx.touches[0].y)
    };
    for (var i = 0; i < boids.length; i++) {
      if (i != index) {
        dist = calculateDistance(this, boids[i]);
        //Find all other boids close to it
        if (dist < this.eyesight) {
          //if the same species then flock
          if (boids[i].type == this.type) {
            //Alignment
            percievedCenter.x += boids[i].x;
            percievedCenter.y += boids[i].y;
            percievedCenter.count++;
            //Cohesion
            percievedVelocity.x += boids[i].v.x;
            percievedVelocity.y += boids[i].v.y;
            percievedVelocity.count++;
            //Separation
            if (dist < this.personalSpace + this.size + this.health) {
              this.avoidOrAttract("avoid", boids[i]);
            }
          } else {
            //if other species fight or flight
            if (dist < this.size + this.health + boids[i].size + boids[i].health) {
              this.eat(boids[i]);
            } else {
              this.handleOther(boids[i]);
            }
          }
        } //if close enough
      } //dont check itself
    } //Loop through boids
    //Get the average for all near boids
    if (percievedCenter.count > 0) {
      percievedCenter.x = ((percievedCenter.x / percievedCenter.count) - this.x) / this.flockDistance;
      percievedCenter.y = ((percievedCenter.y / percievedCenter.count) - this.y) / this.flockDistance;
      this.v = calcVectorAdd(this.v, percievedCenter);
    }
    if (percievedVelocity.count > 0) {
      percievedVelocity.x = ((percievedVelocity.x / percievedVelocity.count) - this.v.x) / this.matchVelFactor;
      percievedVelocity.y = ((percievedVelocity.y / percievedVelocity.count) - this.v.y) / this.matchVelFactor;
      this.v = calcVectorAdd(this.v, percievedVelocity);
    }
    //Avoid Mouse
    if (calculateDistance(mousePredator, this) < this.eyesight) {
      var mouseModifier = 20;
      this.avoidOrAttract("avoid", mousePredator, mouseModifier);
    }
    this.wallAvoid(ctx);
    this.limitVelocity();
  },
  setUnitVector: function() {
    var magnitude = calcMagnitude(this.v.x, this.v.y);
    this.v.x = this.v.x / magnitude;
    this.v.y = this.v.y / magnitude;
  },
  limitVelocity: function() {
    this.v.mag = calcMagnitude(this.v.x, this.v.y);
    this.unitV.x = (this.v.x / this.v.mag);
    this.unitV.y = (this.v.y / this.v.mag);
    if (this.v.mag > this.speed) {
      this.v.x = this.unitV.x * this.speed;
      this.v.y = this.unitV.y * this.speed;
    }
  },
  avoidOrAttract: function(action, other, modifier) {
    var newVector = {
      x: 0,
      y: 0
    };
    var direction = ((action === "avoid") ? -1 : 1);
    var vModifier = modifier || 1;
    newVector.x += ((other.x - this.x) * vModifier) * direction;
    newVector.y += ((other.y - this.y) * vModifier) * direction;
    this.v = calcVectorAdd(this.v, newVector);
  },
  move: function() {
    this.x += this.v.x;
    this.y += this.v.y;
    if (this.v.mag > this.speed) {
      this.hunger += this.speed;
    } else {
      this.hunger += this.v.mag;
    }
  },
  eat: function(other) {
    if (!this.isFull) {
      if (other.type === "plant") {
        other.health--;
        this.health++;
        this.isFull = true;
        this.hunger = 0;
      }
    }
  },
  handleOther: function(other) {
    if (other.type === "predator") {
      this.avoidOrAttract("avoid", other);
    }
  },
  metabolism: function() {
    if (this.hunger >= this.hungerLimit) {
      this.health--;
      this.hunger = 0;
    }
    if (this.hunger >= this.digestTime) {
      this.isFull = false;
    }
    if (this.health <= 0) {
      this.alive = false;
    }
  },
  mitosis: function(boids) {
    if (this.health >= this.maturity) {
      //reset old boid
      this.health = 1;
      birthedBoid = new Boid(
        this.x + random(-this.personalSpace, this.personalSpace),
        this.y + random(-this.personalSpace, this.personalSpace)
      );
      birthedBoid.color = this.color;
      boids.push(birthedBoid);
    }
  },
  draw: function(ctx) {
    drawSize = this.size + this.health;
    ctx.beginPath();
    ctx.moveTo(this.x + (this.unitV.x * drawSize), this.y + (this.unitV.y * drawSize));
    ctx.lineTo(this.x + (this.unitV.y * drawSize), this.y - (this.unitV.x * drawSize));
    ctx.lineTo(this.x - (this.unitV.x * drawSize * 2), this.y - (this.unitV.y * drawSize * 2));
    ctx.lineTo(this.x - (this.unitV.y * drawSize), this.y + (this.unitV.x * drawSize));
    ctx.lineTo(this.x + (this.unitV.x * drawSize), this.y + (this.unitV.y * drawSize));
    ctx.fillStyle = this.color;
    ctx.shadowBlur = 20;
    ctx.shadowColor = this.color;
    ctx.fill();
  }
};
Predator.prototype = new Boid();
Predator.prototype.constructor = Predator;
Predator.constructor = Boid.prototype.constructor;
function Predator(x, y) {
  this.init(x, y);
  this.type = "predator";
  //body
  this.maturity = 6;
  this.speed = 6;
  this.hungerLimit = 25000;
  this.color = 'rgb(' + ~~random(100, 250) + ',' + ~~random(10, 30) + ',' + ~~random(10, 30) + ')';
  //brains
  this.eyesight = 150;
  this.flockDistance = 300;
}
Predator.prototype.eat = function(other) {
  if (!this.isFull) {
    if (other.type === "boid") {
      other.health--;
      this.health++;
      this.isFull = true;
      this.hunger = 0;
    }
  }
};
Predator.prototype.handleOther = function(other) {
  if (other.type === "boid") {
    if (!this.isFull) {
      this.avoidOrAttract("attract", other);
    }
  }
};
Predator.prototype.mitosis = function(boids) {
  if (this.health >= this.maturity) {
    //reset old boid
    this.health = 1;
    birthedBoid = new Predator(
      this.x + random(-this.personalSpace, this.personalSpace),
      this.y + random(-this.personalSpace, this.personalSpace)
    );
    birthedBoid.color = this.color;
    boids.push(birthedBoid);
  }
};
Plant.prototype = new Boid();
Plant.prototype.constructor = Plant;
Plant.constructor = Boid.prototype.constructor;
function Plant(x, y) {
  this.init(x, y);
  this.type = "plant";
  //body
  this.speed = 0;
  this.size = 10;
  this.health = ~~random(1, 10);
  this.color = 'rgb(' + ~~random(130, 210) + ',' + ~~random(40, 140) + ',' + ~~random(160, 220) + ')';
  //brains
  this.eyesight = 0;
  this.flockDistance = 0;
  this.eyesight = 0; //range for object dectection
  this.personalSpace = 100; //distance to avoid safe objects
  this.flightDistance = 0; //distance to avoid scary objects
  this.flockDistance = 0; //factor that determines how attracted the boid is to the center of the flock
  this.matchVelFactor = 0; //factor that determines how much the flock velocity affects the boid
}
Plant.prototype.ai = function(boids, index, ctx) {};
Plant.prototype.move = function() {};
Plant.prototype.mitosis = function(boids) {
  var growProbability = 1,
    maxPlants = 40,
    plantCount = 0;
  for (m = boids.length - 1; m >= 0; m--) {
    if (boids[m].type === "plant") {
      plantCount++;
    }
  }
  if (plantCount <= maxPlants) {
    if (random(0, 100) <= growProbability) {
      birthedBoid = new Plant(
        this.x + random(-this.personalSpace, this.personalSpace),
        this.y + random(-this.personalSpace, this.personalSpace)
      );
      birthedBoid.color = this.color;
      boids.push(birthedBoid);
    }
  }
};
Plant.prototype.draw = function(ctx) {
  var drawSize = this.size + this.health;
  ctx.fillStyle = this.color;
  ctx.shadowBlur = 40;
  ctx.shadowColor = this.color;
  ctx.fillRect(this.x - drawSize, this.y + drawSize, drawSize, drawSize);
};
/***********************
SIM
***********************/
var boids = [];
var sim = Sketch.create({
  container: document.getElementById('container')
});
sim.setup = function() {
  for (i = 0; i < 50; i++) {
    x = random(0, sim.width);
    y = random(0, sim.height);
    sim.spawn(x, y);
  }
};
sim.spawn = function(x, y) {
  var predatorProbability = 0.1,
    plantProbability = 0.3;
  switch (getRandomItem(['boid', 'predator', 'plant'], [1 - predatorProbability - plantProbability, predatorProbability, plantProbability])) {
    case 'predator':
      boid = new Predator(x, y);
      break;
    case 'plant':
      boid = new Plant(x, y);
      break;
    default:
      boid = new Boid(x, y);
      break;
  }
  boids.push(boid);
};
sim.update = function() {
  for (i = boids.length - 1; i >= 0; i--) {
    if (boids[i].alive) {
      boids[i].ai(boids, i, sim);
      boids[i].move();
      boids[i].metabolism();
      boids[i].mitosis(boids);
    } else {
      //remove dead boid
      boids.splice(i, 1);
    }
  }
};
sim.draw = function() {
  sim.globalCompositeOperation = 'lighter';
  for (i = boids.length - 1; i >= 0; i--) {
    boids[i].draw(sim);
  }
  sim.fillText(boids.length, 20, 20);
};
</script>
</body>
</html>

附:完整实例代码点击此处本站下载

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
iframe 异步加载技术及性能分析
Jul 19 Javascript
JS中处理与当前时间间隔的函数代码
May 23 Javascript
JS实现距离上次刷新已过多少秒示例
May 23 Javascript
jQuery中get和post方法传值测试及注意事项
Aug 08 Javascript
jQuery实现多级联动下拉列表查询框
Jan 18 Javascript
修复jQuery tablesorter无法正确排序的bug(加千分位数字后)
Mar 30 Javascript
JS及PHP代码编写八大排序算法
Jul 12 Javascript
详解用node.js实现简单的反向代理
Jun 26 Javascript
微信小程序单选radio及多选checkbox按钮用法示例
Apr 30 Javascript
jquery将json转为数据字典的实例代码
Oct 11 jQuery
微信小程序云开发获取文件夹下所有文件(推荐)
Nov 14 Javascript
openlayers实现图标拖动获取坐标
Sep 25 Javascript
select自定义小三角样式代码(实用总结)
Aug 18 #Javascript
js使用highlight.js高亮你的代码
Aug 18 #Javascript
二维码图片生成器QRCode.js简单介绍
Aug 18 #Javascript
Iphone手机、安卓手机浏览器控制默认缩放大小的方法总结(附代码)
Aug 18 #Javascript
JavaScript实现移动端页面按手机屏幕分辨率自动缩放的最强代码
Aug 18 #Javascript
移动设备手势事件库Touch.js使用详解
Aug 18 #Javascript
JavaScript你不知道的一些数组方法
Aug 18 #Javascript
You might like
PHP小技巧搜集,每个PHPer都来露一手
2007/01/02 PHP
关于PHP模板Smarty的初级使用方法以及心得分享
2013/06/21 PHP
php去除字符串换行符示例分享
2014/02/13 PHP
php中magic_quotes_gpc对unserialize的影响分析
2014/12/16 PHP
php采集内容中带有图片地址的远程图片并保存的方法
2015/01/03 PHP
浅谈PHP中的
2016/04/23 PHP
PHP编写daemon process详解及实例代码
2016/09/30 PHP
ThinkPHP5分页paginate代码实例解析
2020/11/10 PHP
javascript自适应宽度的瀑布流实现思路
2013/02/20 Javascript
node.js调用C++开发的模块实例
2015/07/03 Javascript
angularjs封装bootstrap时间插件datetimepicker
2016/06/20 Javascript
通过bootstrap全面学习less
2016/11/09 Javascript
js实现日历与定时器
2017/02/22 Javascript
基于JS实现仿百度百家主页的轮播图效果
2017/03/06 Javascript
JavaScript实现无刷新上传预览图片功能
2017/08/02 Javascript
JavaScript数组去重算法实例小结
2018/05/07 Javascript
vue+vuex+axios从后台获取数据存入vuex,组件之间共享数据操作
2020/07/31 Javascript
Element-ui 自带的两种远程搜索(模糊查询)用法讲解
2021/01/29 Javascript
python代码检查工具pylint 让你的python更规范
2012/09/05 Python
python中map()与zip()操作方法
2016/02/27 Python
用PyInstaller把Python代码打包成单个独立的exe可执行文件
2018/05/26 Python
Python3.7在anaconda里面使用IDLE编译器的步骤详解
2020/04/29 Python
Python为何不支持switch语句原理详解
2020/10/21 Python
python爬虫调度器用法及实例代码
2020/11/30 Python
pycharm 实现调试窗口恢复
2021/02/05 Python
Python3+Appium安装及Appium模拟微信登录方法详解
2021/02/16 Python
使用html2canvas将页面转成图并使用用canvas2image下载
2019/04/04 HTML / CSS
使用索引有什么好处
2016/07/27 面试题
职称自我鉴定
2013/10/15 职场文书
学校大课间活动方案
2014/01/30 职场文书
新闻报道策划方案
2014/06/11 职场文书
事业单位考察材料范文
2014/12/25 职场文书
学校捐书活动总结
2015/05/08 职场文书
生死抉择观后感
2015/06/09 职场文书
《秋天的雨》教学反思
2016/02/19 职场文书
sql通过日期判断年龄函数的示例代码
2021/07/16 SQL Server