爆炸效果實現
爆炸效果主要爲8張gif圖片依次切換顯示組成。
src/model/abstract/AbstractModel.ts
......
/**
* 爆炸效果實現
* 原理:8張gif圖片依次切換顯示。
* @param model 被打炸的模型
* @protected
*/
protected blast(model: IModel) {
//生成0-7的數組
let list = Array(...Array(8).keys())
// console.log(list)
// 一個Promise調用完,再調用另一個Promise
// 圖片是異步加載的
//Array.reduce 接收一個函數作爲累加器,數組中的每個值(從左到右)開始縮減,最終計算爲一個值。
// 第一個參數爲function(total,currentValue, index,arr)
// total 必需。初始值, 或者計算結束後的返回值。
// currentValue 必需。當前元素
// currentIndex 可選。當前元素的索引
// arr 可選。當前元素所屬的數組對象。
// 第二個參數爲傳遞給函數的初始值
list.reduce((promise, index) => {
return new Promise(resolve => {
// 定時器,每個圖片的切換都有一定的延時
setTimeout(()=>{
const img = new Image
img.src = `src/static/images/blasts/blast${index}.gif`
// 圖片加載完畢的監聽方法
img.onload = () => {
//渲染圖片
this.canvas.ctx.drawImage(img, model.x, model.y, model.width, model.height)
// 跳出結束Promise
resolve(promise)
}
},100)
})
}, Promise.resolve())
}
......
src/model/Bullet.ts
......
// 一些初始化自定義的動作、行爲,都在這裏進行
render(): void {
// super.draw()
// ********************* 座標更新 *********************
let x = this.x;
let y = this.y;
switch (this.direction) {
case EnumDirection.top:
y--
break;
case EnumDirection.right:
x++
break;
case EnumDirection.bottom:
y++
break;
case EnumDirection.left:
x--
break;
}
// // ********************* 座標更新 *********************
let touchNotBlowModel = utils.modelTouch(x, y, [
...wallSteel.models,// 鋼牆
],
config.model.bullet.width,
config.model.bullet.height
)
let touchBlowModel = utils.modelTouch(x, y, [
...wallBrick.models,// 磚牆
],
config.model.bullet.width,
config.model.bullet.height
)
//
if (touchNotBlowModel || utils.isCanvasTouch(x, y, config.model.bullet.width, config.model.bullet.height)) {
// 移除模型
this.destroy()
// 展示爆炸效果
touchNotBlowModel && super.blast(touchNotBlowModel)
} else if (touchBlowModel) {
// 當前子彈模型消失
this.destroy()
// 展示爆炸效果
super.blast(touchBlowModel)
// 碰撞的模型消失
touchBlowModel.destroy();
} else {
this.x = x;
this.y = y;
// 畫布重繪, 渲染坦克模型,在這裏調用減少重繪次數
this.draw()
}
}
......
老巢磚塊
src/service/position.ts
......
// 返回隨機位置
public position() {
let x: number, y: number;
let gridNumX = config.canvas.width / config.model.common.width;
// 隨機格子數量
let leftNumX = Math.floor(Math.random() * gridNumX)
//轉換成px
x = leftNumX * config.model.common.width
let gridNumY = config.canvas.height / config.model.common.height;
// 隨機格子數量
let leftNumY = Math.floor(Math.random() * (gridNumY - 4))
//轉換成px,且頂部空出一格
y = (leftNumY + 1) * config.model.common.height
return {x, y}
}
......
src/config.ts
......
// 敵方坦克
tank: {
num: 40,// 數量
speed: 40 // 速度,越小越快
},
// 子彈的速度
bullet: {
num: 40,// 數量
speed: 10 // 速度,越小越快
},
......
src/canvas/WallBrick.ts
render(): void {
// super:調用父類的方法
super.createModels()
// 創建老巢的牆
this.createBossWall();
// 調用渲染模型,防止每次重新渲染時,又生成新的模型實例
super.renderModels();
}
// 創建老巢的牆
createBossWall() {
const casW = config.canvas.width
const casH = config.canvas.height
const modelW = config.model.common.width
const modelH = config.model.common.height
let pos = [
{x: casW / 2 - modelW * 3, y: casH - modelH},
{x: casW / 2 - modelW * 3, y: casH - modelH * 2},
{x: casW / 2 - modelW * 3, y: casH - modelH * 3},
{x: casW / 2 - modelW * 2, y: casH - modelH * 3},
{x: casW / 2 - modelW, y: casH - modelH * 3},
{x: casW / 2, y: casH - modelH * 3},
{x: casW / 2 + modelW, y: casH - modelH * 3},
{x: casW / 2 + modelW, y: casH - modelH * 2},
{x: casW / 2 + modelW, y: casH - modelH},
{x: casW / 2 + modelW, y: casH},
]
pos.forEach(position => {
const model = this.model() as ConstructorModel
const instance = new model(position.x, position.y)
this.models.push(instance)
})
}
boss老巢
src/config.ts
// 草地
import imgUrlStraw from './static/images/straw/straw.png'
// 磚牆
import imgUrlWallBrick from './static/images/wall/wall.gif'
// 水域
import imgUrlWater from './static/images/water/water.gif'
// 鋼牆
import imgUrlWallSteel from './static/images/wall/steels.gif'
// 坦克
import imgUrlTankTop from './static/images/tank/top.gif'
import imgUrlTankRight from './static/images/tank/right.gif'
import imgUrlTankLeft from './static/images/tank/left.gif'
import imgUrlTankBottom from './static/images/tank/bottom.gif'
// 子彈
import imgUrlBullet from './static/images/bullet/bullet.jpg'
// boss,己方老巢
import imgUrlBoss from './static/images/boss/boss.png'
export default {
// 畫布
canvas: {
width: 900,
height: 600,
},
// 模型
model: {
common: {
width: 30,
height: 30,
},
// 子彈
bullet: {
width: 3,
height: 3,
},
// boss,己方老巢
// boss:{
// width: 60,
// height: 60,
// }
},
//草地
straw: {
num: 100,
},
// 磚牆
wallBrick: {
num: 100,
},
// 鋼牆
wallSteel: {
num: 30,
},
// 水域
water: {
num: 40
},
// 敵方坦克
tank: {
num: 40,// 數量
speed: 10 // 速度,越小越快
},
// 子彈的速度
bullet: {
num: 40,// 數量
speed: 10 // 速度,越小越快
},
// 圖片
images: {
// 草地
straw: imgUrlStraw,
// 磚牆
wallBrick: imgUrlWallBrick,
// 鋼牆
wallSteel: imgUrlWallSteel,
// 水域
water: imgUrlWater,
// 敵方坦克
tankTop: imgUrlTankTop,
tankRight: imgUrlTankRight,
tankBottom: imgUrlTankBottom,
tankLeft: imgUrlTankLeft,
// 子彈
bullet: imgUrlBullet,
// boss,己方老巢
boss:imgUrlBoss
}
}
src/main.ts
import config from './config'
import './style.scss'
import canvasStraw from './canvas/Straw'
import canvasWallBrick from './canvas/WallBrick'
import canvasWater from './canvas/Water'
import canvasWallSteel from './canvas/WallSteel'
import canvasTank from './canvas/Tank'
import {promises} from "./service/image";
import bullet from "./canvas/Bullet";
import boss from "./canvas/Boss";
const app = document.querySelector<HTMLDivElement>("#app")
// @ts-ignore
app.style.width = config.canvas.width + 'px'
// @ts-ignore
app.style.height = config.canvas.height + 'px'
const bootstrap = async () => {
// console.log(promises)
//先加載各種貼圖
await Promise.all(promises)
// console.log(image.get('straw'))
// 調用render方法渲染
canvasStraw.render() // 畫布渲染:草地
canvasWallBrick.render() // 畫布渲染:磚牆
canvasWallSteel.render() // 畫布渲染:鋼牆
canvasTank.render() // 畫布渲染:敵方坦克
canvasWater.render() // 畫布渲染:水域
bullet.render() // 畫布渲染:子彈
boss.render() // 畫布渲染:boss,老巢
}
void bootstrap()
src/style.scss
body {
background-color: #000;
//視圖的寬度
width: 100vw;
//視圖的高度
height: 100vh;
display: flex;
/*主軸*/
justify-content: center;
/*交叉軸*/
align-items: center;
//div畫布默認就是居中
#app {
background-color: #000;
position: relative;
border: solid 12px #333;
//默認值。如果你設置一個元素的寬爲 100px,
// 那麼這個元素的內容區會有 100px 寬,
// 並且任何邊框和內邊距的寬度都會被增加到最後繪製出來的元素寬度中
box-sizing: content-box;
canvas {
position: absolute;
}
}
}
src/canvas/Boss.ts
/**
* 畫布
* 草地
*/
import AbstractCanvas from "./abstract/AbstractCanvas";
import ModelBoss from "../model/Boss";
import config from "../config";
/**
* 畫布是單例模式
* 在一個圖層,所以只需要new一個實例即可。
*/
export default new (class extends AbstractCanvas implements ICanvas {
render(): void {
// super:調用父類的方法
this.createModels()
// 調用渲染模型,防止每次重新渲染時,又生成新的模型實例
super.renderModels();
}
createModels() {
const casW = config.canvas.width
const casH = config.canvas.height
const modelW = config.model.common.width
const modelH = config.model.common.height
let position = {x: casW / 2 - modelW, y: casH - modelH}
const model = this.model() as ConstructorModel
const instance = new model(position.x, position.y)
this.models.push(instance)
}
// 抽象方法,返回模型
model(): ConstructorModel {
return ModelBoss;
}
// 抽象方法:返回模型數量,這裏不用
num(): number {
return 0;
}
})('boss')
src/model/Boss.ts
/**
* 模型
* 草地
*/
import AbstractModel from "./abstract/AbstractModel";
import {image} from "../service/image";
import config from "../config";
import boss from "../canvas/Boss";
export default class ModelBoss extends AbstractModel implements IModel {
name: string = 'boss';
// 畫布實例
canvas: ICanvas = boss;
// 繼承父類抽象方法:渲染貼圖
// 一些初始化自定義的動作、行爲,都在這裏進行
render(): void {
super.draw()
}
// 獲取貼圖
getImage(): HTMLImageElement {
return image.get(this.name as keyof typeof config.images)!;
}
}
src/model/abstract/AbstractModel.ts
import config from "../../config";
import {EnumDirection} from "../../enum/enumPosition";
/**
* 抽象類
*/
export default abstract class AbstractModel {
// 寬度,碰撞判斷需要跨模型調用,所以爲public
public width = config.model.common.width;
// 高度,碰撞判斷需要跨模型調用,所以爲public
public height = config.model.common.height;
// 抽象屬性:模型名稱
abstract name: string
// 抽象屬性:畫布實例
abstract canvas: ICanvas
// 方向,子彈的方向取決於坦克的方向,需要同層級調用
public direction: EnumDirection = EnumDirection.top
//構造函數渲染
// 碰撞判斷需要跨模型調用模型的座標位置,所以爲public
constructor(
public x: number,
public y: number
) {
// 方向隨機生成
this.randomDirection();
}
// 抽象方法:渲染貼圖
abstract render(): void
// 抽象方法:獲取貼圖
abstract getImage(): HTMLImageElement
// 方向隨機生成
randomDirection(): void {
// 隨機取一個
const index = Math.floor((Math.random() * 4))
// 存儲方向
this.direction = Object.keys(EnumDirection)[index] as EnumDirection
}
// 函數:卸載(移除)模型
public destroy(): void {
// 讓畫布將模型移除掉
this.canvas.removeModel(this)
// 重新渲染畫布
this.canvas.renderModels()
}
// 函數:渲染模型
protected draw(): void {
this.canvas.ctx.drawImage(
this.getImage(),
this.x,
this.y,
config.model.common.width,
config.model.common.height
)
}
/**
* 爆炸效果實現
* 原理:8張gif圖片依次切換顯示。
* @param model 被打炸的模型
* @protected
*/
protected blast(model: IModel) {
//生成0-7的數組
let list = Array(...Array(8).keys())
// console.log(list)
// 一個Promise調用完,再調用另一個Promise
// 圖片是異步加載的
//Array.reduce 接收一個函數作爲累加器,數組中的每個值(從左到右)開始縮減,最終計算爲一個值。
// 第一個參數爲function(total,currentValue, index,arr)
// total 必需。初始值, 或者計算結束後的返回值。
// currentValue 必需。當前元素
// currentIndex 可選。當前元素的索引
// arr 可選。當前元素所屬的數組對象。
// 第二個參數爲傳遞給函數的初始值
list.reduce((promise, index) => {
return new Promise(resolve => {
// 定時器,每個圖片的切換都有一定的延時
setTimeout(() => {
const img = new Image
img.src = `src/static/images/blasts/blast${index}.gif`
// 圖片加載完畢的監聽方法
img.onload = () => {
//渲染圖片
this.canvas.ctx.drawImage(img, model.x, model.y, model.width, model.height)
// 跳出結束Promise
resolve(promise)
}
}, 60)
})
// @ts-ignore
}, Promise.resolve()).then(promise => {
})
}
}
boss碰撞檢測
src/model/Bullet.ts
import wallSteel from "../canvas/WallSteel";
......
// 一些初始化自定義的動作、行爲,都在這裏進行
render(): void {
......
// 打不掉的障礙
let touchNotBlowModel = utils.modelTouch(x, y, [
...wallSteel.models,// 鋼牆
],
config.model.bullet.width,
config.model.bullet.height
)
// 打掉的障礙
let touchBlowModel = utils.modelTouch(x, y, [
...wallBrick.models,// 磚牆
...boss.models,// boss
],
config.model.bullet.width,
config.model.bullet.height
)
......
}
......