1. 先搭架子
整体效果:
点击开始后进入主场景
左侧是植物卡片
右上角是游戏的开始和暂停键
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="css/common.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="js-startGame-btn" class="startGame-btn">点击开始游戏</div>
<!--主场景-->
<div class="content-box">
<canvas id="canvas" width="1400" height="600"></canvas>
</div>
<!--左侧植物-->
<ul class="cards-list">
<li class="cards-item" data-section="sunflower">
<div class="card-intro">
<span>向日葵</span>
<span>冷却时间:5秒</span>
</div>
</li>
<li class="cards-item" data-section="wallnut">
<div class="card-intro">
<span>坚果墙</span>
<span>冷却时间:12秒</span>
</div>
</li>
<li class="cards-item" data-section="peashooter">
<div class="card-intro">
<span>豌豆射手</span>
<span>冷却时间:7秒</span>
</div>
</li>
<li class="cards-item" data-section="repeater">
<div class="card-intro">
<span>双发豌豆射手</span>
<span>冷却时间:10秒</span>
</div>
</li>
<li class="cards-item" data-section="gatlingpea">
<div class="card-intro">
<span>加特林射手</span>
<span>冷却时间:15秒</span>
</div>
</li>
<li class="cards-item" data-section="chomper">
<div class="card-intro">
<span>食人花</span>
<span>冷却时间:15秒</span>
</div>
</li>
<li class="cards-item" data-section="cherrybomb">
<div class="card-intro">
<span>樱桃炸弹</span>
<span>冷却时间:25秒</span>
</div>
</li>
</ul>
<!--Start and Pause-->
<div class="menu-box">
<div id="pauseGame" class="contro-btn">暂停</div>
<div id="restartGame" class="contro-btn">开始游戏</div>
</div>
<!--自动生成阳光-->
<!-- <img class="sum-img systemSun" src="images/sun.gif" alt=""> -->
<script src="js/common.js"></script>
<script src="js/scene.js"></script>
<script src="js/game.js"></script>
<script src="js/main.js"></script>
</body>
</html>
2. 导入植物/僵尸/阳光...的图片
图片包含:植物cd好的状态和冷却期的状态,植物空闲状态/攻击状态,僵尸包含移动状态/攻击状态/樱桃炸弹炸的效果, 同时我们提供对外的imageFromPath函数, 用来生成图片路径
const imageFromPath = function(src){
let img = new Image()
img.src = './images/' + src
return img
}
// 原生动画参数
// const keyframesOptions = {
// iterations: 1,
// iterationStart: 0,
// delay: 0,
// endDelay: 0,
// direction: 'alternate',
// duration: 3000,
// fill: 'forwards',
// easing: 'ease-out',
// }
// 图片素材路径
const allImg = {
startBg: 'coverBg.jpg', // 首屏背景图
bg: 'background1.jpg', // 游戏背景
bullet: 'bullet.png', // 子弹普通状态
bulletHit: 'bullet_hit.png', // 子弹击中敌人状态
sunback: 'sunback.png', // 阳光背景框
zombieWon: 'zombieWon.png', // 僵尸胜利画面
car: 'car.png', // 小汽车图片
loading: { // loading 画面
write: {
path: 'loading/loading_*.png',
len: 3,
},
},
plantsCard: { // 植物卡片
sunflower: { // 向日葵
img: 'cards/plants/SunFlower.png',
imgG: 'cards/plants/SunFlowerG.png',
},
peashooter: { // 豌豆射手
img: 'cards/plants/Peashooter.png',
imgG: 'cards/plants/PeashooterG.png',
},
repeater: { // 双发射手
img: 'cards/plants/Repeater.png',
imgG: 'cards/plants/RepeaterG.png',
},
gatlingpea: { // 加特林射手
img: 'cards/plants/GatlingPea.png',
imgG: 'cards/plants/GatlingPeaG.png',
},
cherrybomb: { // 樱桃炸弹
img: 'cards/plants/CherryBomb.png',
imgG: 'cards/plants/CherryBombG.png',
},
wallnut: { // 坚果墙
img: 'cards/plants/WallNut.png',
imgG: 'cards/plants/WallNutG.png',
},
chomper: { // 食人花
img: 'cards/plants/Chomper.png',
imgG: 'cards/plants/ChomperG.png',
},
},
plants: { // 植物
sunflower: { // 向日葵
idle: {
path: 'plants/sunflower/idle/idle_*.png',
len: 18,
},
},
peashooter: { // 豌豆射手
idle: {
path: 'plants/peashooter/idle/idle_*.png',
len: 8,
},
attack: {
path: 'plants/peashooter/attack/attack_*.png',
len: 8,
},
},
repeater: { // 双发射手
idle: {
path: 'plants/repeater/idle/idle_*.png',
len: 15,
},
attack: {
path: 'plants/repeater/attack/attack_*.png',
len: 15,
},
},
gatlingpea: { // 加特林射手
idle: {
path: 'plants/gatlingpea/idle/idle_*.png',
len: 13,
},
attack: {
path: 'plants/gatlingpea/attack/attack_*.png',
len: 13,
},
},
cherrybomb: { // 樱桃炸弹
idle: {
path: 'plants/cherrybomb/idle/idle_*.png',
len: 7,
},
attack: {
path: 'plants/cherrybomb/attack/attack_*.png',
len: 5,
},
},
wallnut: { // 坚果墙
idleH: { // 血量高时动画
path: 'plants/wallnut/idleH/idleH_*.png',
len: 16,
},
idleM: { // 血量中等时动画
path: 'plants/wallnut/idleM/idleM_*.png',
len: 11,
},
idleL: { // 血量低时动画
path: 'plants/wallnut/idleL/idleL_*.png',
len: 15,
},
},
chomper: { // 食人花
idle: { // 站立动画
path: 'plants/chomper/idle/idle_*.png',
len: 13,
},
attack: { // 攻击动画
path: 'plants/chomper/attack/attack_*.png',
len: 8,
},
digest: { // 消化阶段动画
path: 'plants/chomper/digest/digest_*.png',
len: 6,
}
},
},
zombies: { // 僵尸
idle: { // 站立动画
path: 'zombies/idle/idle_*.png',
len: 31,
},
run: { // 移动动画
path: 'zombies/run/run_*.png',
len: 31,
},
attack: { // 攻击动画
path: 'zombies/attack/attack_*.png',
len: 21,
},
dieboom: { // 被炸死亡动画
path: 'zombies/dieboom/dieboom_*.png',
len: 20,
},
dying: { // 濒死动画
head: {
path: 'zombies/dying/head/head_*.png',
len: 12,
},
body: {
path: 'zombies/dying/body/body_*.png',
len: 18,
},
},
die: { // 死亡动画
head: {
path: 'zombies/dying/head/head_*.png',
len: 12,
},
body: {
path: 'zombies/die/die_*.png',
len: 10,
},
},
}
}
3. 场景的塑造
例如:左上角的阳光显示板, 右侧的植物卡片, 小汽车和子弹等等...
先来了解一下Canvas这个标签, 你可以把它想像成一个画布,我们可以通过获取上下文来绘制在画布上进行绘画(坐标系如下)
<canvas id="canvas" width="500" height="500"></canvas>
<script>
let canvas=document.getElementById("canvas")
let cxt=canvas.getContext("2d") //画笔
//绘制一个矩形
ctx.rect(0,0,100,200)
//实心
ctx.fill()
//描边
ctx.stroke()
//为上下文填充颜色
cxt.fillStyle="orange"
//填充文本
ctx.font="700 16px Arial"
ctx.fillText("内容",x,y,[,maxWidth])
//添加图片
let img=new Image()
img.src='myImage.png'
cxt.drawImage(img,x,y,width,height)
//预加载
let img=new Image()
img.onload=function(){
ctx.drawImage(img,0,0)
}
img.src='myImage.png'
</script>
阳光显示板:1. 背景img 2. 所显示的阳光总数量 3. 字体大小和颜色
class SunNum{
constructor(){
let s={
img:null,
sun_num:window._main.allSunVal, //阳光总数量
x:105,
y:0,
}
Object.assign(this,s)
}
static new(){
let s=new this()
s.img=imageFromPath(allImg.sunback)
return s
}
draw(cxt){
let self=this
cxt.drawImage(self.img,self.x+120,self.y) //用于在Canvas上绘制图像
cxt.fillStyle='black'
cxt.font='24px Microsoft YaHei'
cxt.fontWeight=700
cxt.fillText(self.sun_num,self.x+175,self.y+27)
}
//修改阳光 !!!!!
changeSunNum(num=25){
let self=this
window._main.allSunVal+=num
self.sun_num+=num
}
}
左侧卡片:当我们使用了一个植物后,它的状态就会改变, 类似于进入到冷却时间
class Card{
constructor(obj){
let c={
name:obj.name,
canGrow:true,
canClick:true,
img:null,
images:[],
timer:null,
timer_spacing:obj.timer_spacing,
timer_num:1,
sun_val:obj.sun_val,
row:obj.row,
x:0,
y:obj.y,
}
Object.assign(this,c)
}
static new(obj){
let b=new this(obj)
b.images.push(imageFromPath(allImg.plantsCard[b.name].img))
b.images.push(imageFromPath(allImg.plantsCard[b.name].imgG))
if(b.canClick){
b.img=b.images[0]
}else{
b.img=b.images[1]
}
b.timer_num = b.timer_spacing / 1000 //1000ms
return b
}
draw(cxt) {
let self = this, marginLeft = 120
if(self.sun_val > window._main.allSunVal){
self.canGrow = false
}else{
self.canGrow = true
}
if(self.canGrow && self.canClick){
self.img = self.images[0]
}else{
self.img = self.images[1]
}
cxt.drawImage(self.img, self.x + marginLeft, self.y)
cxt.fillStyle = 'black'
cxt.font = '16px Microsoft YaHei'
cxt.fillText(self.sun_val, self.x + marginLeft + 60, self.y + 55)
if (!self.canClick && self.canGrow) {
cxt.fillStyle = 'rgb(255, 255, 0)'
cxt.font = '20px Microsoft YaHei'
cxt.fillText(self.timer_num, self.x + marginLeft + 30, self.y + 35)
}
}
drawCountDown(){
let self=this
self.timer=setInterval(()=>{ //定时器
if(self.timer_num>0){
self.timer_num--
}else{
clearInterval(self.timer)
self.timer_num=self.timer_spacing/1000
}
},1000)
}
changeState(){
let self=this
if(!self.canClick){
self.timer=setTimeout(()=> { //延时器
self.canClick=true
},self.timer_spacing)
}
}
}
除草车:当僵尸靠近坐标x(在一定范围内)的时候, 就会清除整行僵尸
class Car{
constructor(obj){
let c={
img: imageFromPath(allImg.car),
state:1,
state_NORMALE:1,
state_ATTACK:2,
w:71,
h:57,
x:obj.x,
y:obj.y,
row:obj.row,
}
Object.assign(this,c)
}
static new(obj){
let c=new this(obj)
return c
}
draw(game,cxt){
let self = this
self.canMove()
self.state === self.state_ATTACK && self.step(game)
cxt.drawImage(self.img, self.x, self.y)
}
step(game) {
game.state === game.state_RUNNING ? this.x += 15 : this.x = this.x
}
// 判断是否移动小车 (zombie.x < 150时)
canMove () {
let self = this
for (let zombie of window._main.zombies) {
if (zombie.row === self.row) {
if (zombie.x < 150) {
self.state = self.state_ATTACK
}
if (self.state === self.state_ATTACK) {
if (zombie.x - self.x < self.w && zombie.x < 950) {
zombie.life = 0
zombie.changeAnimation('die')
}
}
}
}
}
}
子弹:例如像豌豆射手就会发射子弹,但是只有在state_RUNNING状态下, 才会进行触发
class Bullet{
constructor(plant){
let b={
img: imageFromPath(allImg.bullet),
w:56,
h:34,
x:0,
y:0,
}
Object.assign(this,b)
}
static new(plant){
let b=new this(plant)
switch (plant.section) {
case 'peashooter':
b.x = plant.x + 30
b.y = plant.y
break
case 'repeater':
b.x = plant.x + 30
b.y = plant.y
break
case 'gatlingpea':
b.x = plant.x + 30
b.y = plant.y + 10
break
}
return b
}
draw(game,cxt){
let self=this
self.step(game)
cxt.drawImage(self.img,self.x,self.y)
}
step(game){
if(game.state === game.state_RUNNING){
this.x+=4
}else{
this.x=this.x
}
}
}
为角色设置动画
class Animation{
constructor (role, action, fps) {
let a = {
type: role.type, // 动画类型(植物、僵尸等等)
section: role.section, // 植物或者僵尸类别(向日葵、豌豆射手)
action: action, // 根据传入动作生成不同动画对象数组
images: [], // 当前引入角色图片对象数组
img: null, // 当前显示角色图片
imgIdx: 0, // 当前角色图片序列号
count: 0, // 计数器,控制动画运行
imgHead: null, // 当前显示角色头部图片
imgBody: null, // 当前显示角色身体图片
imgIdxHead: 0, // 当前角色头部图片序列号
imgIdxBody: 0, // 当前角色身体图片序列号
countHead: 0, // 当前角色头部计数器,控制动画运行
countBody: 0, // 当前角色身体计数器,控制动画运行
fps: fps, // 角色动画运行速度系数,值越小,速度越快
}
Object.assign(this, a)
}
// 创建,并初始化当前对象
static new (role, action, fps) {
let a = new this(role, action, fps)
// 濒死动画、死亡动画对象(僵尸)
if (action === 'dying' || action === 'die') {
a.images = {
head: [],
body: [],
}
a.create()
} else {
a.create()
a.images[0].onload = function () {
role.w = this.width
role.h = this.height
}
}
return a
}
/**
* 为角色不同动作创造动画序列
*/
create () {
let self = this,
section = self.section // 植物种类
switch (self.type) {
case 'plant':
for(let i = 0; i < allImg.plants[section][self.action].len; i++){
let idx = i < 10 ? '0' + i : i,
path = allImg.plants[section][self.action].path
// 依次添加动画序列
self.images.push(imageFromPath(path.replace(/\*/, idx)))
}
break
case 'zombie':
// 濒死动画、死亡动画对象,包含头部动画以及身体动画
if (self.action === 'dying' || self.action === 'die') {
for(let i = 0; i < allImg.zombies[self.action].head.len; i++){
let idx = i < 10 ? '0' + i : i,
path = allImg.zombies[self.action].head.path
// 依次添加动画序列
self.images.head.push(imageFromPath(path.replace(/\*/, idx)))
}
for(let i = 0; i < allImg.zombies[self.action].body.len; i++){
let idx = i < 10 ? '0' + i : i,
path = allImg.zombies[self.action].body.path
// 依次添加动画序列
self.images.body.push(imageFromPath(path.replace(/\*/, idx)))
}
} else { // 普通动画对象
for(let i = 0; i < allImg.zombies[self.action].len; i++){
let idx = i < 10 ? '0' + i : i,
path = allImg.zombies[self.action].path
// 依次添加动画序列
self.images.push(imageFromPath(path.replace(/\*/, idx)))
}
}
break
case 'loading': // loading动画
for(let i = 0; i < allImg.loading[self.action].len; i++){
let idx = i < 10 ? '0' + i : i,
path = allImg.loading[self.action].path
// 依次添加动画序列
self.images.push(imageFromPath(path.replace(/\*/, idx)))
}
break
}
}
}
为植物和僵尸设置不同状态下的动画效果
/**
* 角色类
* 植物、僵尸类继承的基础属性
*/
class Role{
constructor (obj) {
let r = {
id: Math.random().toFixed(6) * Math.pow(10, 6), // 随机生成 id 值,用于设置当前角色 ID
type: obj.type, // 角色类型(植物或僵尸)
section: obj.section, // 角色类别(豌豆射手、双发射手...)
x: obj.x, // x轴坐标
y: obj.y, // y轴坐标
row: obj.row, // 角色初始化行坐标
col: obj.col, // 角色初始化列坐标
w: 0, // 角色图片宽度
h: 0, // 角色图片高度
isAnimeLenMax: false, // 是否处于动画最后一帧,用于判断动画是否执行完一轮
isDel: false, // 判断是否死亡并移除当前角色
isHurt: false, // 判断是否受伤
}
Object.assign(this, r)
}
}
// 植物类
class Plant extends Role{
constructor (obj) {
super(obj)
// 植物类私有属性
let p = {
life: 3, // 角色血量
idle: null, // 站立动画对象
idleH: null, // 坚果高血量动画对象
idleM: null, // 坚果中等血量动画对象
idleL: null, // 坚果低血量动画对象
attack: null, // 角色攻击动画对象
digest: null, // 角色消化动画对象
bullets: [], // 子弹数组对象
state: obj.section === 'wallnut' ? 2 : 1, // 保存当前状态值
state_IDLE: 1, // 站立不动状态
state_IDLE_H: 2, // 站立不动高血量状态(坚果墙相关动画)
state_IDLE_M: 3, // 站立不动中等血量状态(坚果墙相关动画)
state_IDLE_L: 4, // 站立不动低血量状态(坚果墙相关动画)
state_ATTACK: 5, // 攻击状态
state_DIGEST: 6, // 待攻击状态(食人花消化僵尸状态)
canShoot: false, // 植物是否具有发射子弹功能
canSetTimer: obj.canSetTimer, // 能否设置生成阳光定时器
sunTimer: null, // 生成阳光定时器
sunTimer_spacing: 10, // 生成阳光时间间隔(秒)
}
Object.assign(this, p)
}
// 创建,并初始化当前对象
static new (obj) {
let p = new this(obj)
p.init()
return p
}
// 设置阳光生成定时器
setSunTimer () {
let self = this
self.sunTimer = setInterval(function () {
// 创建阳光元素
let img = document.createElement('img'), // 创建元素
container = document.getElementsByTagName('body')[0], // 父级元素容器
id = self.id, // 当前角色 ID
top = self.y + 30,
left = self.x - 130,
keyframes1 = [ // 阳光移动动画 keyframes
{ transform: 'translate(0,0)', opacity: 0 },
{ offset: .3,transform: 'translate(0,0)', opacity: 1 },
{ offset: .5,transform: 'translate(0,0)', opacity: 1 },
{ offset: 1,transform: 'translate(-'+ (left - 110) +'px,-'+ (top + 50) +'px)',opacity: 0 }
]
// 添加阳关元素
img.src = 'images/sun.gif'
img.className += 'sun-img plantSun' + id
img.style.top = top + 'px'
img.style.left = left + 'px'
container.appendChild(img)
// 添加阳光移动动画
let sun = document.getElementsByClassName('plantSun' + id)[0]
sun.animate(keyframes1,keyframesOptions)
// 动画完成,清除阳光元素
setTimeout(()=> {
sun.parentNode.removeChild(sun)
// 增加阳光数量
window._main.sunnum.changeSunNum()
}, 2700)
}, self.sunTimer_spacing * 1000)
}
// 清除阳光生成定时器
clearSunTimer () {
let self = this
clearInterval(self.sunTimer)
}
// 初始化
init () {
let self = this,
setPlantFn = null
// 初始化植物动画对象方法集
setPlantFn = {
sunflower () { // 向日葵
self.idle = Animation.new(self, 'idle', 12)
// 定时生成阳光
self.canSetTimer && self.setSunTimer()
},
peashooter () { // 豌豆射手
self.canShoot = true
self.idle = Animation.new(self, 'idle', 12)
self.attack = Animation.new(self, 'attack', 12)
},
repeater () { // 双发射手
self.canShoot = true
self.idle = Animation.new(self, 'idle', 12)
self.attack = Animation.new(self, 'attack', 8)
},
gatlingpea () { // 加特林射手
// 改变加特林渲染 y 轴距离
self.y -= 12
self.canShoot = true
self.idle = Animation.new(self, 'idle', 8)
self.attack = Animation.new(self, 'attack', 4)
},
cherrybomb () { // 樱桃炸弹
self.x -= 15
self.idle = Animation.new(self, 'idle', 15)
self.attack = Animation.new(self, 'attack', 15)
setTimeout(()=> {
self.state = self.state_ATTACK
}, 2000)
},
wallnut () { // 坚果墙
self.x += 15
// 设置坚果血量
self.life = 12
// 创建坚果三种不同血量下的动画对象
self.idleH = Animation.new(self, 'idleH', 10)
self.idleM = Animation.new(self, 'idleM', 8)
self.idleL = Animation.new(self, 'idleL', 10)
},
chomper () { // 食人花
self.life = 5
self.y -= 45
self.idle = Animation.new(self, 'idle', 10)
self.attack = Animation.new(self, 'attack', 12)
self.digest = Animation.new(self, 'digest', 12)
},
}
// 执行对应植物初始化方法
for (let key in setPlantFn) {
if (self.section === key) {
setPlantFn[key]()
}
}
}
// 绘制方法
draw (cxt) {
let self = this,
stateName = self.switchState()
switch (self.isHurt) {
case false:
if (self.section === 'cherrybomb' && self.state === self.state_ATTACK) {
// 正常状态,绘制樱桃炸弹爆炸图片
cxt.drawImage(self[stateName].img, self.x - 60, self.y - 50)
} else {
// 正常状态,绘制普通植物图片
cxt.drawImage(self[stateName].img, self.x, self.y)
}
break
case true:
// 受伤或移动植物时,绘制半透明图片
cxt.globalAlpha = 0.5
cxt.beginPath()
cxt.drawImage(self[stateName].img, self.x, self.y)
cxt.closePath()
cxt.save()
cxt.globalAlpha = 1
break
}
}
// 更新状态
update (game) {
let self = this,
section = self.section,
stateName = self.switchState()
// 修改当前动画序列长度
let animateLen = allImg.plants[section][stateName].len
// 累加动画计数器
self[stateName].count += 1
// 设置角色动画运行速度
self[stateName].imgIdx = Math.floor(self[stateName].count / self[stateName].fps)
// 一整套动画完成后重置动画计数器
self[stateName].imgIdx === animateLen - 1 ? self[stateName].count = 0 : self[stateName].count = self[stateName].count
// 绘制发射子弹动画
if (game.state === game.state_RUNNING) {
// 设置当前帧动画对象
self[stateName].img = self[stateName].images[self[stateName].imgIdx]
if (self[stateName].imgIdx === animateLen - 1) {
if (stateName === 'attack' && !self.isDel) {
// 未死亡,且为可发射子弹植物时
if (self.canShoot) {
// 发射子弹
self.shoot()
// 双发射手额外发射子弹
self.section === 'repeater' && setTimeout(()=> {self.shoot()}, 250)
}
// 当为樱桃炸弹时,执行完一轮动画,自动消失
self.section === 'cherrybomb' ? self.isDel = true : self.isDel = false
// 当为食人花时,执行完攻击动画,切换为消化动画
if (self.section === 'chomper') {
// 立即切换动画会出现图片未加载完成报错
setTimeout(()=> {
self.changeAnimation('digest')
}, 0)
}
} else if (self.section === 'chomper' && stateName === 'digest') {
// 消化动画完毕后,间隔一段时间切换为正常状态
setTimeout(()=> {
self.changeAnimation('idle')
}, 30000)
}
self.isAnimeLenMax = true
} else {
self.isAnimeLenMax = false
}
}
}
// 检测植物是否可攻击僵尸方法
canAttack () {
let self = this
// 植物类别为向日葵和坚果墙时,不需判定
if (self.section === 'sunflower' || self.section === 'wallnut') return false
// 循环僵尸对象数组
for (let zombie of window._main.zombies) {
if (self.section === 'cherrybomb') { // 当为樱桃炸弹时
// 僵尸在以樱桃炸弹为圆心的 9 个格子内时
if (Math.abs(self.row - zombie.row) <= 1 && Math.abs(self.col - zombie.col) <= 1 && zombie.col < 10) {
// 执行爆炸动画
self.changeAnimation('attack')
zombie.life = 0
// 僵尸炸死动画
zombie.changeAnimation('dieboom')
}
} else if (self.section === 'chomper' && self.state === self.state_IDLE) { // 当为食人花时
// 僵尸在食人花正前方时
if (self.row === zombie.row && (zombie.col - self.col) <= 1 && zombie.col < 10) {
self.changeAnimation('attack')
setTimeout(()=> {
zombie.isDel = true
}, 1300)
}
} else if (self.canShoot && self.row === zombie.row) { // 当植物可发射子弹,且僵尸和植物处于同行时
// 僵尸进入植物射程范围
zombie.x < 940 && self.x < zombie.x + 10 && zombie.life > 0 ? self.changeAnimation('attack') : self.changeAnimation('idle')
// 植物未被移除时,可发射子弹
if (!self.isDel) {
self.bullets.forEach(function (bullet, j) {
// 当子弹打中僵尸,且僵尸未死亡时
if (Math.abs(zombie.x + bullet.w - bullet.x) < 10 && zombie.life > 0) { // 子弹和僵尸距离小于 10 且僵尸未死亡
// 移除子弹
self.bullets.splice(j, 1)
// 根据血量判断执行不同阶段动画
if (zombie.life !== 0) {
zombie.life--
zombie.isHurt = true
setTimeout(()=> {
zombie.isHurt = false
}, 200)
}
if (zombie.life === 2) {
zombie.changeAnimation('dying')
} else if (zombie.life === 0) {
zombie.changeAnimation('die')
}
}
})
}
}
}
}
// 射击方法
shoot () {
let self = this
self.bullets[self.bullets.length] = Bullet.new(self)
}
/**
* 判断角色状态并返回对应动画对象名称方法
*/
switchState () {
let self = this,
state = self.state,
dictionary = {
idle: self.state_IDLE,
idleH: self.state_IDLE_H,
idleM: self.state_IDLE_M,
idleL: self.state_IDLE_L,
attack: self.state_ATTACK,
digest: self.state_DIGEST,
}
for (let key in dictionary) {
if (state === dictionary[key]) {
return key
}
}
}
/**
* 切换角色动画
* game => 游戏引擎对象
* action => 动作类型
* -idle: 站立动画
* -idleH: 角色高血量动画(坚果墙)
* -idleM: 角色中等血量动画(坚果墙)
* -idleL: 角色低血量动画(坚果墙)
* -attack: 攻击动画
* -digest: 消化动画(食人花)
*/
changeAnimation (action) {
let self = this,
stateName = self.switchState(),
dictionary = {
idle: self.state_IDLE,
idleH: self.state_IDLE_H,
idleM: self.state_IDLE_M,
idleL: self.state_IDLE_L,
attack: self.state_ATTACK,
digest: self.state_DIGEST,
}
if (action === stateName) return
self.state = dictionary[action]
}
}
// 僵尸类
class Zombie extends Role{
constructor (obj) {
super(obj)
// 僵尸类私有属性
let z = {
life: 10, // 角色血量
canMove: true, // 判断当前角色是否可移动
attackPlantID: 0, // 当前攻击植物对象 ID
idle: null, // 站立动画对象
run: null, // 奔跑动画对象
attack: null, // 攻击动画对象
dieboom: null, // 被炸死亡动画对象
dying: null, // 濒临死亡动画对象
die: null, // 死亡动画对象
state: 1, // 保存当前状态值,默认为1
state_IDLE: 1, // 站立不动状态
state_RUN: 2, // 奔跑状态
state_ATTACK: 3, // 攻击状态
state_DIEBOOM: 4, // 死亡状态
state_DYING: 5, // 濒临死亡状态
state_DIE: 6, // 死亡状态
state_DIGEST: 7, // 消化死亡状态
speed: 3, // 移动速度
head_x: 0, // 头部动画 x 轴坐标
head_y: 0, // 头部动画 y 轴坐标
}
Object.assign(this, z)
}
// 创建,并初始化当前对象
static new (obj) {
let p = new this(obj)
p.init()
return p
}
// 初始化
init () {
let self = this
// 站立
self.idle = Animation.new(self, 'idle', 12)
// 移动
self.run = Animation.new(self, 'run', 12)
// 攻击
self.attack = Animation.new(self, 'attack', 8)
// 炸死
self.dieboom = Animation.new(self, 'dieboom', 8)
// 濒死
self.dying = Animation.new(self, 'dying', 8)
// 死亡
self.die = Animation.new(self, 'die', 12)
}
// 绘制方法
draw (cxt) {
let self = this,
stateName = self.switchState()
if (stateName !== 'dying' && stateName !== 'die') { // 绘制普通动画
if (!self.isHurt) { // 未受伤时,绘制正常动画
cxt.drawImage(self[stateName].img, self.x, self.y)
} else { // 受伤时,绘制带透明度动画
// 绘制带透明度动画
cxt.globalAlpha = 0.5
cxt.beginPath()
cxt.drawImage(self[stateName].img, self.x, self.y)
cxt.closePath()
cxt.save()
cxt.globalAlpha = 1
}
} else { // 绘制濒死、死亡动画
if (!self.isHurt) { // 未受伤时,绘制正常动画
cxt.drawImage(self[stateName].imgHead, self.head_x + 70, self.head_y - 10)
cxt.drawImage(self[stateName].imgBody, self.x, self.y)
} else { // 受伤时,绘制带透明度动画
// 绘制带透明度身体
cxt.globalAlpha = 0.5
cxt.beginPath()
cxt.drawImage(self[stateName].imgBody, self.x, self.y)
cxt.closePath()
cxt.save()
cxt.globalAlpha = 1
// 头部不带透明度
cxt.drawImage(self[stateName].imgHead, self.head_x + 70, self.head_y - 10)
}
}
}
// 更新状态
update (game) {
let self = this,
stateName = self.switchState()
// 更新能否移动状态值
self.canMove ? self.speed = 3 : self.speed = 0
// 更新僵尸列坐标值
self.col = Math.floor((self.x - window._main.zombies_info.x) / 80 + 1)
if (stateName !== 'dying' && stateName !== 'die') { // 普通动画(站立,移动,攻击)
// 修改当前动画序列长度
let animateLen = allImg.zombies[stateName].len
// 累加动画计数器
self[stateName].count += 1
// 设置角色动画运行速度
self[stateName].imgIdx = Math.floor(self[stateName].count / self[stateName].fps)
// 一整套动画完成后重置动画计数器
if (self[stateName].imgIdx === animateLen) {
self[stateName].count = 0
self[stateName].imgIdx = 0
if (stateName === 'dieboom') { // 被炸死亡状态
// 当死亡动画执行完一轮后,移除当前角色
self.isDel = true
}
// 当前动画帧数达到最大值
self.isAnimeLenMax = true
} else {
self.isAnimeLenMax = false
}
// 游戏运行状态
if (game.state === game.state_RUNNING) {
// 设置当前帧动画对象
self[stateName].img = self[stateName].images[self[stateName].imgIdx]
if (stateName === 'run') { // 当僵尸移动时,控制移动速度
self.x -= self.speed / 17
}
}
} else if (stateName === 'dying') { // 濒死动画,包含两个动画对象
// 获取当前动画序列长度
let headAnimateLen = allImg.zombies[stateName].head.len,
bodyAnimateLen = allImg.zombies[stateName].body.len
// 累加动画计数器
if (self[stateName].imgIdxHead !== headAnimateLen - 1) {
self[stateName].countHead += 1
}
self[stateName].countBody += 1
// 设置角色动画运行速度
self[stateName].imgIdxHead = Math.floor(self[stateName].countHead / self[stateName].fps)
self[stateName].imgIdxBody = Math.floor(self[stateName].countBody / self[stateName].fps)
// 设置当前帧动画对象,头部动画
if (self[stateName].imgIdxHead === 0) {
self.head_x = self.x
self.head_y = self.y
self[stateName].imgHead = self[stateName].images.head[self[stateName].imgIdxHead]
} else if (self[stateName].imgIdxHead === headAnimateLen) {
self[stateName].imgHead = self[stateName].images.head[headAnimateLen - 1]
} else {
self[stateName].imgHead = self[stateName].images.head[self[stateName].imgIdxHead]
}
// 设置当前帧动画对象,身体动画
if (self[stateName].imgIdxBody === bodyAnimateLen) {
self[stateName].countBody = 0
self[stateName].imgIdxBody = 0
// 当前动画帧数达到最大值
self.isAnimeLenMax = true
} else {
self.isAnimeLenMax = false
}
// 游戏运行状态
if (game.state === game.state_RUNNING) {
// 设置当前帧动画对象
self[stateName].imgBody = self[stateName].images.body[self[stateName].imgIdxBody]
if (stateName === 'dying') { // 濒死状态,可以移动
self.x -= self.speed / 17
}
}
} else if (stateName === 'die') { // 死亡动画,包含两个动画对象
// 获取当前动画序列长度
let headAnimateLen = allImg.zombies[stateName].head.len,
bodyAnimateLen = allImg.zombies[stateName].body.len
// 累加动画计数器
if (self[stateName].imgIdxBody !== bodyAnimateLen - 1) {
self[stateName].countBody += 1
}
// 设置角色动画运行速度
self[stateName].imgIdxBody = Math.floor(self[stateName].countBody / self[stateName].fps)
// 设置当前帧动画对象,死亡状态,定格头部动画
if (self[stateName].imgIdxHead === 0) {
if (self.head_x == 0 && self.head_y == 0) {
self.head_x = self.x
self.head_y = self.y
}
self[stateName].imgHead = self[stateName].images.head[headAnimateLen - 1]
}
// 设置当前帧动画对象,身体动画
if (self[stateName].imgIdxBody === 0) {
self[stateName].imgBody = self[stateName].images.body[self[stateName].imgIdxBody]
} else if (self[stateName].imgIdxBody === bodyAnimateLen - 1) {
// 当死亡动画执行完一轮后,移除当前角色
self.isDel = true
self[stateName].imgBody = self[stateName].images.body[bodyAnimateLen - 1]
} else {
self[stateName].imgBody = self[stateName].images.body[self[stateName].imgIdxBody]
}
}
}
// 检测僵尸是否可攻击植物
canAttack () {
let self = this
// 循环植物对象数组
for (let plant of window._main.plants) {
if (plant.row === self.row && !plant.isDel) { // 当僵尸和植物处于同行时
if (self.x - plant.x < -20 && self.x - plant.x > -60) {
if (self.life > 2) {
// 保存当前攻击植物 hash 值,在该植物被删除时,再控制当前僵尸移动
self.attackPlantID !== plant.id ? self.attackPlantID = plant.id : self.attackPlantID = self.attackPlantID
self.changeAnimation('attack')
} else {
self.canMove = false
}
if (self.isAnimeLenMax && self.life > 2) { // 僵尸动画每执行完一轮次
// 扣除植物血量
if (plant.life !== 0) {
plant.life--
plant.isHurt = true
setTimeout(()=> {
plant.isHurt = false
// 坚果墙判断切换动画状态
if (plant.life <= 8 && plant.section === 'wallnut') {
plant.life <= 4 ? plant.changeAnimation('idleL') : plant.changeAnimation('idleM')
}
// 判断植物是否可移除
if (plant.life <= 0) {
// 设置植物死亡状态
plant.isDel = true
// 清除死亡向日葵的阳光生成定时器
plant.section === 'sunflower' && plant.clearSunTimer()
}
}, 200)
}
}
}
}
}
}
/**
* 判断角色状态并返回对应动画对象名称方法
*/
switchState () {
let self = this,
state = self.state,
dictionary = {
idle: self.state_IDLE,
run: self.state_RUN,
attack: self.state_ATTACK,
dieboom: self.state_DIEBOOM,
dying: self.state_DYING,
die: self.state_DIE,
digest: self.state_DIGEST,
}
for (let key in dictionary) {
if (state === dictionary[key]) {
return key
}
}
}
/**
* 切换角色动画
* game => 游戏引擎对象
* action => 动作类型
* -idle: 站立不动
* -attack: 攻击
* -die: 死亡
* -dying: 濒死
* -dieboom: 爆炸
* -digest: 被消化
*/
changeAnimation (action) {
let self = this,
stateName = self.switchState(),
dictionary = {
idle: self.state_IDLE,
run: self.state_RUN,
attack: self.state_ATTACK,
dieboom: self.state_DIEBOOM,
dying: self.state_DYING,
die: self.state_DIE,
digest: self.state_DIGEST,
}
if (action === stateName) return
self.state = dictionary[action]
}
}
游戏引擎
class Game {
constructor (){
let g = {
actions: {}, // 注册按键操作
keydowns: {}, // 按键事件对象
cardSunVal: null, // 当前选中植物卡片index以及需消耗阳光值
cardSection: '', // 绘制随鼠标移动植物类别
canDrawMousePlant: false, // 能否绘制随鼠标移动植物
canLayUp: false, // 能否放置植物
mousePlant: null, // 鼠标绘制植物对象
mouseX: 0, // 鼠标 x 轴坐标
mouseY: 0, // 鼠标 y 轴坐标
mouseRow: 0, // 鼠标移动至可种植植物区域的行坐标
mouseCol: 0, // 鼠标移动至可种植植物区域的列坐标
state: 0, // 游戏状态值,初始默认为 0
state_LOADING: 0, // 准备阶段
state_START: 1, // 开始游戏
state_RUNNING: 2, // 游戏开始运行
state_STOP: 3, // 暂停游戏
state_PLANTWON: 4, // 游戏结束,玩家胜利
state_ZOMBIEWON: 5, // 游戏结束,僵尸胜利
canvas: document.getElementById("canvas"), // canvas元素
context: document.getElementById("canvas").getContext("2d"), // canvas画布
timer: null, // 轮询定时器
fps: window._main.fps, // 动画帧数
}
Object.assign(this,g)
}
static new(){
let g=new this()
g.init()
return g
}
// clearGameTimer(){
// let g=this
// clearInterval(g.timer)
// }
drawBg(){
let g=this,cxt=g.context,sunnum=window._main.sunnum,cards=window._main.cards,img=imageFromPath(allImg.bg)
cxt.drawImage(img,0,0)
sunnum.draw(cxt)
}
drawCars(){
let g=this,cxt=g.context,cars=window._main.cars
cars.forEach((car,idx)=>{
if(car.x>950){
cars.splice(idx,1)
}
car.draw(g,cxt)
})
}
drawCards(){
let g=this,cxt=g.context,cards=window._main.cards
for(let card of cards){
card.draw(cxt)
}
}
drawPlantWon(){
let g=this,cxt=g.context,text='恭喜玩家获得胜利!'
cxt.fillStyle='red'
cxt.font='48px Microsoft YaHei'
cxt.fillText(text,354,300)
}
drawZombieWon(){
let g=this,cxt=g.context,img=imageFromPath(allImg.zombieWon)
cxt.drawImage(img,293,66)
}
drawLoading(){
let g=this,cxt=g.context,img=imageFromPath(allImg.startBg)
cxt.drawImage(img,119,0)
}
drawStartAnime(){
let g=this,stateName='write',loading=window._main.loading,cxt=g.context,canvas_w=g.canvas.width,canvas_h=g.canvas.height,
animateLen=allImg.loading[stateName].len
if(loading.imgIdx!=animateLen){
loading.count+=1
}
loading.imgIdx=Math.floor(loading.count/loading.fps)
if(loading.imgIdx==animateLen){
loading.img=loading.images[loading.imgIdx-1]
}else{
loading.img=loading.images[loading.imgIdx]
}
cxt.drawImage(loading.img,437,246)
}
drawBullets(plants){
let g=this,context = g.context, canvas_w = g.canvas.width - 440
for(let item of plants){
item.bullets.forEach((bullet,idx,arr)=>{
bullet.draw(g,context)
if(bullet.x>=canvas_w){
arr.splice(idx,1)
}
})
}
}
drawBlood (role) {
let g = this,cxt = g.context,x = role.x,y = role.y
cxt.fillStyle = 'red'
cxt.font = '18px Microsoft YaHei'
if(role.type === 'plant'){
cxt.fillText(role.life, x + 30, y - 10)
}else if(role.type === 'zombie') {
cxt.fillText(role.life, x + 85, y + 10)
}
}
updateImage(plants,zombies){
let g = this,cxt = g.context
plants.forEach((plant, idx)=>{ plant.canAttack()
plant.update(g)
})
zombies.forEach((zombie, idx)=>{
if (zombie.x < 50){
g.state = g.state_ZOMBIEWON
}
zombie.canAttack()
zombie.update(g)
})
}
drawImage (plants, zombies){
let g = this,cxt = g.context, delPlantsArr = []
plants.forEach((plant, idx, arr)=>{
if(plant.isDel){
delPlantsArr.push(plant)
arr.splice(idx,1)
}else{
plant.draw(cxt)
// g.drawBlood(plant)
}
})
zombies.forEach(function (zombie, idx) {
if(zombie.isDel){
zombies.splice(idx, 1)
if(zombies.length === 0) {
g.state = g.state_PLANTWON
}
}else{
zombie.draw(cxt)
// g.drawBlood(zombie)
}
for(let plant of delPlantsArr) {
if(zombie.attackPlantID === plant.id) {
zombie.canMove = true
if(zombie.life > 2){
zombie.changeAnimation('run')
}
}
}
})
}
getMousePos(){
let g = this,_main=window._main,cxt=g.context,cards=_main.cards,x=g.mouseX,y=g.mouseY
if(g.canDrawMousePlant){
g.mousePlantCallback(x,y)
}
}
drawMousePlant(plant_info){
let g = this,cxt = g.context,plant = null
let mousePlant_info={
type:'plant',
section:g.cardSection,
x: g.mouseX + 82,
y: g.mouseY - 40,
row: g.mouseRow,
col: g.mouseCol,
}
if(g.canLayUp){
plant=Plant.new(plant_info)
plant.isHurt=true
plant.update(g)
plant.draw(cxt)
}
g.mousePlant = Plant.new(mousePlant_info)
g.mousePlant.update(g)
g.mousePlant.draw(cxt)
}
mousePlantCallback(x,y){
let g = this,_main = window._main,cxt = g.context, row = Math.floor((y - 75) / 100) + 1, col = Math.floor((x - 175) / 80) + 1
let plant_info={
type:'plant' ,
section: g.cardSection,
x: _main.plants_info.x + 80 * (col - 1),
y: _main.plants_info.y + 100 * (row - 1),
row: row,
col: col,
}
g.mouseRow = row
g.mouseCol = col
if(row>=1&&row<=5&&col>=1&&col<=9){
g.canLayUp=true
for(let plant of _main.plants){
if(row==plant.row&&col==plant.col){
g.canLayUp=false
}
}
}else{
g.canLayUp=false
}
if(g.canDrawMousePlant){
g.drawMousePlant(plant_info)
}
}
registerAction (key, callback) {
this.actions[key] = callback
}
setTimer(_main) {
let g = this,plants = _main.plants,zombies = _main.zombies
let actions = Object.keys(g.actions)
for (let i = 0; i < actions.length; i++) {
let key = actions[i]
if (g.keydowns[key]) {
g.actions[key]()
}
}
g.context.clearRect(0, 0, g.canvas.width, g.canvas.height)
if (g.state === g.state_LOADING) {
g.drawLoading()
} else if (g.state === g.state_START) {
g.drawBg()
g.drawCars()
g.drawCards()
g.drawStartAnime()
} else if (g.state === g.state_RUNNING) {
g.drawBg()
g.updateImage(plants, zombies)
g.drawImage(plants, zombies)
g.drawCars()
g.drawCards()
g.drawBullets(plants)
g.getMousePos()
} else if (g.state === g.state_STOP) {
g.drawBg()
g.updateImage(plants, zombies)
g.drawImage(plants, zombies)
g.drawCars()
g.drawCards()
g.drawBullets(plants)
_main.clearTiemr()
} else if (g.state === g.state_PLANTWON) {
g.drawBg()
g.drawCars()
g.drawCards()
g.drawPlantWon()
_main.clearTiemr()
} else if (g.state === g.state_ZOMBIEWON) {
g.drawBg()
g.drawCars()
g.drawCards()
g.drawZombieWon()
_main.clearTiemr()
}
}
//========================================================================
init(){
let g=this,_main=window._main
// window.addEventListener('keydown', function (event) {
// g.keydowns[event.keyCode] = 'down'
// })
// window.addEventListener('keyup', function (event) {
// g.keydowns[event.keyCode] = 'up'
// })
g.registerAction = function (key, callback) {
g.actions[key] = callback
}
g.timer = setInterval(function () {
g.setTimer(_main)
}, 1000/g.fps)
document.getElementById('canvas').onmousemove = function (event) {
let e = event || window.event,
scrollX = document.documentElement.scrollLeft || document.body.scrollLeft,
scrollY = document.documentElement.scrollTop || document.body.scrollTop,
x = e.pageX || e.clientX + scrollX,
y = e.pageY || e.clientY + scrollY
g.mouseX = x
g.mouseY = y
}
document.getElementById('js-startGame-btn').onclick = function () {
g.state = g.state_START
setTimeout(function () {
g.state = g.state_RUNNING
document.getElementById('pauseGame').className += ' show'
document.getElementById('restartGame').className += ' show'
_main.clearTiemr()
_main.setTimer()
}, 2500)
document.getElementsByClassName('cards-list')[0].className += ' show'
document.getElementsByClassName('menu-box')[0].className += ' show'
document.getElementById('js-startGame-btn').style.display = 'none'
document.getElementById('js-intro-game').style.display = 'none'
document.getElementById('js-log-btn').style.display = 'none'
}
document.querySelectorAll('.cards-item').forEach(function (card, idx) {
card.onclick = function () {
let plant = null,cards = _main.cards
if (cards[idx].canClick) {
g.cardSection = this.dataset.section
g.canDrawMousePlant = true
g.cardSunVal = {
idx: idx,
val: cards[idx].sun_val,
}
}
}
})
document.getElementById('canvas').onclick = function (event) {
let plant = null,cards = _main.cards,x = g.mouseX,y = g.mouseY,
plant_info = {
type: 'plant',
section: g.cardSection,
x: _main.plants_info.x + 80 * (g.mouseCol - 1),
y: _main.plants_info.y + 100 * (g.mouseRow - 1),
row: g.mouseRow,
col: g.mouseCol,
canSetTimer: g.cardSection === 'sunflower' ? true : false,
}
for (let item of _main.plants){
if(g.mouseRow === item.row && g.mouseCol === item.col) {
g.canLayUp = false
g.mousePlant = null
}
}
if (g.canLayUp && g.canDrawMousePlant) {
let cardSunVal = g.cardSunVal
if (cardSunVal.val <= _main.allSunVal) {
cards[cardSunVal.idx].canClick = false
cards[cardSunVal.idx].changeState()
cards[cardSunVal.idx].drawCountDown()
plant = Plant.new(plant_info)
_main.plants.push(plant)
_main.sunnum.changeSunNum(-cardSunVal.val)
g.canDrawMousePlant = false
} else {
g.canDrawMousePlant = false
g.mousePlant = null
}
} else {
g.canDrawMousePlant = false
g.mousePlant = null
}
}
document.getElementById('pauseGame').onclick = function (event) {
g.state = g.state_STOP
}
document.getElementById('restartGame').onclick = function (event) {
if (g.state === g.state_LOADING) {
g.state = g.state_START
}else{
g.state = g.state_RUNNING
for (let plant of _main.plants) {
if (plant.section === 'sunflower') {
plant.setSunTimer()
}
}
}
_main.setTimer()
}
}
}
主程序入口
class Main{
constructor(){
let m={
allSunVal:200, // 阳光总数量
loading:null, // loading 动画对象
sunnum:null, // 阳光实例对象
cars:[], // 实例化除草车对象数组
cars_info:{ // 初始化参数
x:170, // x 轴坐标
y:102, // y 轴坐标
position:[
{row:1},
{row:2},
{row:3},
{row:4},
{row:5},
],
},
cards:[],
cards_info:{
x:0,
y:0,
position:[
{name: 'sunflower', row: 1, sun_val: 50, timer_spacing: 5 * 1000},
{name: 'wallnut', row: 2, sun_val: 50, timer_spacing: 12 * 1000},
{name: 'peashooter', row: 3, sun_val: 100, timer_spacing: 7 * 1000},
{name: 'repeater', row: 4, sun_val: 150, timer_spacing: 10 * 1000},
{name: 'gatlingpea', row: 5, sun_val: 200, timer_spacing: 15 * 1000},
{name: 'chomper', row: 6, sun_val: 200, timer_spacing: 15 * 1000},
{name: 'cherrybomb', row: 7, sun_val: 250, timer_spacing: 25 * 1000},
]
},
plants:[],
zombies:[],
plants_info:{
type:'plant',
x:250,
y:92,
position:[]
},
zombies_info:{
type:'zombie',
x:170,
y:15,
position:[]
},
zombies_idx: 0,
zombies_row: 0,
zombies_iMax: 50,
sunTimer: null,
sunTimer_difference: 20,
zombieTimer: null,
zombieTimer_difference: 12,
game: null,
fps: 60,
}
Object.assign(this,m)
}
setZombiesInfo () {
let self = this,
iMax = self.zombies_iMax
for(let i = 0; i < iMax; i++) {
let row = Math.ceil(Math.random() * 4 + 1)
self.zombies_info.position.push({
section: 'zombie',
row: row,
col: 11 + Number(Math.random().toFixed(1))
})
}
}
clearTiemr(){
let self=this
clearInterval(self.sunTimer)
clearInterval(self.zombieTimer)
for(let plant of self.plants){
if(plant.section=='sunflower'){
plant.clearSunTimer()
}
}
}
// 设置全局阳光、僵尸生成定时器
setTimer(){
let self=this,zombies=self.zombies
self.sunTimer = setInterval(function () {
let left = parseInt(window.getComputedStyle(document.getElementsByClassName('systemSun')[0],null).left), // 获取当前元素left值
top = '-100px',
keyframes1 = [
{ transform: 'translate(0,0)', opacity: 0 },
{ offset: .5,transform: 'translate(0,300px)', opacity: 1 },
{ offset: .75,transform: 'translate(0,300px)', opacity: 1 },
{ offset: 1,transform: 'translate(-'+ (left - 110) +'px,50px)',opacity: 0 }
]
document.getElementsByClassName('systemSun')[0].animate(keyframes1,keyframesOptions)
setTimeout(function () {
self.sunnum.changeSunNum()
document.getElementsByClassName('systemSun')[0].style.left = Math.floor(Math.random() * 200 + 300) + 'px'
document.getElementsByClassName('systemSun')[0].style.top = '-100px'
}, 2700)
}, 1000 * self.sunTimer_difference)
self.zombieTimer = setInterval(function () {
let idx = self.zombies_iMax - self.zombies_idx - 1
if(self.zombies_idx === self.zombies_iMax) { // 僵尸生成数量达到最大值,清除定时器
return clearInterval(self.zombieTimer)
}
if(self.zombies[idx]) {
self.zombies[idx].state = self.zombies[idx].state_RUN
}
self.zombies_idx++
},1000 * self.zombieTimer_difference)
}
setCars(cars_info){
let self=this
for(let car of cars_info.position){
let info={
x: cars_info.x,
y: cars_info.y + 100 * (car.row - 1),
row: car.row,
}
self.cars.push(Car.new(info))
}
}
setCards(cards_info){
let self=this
for (let card of cards_info.position) {
let info={
name:card.name,
row:card.row,
sun_val:card.sun_val,
timer_spacing: card.timer_spacing,
y: cards_info.y + 60 * (card.row - 1),
}
self.cards.push(Card.new(info))
}
}
//palnt or zombie
setRoles(roles_info){
let self=this,type = roles_info.type
for (let role of roles_info.position){
let info = {
type: roles_info.type,
section: role.section,
x: roles_info.x + 80 * (role.col - 1),
y: roles_info.y + 100 * (role.row - 1),
col: role.col,
row: role.row,
}
if(type==='plant'){
self.plants.push(Plant.new(info))
}else if(type==='zombie'){
self.zombies.push(Zombie.new(info))
}
}
}
//===========================================
start(){
let self=this
self.loading = Animation.new({type: 'loading'}, 'write', 55)
self.sunnum = SunNum.new()
self.setZombiesInfo()
self.setCars(self.cars_info)
self.setCards(self.cards_info)
self.setRoles(self.plants_info)
self.setRoles(self.zombies_info)
self.game = Game.new()
}
}
window._main=new Main()
window._main.start()
只对JS中常见的DOM/BOM和基础语法进行巩固,后续的CSS代码和相关图片资源也会上传
感谢大家的点赞和关注,你们的支持是我创作的动力!