简介
这是我给社团学弟写的一个小demo,一个简易版植物大战僵尸,基本上涉及了不少简单而且重要的小知识,对学习前端入门应该还是有些帮助的,现在我带大家来分析分析这个小demo
github地址:https://github.com/likaixuan/record/blob/master/demo/植物大战僵尸
demo实现的功能点
1.掉落星星、点击吃掉星星增加积分
2.购买植物并放置在草地上、购买需要支付一定的星星、星星余额不足不能购买星星
3.植物与僵尸有血量、射速、护甲值一类的属性
4.定时发送炮弹、产生僵尸、炮弹触碰僵尸与僵尸触碰植物时,会经过自身属性转换伤害、根据血量不同展示
不同的状态图片
布局
贴一下代码
@charset "utf-8";
body,
ul {
padding: 0px;
margin: 0px;
}
/*地图*/
#map {
position: relative;
width: 1400px;
height: 600px;
background: url(1.jpg);
}
/*路*/
#road {
position: absolute;
z-index: 2;
left: 250px;
top: 200px;
background: url(8.png);
width: 755px;
height: 110px;
z-index: 10;
}
/*植物*/
#road .plant {
position: absolute;
width: 50px;
height: 50px;
top: 0px;
bottom: 0px;
margin: auto 0px;
}
/*僵尸*/
#road .createZombies {
position: absolute;
height: 128px;
width: 66px;
top: 0px;
bottom: 0px;
right: 0px;
margin: auto 0px;
z-index: 1000;
}
/*子弹*/
#road .bullet {
position: absolute;
width: 35px;
height: 35px;
top: 0px;
bottom: 0px;
margin: auto 0px;
z-index: 1001;
}
/*道具栏*/
#props {
position: absolute;
bottom: 0px;
left: 100px;
z-index: 1000;
background: gainsboro;
list-style: none;
text-align: center;
}
#props li {
position: relative;
display: inline-block;
height: 50px;
width: 50px;
}
#props li span {
position: absolute;
padding: 2px;
right: 2px;
top: 2px;
border-radius: 100%;
background: gold;
}
#props li img {
border: 2px solid gray;
height: 100%;
width: 100%;
cursor: pointer;
}
#props li img.action {
border: 3px solid green;
}
/*星星数量*/
#star-number {
position: absolute;
z-index: 1000;
height: 50px;
width: 50px;
border-radius: 50px;
text-align: center;
line-height: 50px;
background: gold;
color: white;
font-weight: 900;
font-size: 20px;
}
/*降落星星*/
.star {
position: absolute;
width: 50px;
height: 50px;
z-index: 1000;
}
代码的注释也很清楚了、我的思路就是给容器div一个背景图片,也就是咱们看到的地图,然后道具栏呀、草地呀都是通过定位的方式去搞定的
JS
掉落星星
//降落星星
setInterval(function() {
//创建一个img标签
var img = document.createElement("img");
//给img标签赋值上我们写的star class
img.className = "star";
//将图片地址赋值给src
img.src = "star.gif"
map.appendChild(img);
//随机生成img的水平位置 而且不能超出地图
img.style.left = Math.random() * (map.offsetWidth - img.offsetWidth) + 'px';
setInterval(function() {
img.style.top = img.offsetTop + 5 + "px";
//碰到地图边界 删除img
if(img.offsetTop >= map.offsetHeight - img.offsetHeight) {
map.removeChild(img);
}
}, 100);
}, starTimer);
就是间隔一段时间创建一个星星、而且星星的x轴位置是生成的随机数 而且这个随机数是在一个区间内的(不能超出地图)这个,通过offset家族的一系列属性 我们可以判断星星是不是掉落出界了,出界就删除这个节点。
点击星星加分数
//事件委托
map.onclick = function(event) {
//当点击时给star
if(event.target.tagName === "IMG" && event.target.className === "star") {
setStar(10);
//谁被点击 this就是谁 parentNode 就是 this的 父节点
event.target.parentNode.removeChild(event.target);
}
}
//设置星星数 减去传负数、加则传正数
function setStar(n) {
star += n;
starNumber.innerText = star;
}
这个我们需要做的就是判断当这个星星图片被点击的时候去给总星数加一个数值、但是我们怎么知道他什么时候被点击呢? 那么大家肯定说给这个星星添加点击事件呀,那么问题来了,星星是间隔一段时间创建的一个,也就是说我们要给每个星星都绑定一个单击事件,这样其实不是最优解,更好的办法是我们可以对星星的父容器设置一个点击事件
事件冒泡:就比如我们这个星星是在地图上面的,我们点击星星的时候它是触发星星还是地图的click事件呢?答案是先触发上面的星星再触发下面的地图,这个其实就是事件冒泡的一个简单理解(不懂的话可以百度、谷歌一下事件冒泡、事件捕获)
我们通过给父节点地图设置点击事件,当我们点击在地图上方的星星时,其实也是会逐渐往下冒泡的,而我们的点击事件会有一个默认的参数 event(事件对象) 它有一个属性 event.target 她就是当前触发此事件的目标节点,比如我点击在星星上 这个event.target 就是星星。所以就有了我上方的判断,判断是不是img标签而且class叫star 如果点击的是星星那么就用我们设置的setStar方法去设置总星数。
选择植物
//事件委托
map.onclick = function(event) {
//选择道具
if(event.target.tagName === "IMG" && event.target.className === "plant") {
if(event.target.dataset.star <= star) {
clearStyle();
event.target.className = "action plant";
plant = event.target.cloneNode();
}
}
//当点击时给star
if(event.target.tagName === "IMG" && event.target.className === "star") {
setStar(10);
//谁被点击 this就是谁 parentNode 就是 this的 父节点
event.target.parentNode.removeChild(event.target);
}
}
//清除道具选中样式
function clearStyle() {
var t = props.getElementsByTagName("img");
for(var i = 0; i < t.length; i++) {
t[i].className = "plant";
}
}
思路跟之前点击星星一样,用事件委托的方式去判断哪个道具被点击了,道具身上有自定义属性,定义了一系列的属性,比如护甲、hp、购买所需star数,上面加了一个if判断就是为了让总星数小于该道具star数不能选中该道具,选中的则会加一个样式,这里需要注意的一点是,明确 html负责结构、css负责样式、js负责控制,虽然我们可以通过js去设置这个选中边框,但是我们最好是通过class的方式去设置样式,让它们各司其职
放置植物
road.onclick = function(event) {
//植物可摆放的区间
if(event.offsetX > 25 && event.offsetX + 50 < this.offsetWidth) {
if(!!plant && event.target.className !== "action plant") {
plant.style.left = event.offsetX - 25 + 'px';
//购买植物减去相应star
setStar(-plant.dataset.star);
//放置植物
this.appendChild(plant);
//植物数组
plantArr.push(plant);
//战斗力为零 不发射子弹
if(parseInt(plant.dataset.damage) !== 0) {
//创建子弹
bullet.push(createBullet(plant.dataset.speed, plant.dataset.damage, event.offsetX + 25));
}
//清除道具选中样式
clearStyle();
//清除选中道具
plant = null;
}
}
}
// 生成子弹
function createBullet(speed, damage, left) {
/*
* speed 射速
* damage 伤害
*/
var img = document.createElement("img");
img.className = 'bullet';
//设置到创建的子弹标签上
img.dataset.speed = speed;
img.dataset.damage = damage;
img.style.left = left + 'px';
img.src = '6.gif';
road.appendChild(img);
return img;
}
植物不是随便位置就能放置的,给路加click就是说,我肯定会放置在这条路上,因为只有在点击路的时候才会触发放置操作、也是通过offset系列属性去控制放置位置区间、放置成功时要减去对应的star数、并将新添加的植物添加到数组里,我们是通过一个数组来维护植物列表的、再来判断植物的攻击力是不是为0 为0说明是土豆一类的植物不会发射子弹,否则创建子弹并将子弹放置到植物前方
发射子弹
// 间隔一段时间 生成一波子弹
setInterval(function() {
for(var i = 0; i < plantArr.length; i++) {
//战斗力不为0
if(parseInt(plantArr[i].dataset.damage) !== 0) {
//创建子弹
bullet.push(createBullet(plantArr[i].dataset.speed, plantArr[i].dataset.damage, plantArr[i].offsetLeft + 25));
}
}
}, 9000);
// 让子弹飞
setInterval(function() {
for(var i = 0; i < bullet.length; i++) {
bullet[i].style.left = bullet[i].offsetLeft + parseInt(bullet[i].dataset.speed) + "px";
for(var j = 0; j < zombiesArr.length; j++) {
//打到僵尸身上了 -30的原因是 图片有空白
if(bullet[i].offsetLeft + bullet[i].offsetWidth - 30 >= zombiesArr[j].offsetLeft) {
/*
* data-star 所需star数
* data-hp hp
* data-defense 防御力
* data-damage 攻击力
* data-speed 攻速
*/
if(bullet[i].offsetLeft - zombiesArr[j].offsetLeft - zombiesArr[j].offsetWidth < 5) {
//计算伤害
calcDamage(zombiesArr[j], bullet[i], '11.gif');
//受伤状态
zombiesState(j, zombiesArr[j], zombiesArr);
//从地图中删除
road.removeChild(bullet[i]);
//从数组中删除
bullet.splice(i, 1);
break;
}
//打到地图外 删除子弹
if(bullet[i].offsetLeft + bullet[i].offsetWidth > road.offsetWidth) {
bullet[i].parentNode.removeChild(bullet[i]);
//从数组中删除
bullet.splice(i, 1);
}
}
}
}
}, 20);
子弹这方面的思路是这样的,我每一段时间就在有攻击力的植物面前产生子弹,子弹(所有子弹存在一个数组里)会一直被定时器去控制移动,检测到子弹与僵尸碰撞时(生成僵尸和子弹类似就不贴代码了),就会给僵尸减掉一定血量、血量是经过计算的、具体代码大家可以看看github的完整示例,代码都很简单。
基本情况其实就是这样,那么我们再来总结捋一捋
各种属性例如血量、攻击力都存放在html5的自定义属性里,添加的植物会放置到植物数组里、定时器添加的僵尸会放置到僵尸数组里,子弹会间隔一段时间在有攻击力的植物面前添加一颗(其实就是定时器遍历植物列表去添加子弹),定时器会遍历子弹让子弹去移动,也会遍历僵尸让僵尸去移动,僵尸移动到植物上,以及子弹碰到僵尸都会有一个伤害,这个伤害会由我们单独封装的函数去计算。