javascript开发植物大战僵尸

简介

这是我给社团学弟写的一个小demo,一个简易版植物大战僵尸,基本上涉及了不少简单而且重要的小知识,对学习前端入门应该还是有些帮助的,现在我带大家来分析分析这个小demo

github地址:https://github.com/likaixuan/record/blob/master/demo/植物大战僵尸

`8BSJ`5T~SW$RW)0B%G{$V0.png

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的自定义属性里,添加的植物会放置到植物数组里、定时器添加的僵尸会放置到僵尸数组里,子弹会间隔一段时间在有攻击力的植物面前添加一颗(其实就是定时器遍历植物列表去添加子弹),定时器会遍历子弹让子弹去移动,也会遍历僵尸让僵尸去移动,僵尸移动到植物上,以及子弹碰到僵尸都会有一个伤害,这个伤害会由我们单独封装的函数去计算。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容