【Tank】9.0 创建玩家;玩家坦克控制、碰撞检测、子弹、坦克互战
创建玩家
首先加载贴图
src/config.ts
......
// 玩家坦克
import imgUrlPlayerTop from './static/images/player/top.gif'
import imgUrlPlayerRight from './static/images/player/right.gif'
import imgUrlPlayerBottom from './static/images/player/bottom.gif'
import imgUrlPlayerLeft from './static/images/player/left.gif'
export default {
......
// 图片
images: {
......
//玩家坦克
playerTop: imgUrlPlayerTop,
playerRight: imgUrlPlayerRight,
playerBottom: imgUrlPlayerBottom,
playerLeft: imgUrlPlayerLeft
}
}
从Tank.ts复制Player.ts
src/canvas/Player.ts
/**
* 模型
* 敌方坦克
*/
import AbstractModel from "./abstract/AbstractModel";
import {image} from "../service/image";
import {EnumDirection} from "../enum/enumPosition";
import {upperFirst} from 'lodash'
import config from "../config";
import water from "../canvas/Water";
import wallBrick from "../canvas/WallBrick";
import wallSteel from "../canvas/WallSteel";
import player from "../canvas/Player";
import utils from "../utils";
export default class ModelTank extends AbstractModel implements IModel {
name: string = 'player';
// 画布实例
canvas: ICanvas = player;
// 继承父类抽象方法:渲染贴图
// 一些初始化自定义的动作、行为,都在这里进行
render(): void {
// 让坦克动
this.move()
}
// 随机取用其中一个图片
getImage(): HTMLImageElement {
return image.get(`${this.name}${upperFirst(this.direction)}` as keyof typeof config.images)!
}
// 坦克行动
protected move(): void {
while (true) {
// 画布清空
// this.canvas.clearRect(this.x, this.y, config.model.common.width, config.model.common.height);
// ********************* 坐标更新 *********************
let x = this.x;
let y = this.y;
switch (this.direction) {
case EnumDirection.top:
y -= 2
break;
case EnumDirection.right:
x += 2
break;
case EnumDirection.bottom:
y += 2
break;
case EnumDirection.left:
x -= 2
break;
}
if (utils.modelTouch(x, y, [
...water.models,// 水域
...wallBrick.models,// 砖墙
...wallSteel.models,// 钢墙
]) || utils.isCanvasTouch(x, y)) {
// 随机获取方向
this.randomDirection()
} else {
this.x = x;
this.y = y;
// 跳出while死循环
break;
}
}
// ********************* 坐标更新 *********************
// 画布重绘, 渲染坦克模型,在这里调用减少重绘次数
super.draw()
}
}
src/model/Player.ts
/**
* 模型
* 敌方坦克
*/
import AbstractModel from "./abstract/AbstractModel";
import {image} from "../service/image";
import {EnumDirection} from "../enum/enumPosition";
import {random, upperFirst} from 'lodash'
import config from "../config";
import water from "../canvas/Water";
import wallBrick from "../canvas/WallBrick";
import wallSteel from "../canvas/WallSteel";
import player from "../canvas/Player";
import utils from "../utils";
export default class ModelTank extends AbstractModel implements IModel {
name: string = 'player';
// 画布实例
canvas: ICanvas = player;
// 继承父类抽象方法:渲染贴图
// 一些初始化自定义的动作、行为,都在这里进行
render(): void {
// 让坦克动起来:循环定时器
// setInterval(() => {
// this.move()
// }, 50)
// 随机转向
if (random(79) == 1) {
this.randomDirection()
}
// 让坦克动
this.move()
// 增加敌方坦克向下移动的概率
// if (Math.floor(Math.random() * 5) == 1) {
if (random(79) == 1) {
this.direction = EnumDirection.bottom
}
}
// 随机取用其中一个图片
getImage(): HTMLImageElement {
return image.get(`${this.name}${upperFirst(this.direction)}` as keyof typeof config.images)!
}
// 坦克行动
protected move(): void {
while (true) {
// 画布清空
// this.canvas.clearRect(this.x, this.y, config.model.common.width, config.model.common.height);
// ********************* 坐标更新 *********************
let x = this.x;
let y = this.y;
switch (this.direction) {
case EnumDirection.top:
y -= 2
break;
case EnumDirection.right:
x += 2
break;
case EnumDirection.bottom:
y += 2
break;
case EnumDirection.left:
x -= 2
break;
}
if (utils.modelTouch(x, y, [
...water.models,// 水域
...wallBrick.models,// 砖墙
...wallSteel.models,// 钢墙
]) || utils.isCanvasTouch(x, y)) {
// 随机获取方向
this.randomDirection()
} else {
this.x = x;
this.y = y;
// 跳出while死循环
break;
}
}
// ********************* 坐标更新 *********************
// 画布重绘, 渲染坦克模型,在这里调用减少重绘次数
super.draw()
}
}
src/main.ts
import player from "./canvas/Player";
......
const bootstrap = async () => {
......
player.render() // 画布渲染:玩家坦克
}
......
玩家坦克控制
src/model/Player.ts
/**
* 模型
* 敌方坦克
*/
import AbstractModel from "./abstract/AbstractModel";
import {image} from "../service/image";
import {upperFirst} from 'lodash'
import config from "../config";
import player from "../canvas/Player";
export default class ModelTank extends AbstractModel implements IModel {
name: string = 'player';
// 画布实例
canvas: ICanvas = player;
// 继承父类抽象方法:渲染贴图
// 一些初始化自定义的动作、行为,都在这里进行
render(): void {
// 初始化模型
super.draw()
// 控制坦克移动,添加键盘监听事件
document.addEventListener('keydown', this.changeDirection.bind(this))
}
/**
* 方向改变
* @param event 键盘对象
*/
changeDirection(event: KeyboardEvent) {
console.log(event)
}
// 随机取用其中一个图片
getImage(): HTMLImageElement {
return image.get(`${this.name}${upperFirst(this.direction)}` as keyof typeof config.images)!
}
}
这里可以看到,键盘按键区别类别有code和key。这里我们通过code处理逻辑。
src/model/Player.ts
/**
* 模型
* 敌方坦克
*/
import AbstractModel from "./abstract/AbstractModel";
import {image} from "../service/image";
import {upperFirst} from 'lodash'
import config from "../config";
import player from "../canvas/Player";
import {EnumDirection} from "../enum/enumPosition";
export default class ModelTank extends AbstractModel implements IModel {
name: string = 'player';
// 画布实例
canvas: ICanvas = player;
// 继承父类抽象方法:渲染贴图
// 一些初始化自定义的动作、行为,都在这里进行
render(): void {
//初始化时永远朝上
// this.direction = EnumDirection.top
// 初始化模型
super.draw()
// 控制坦克移动,添加键盘监听事件
document.addEventListener('keydown', this.changeDirection.bind(this))
}
/**
* 方向改变
* @param event 键盘对象
*/
changeDirection(event: KeyboardEvent) {
// console.log(event)
switch (event.code) {
case 'KeyW':
case 'ArrowUp':
this.direction = EnumDirection.top
break;
case 'KeyD':
case 'ArrowRight':
this.direction = EnumDirection.right
break;
case 'KeyS':
case 'ArrowDown':
this.direction = EnumDirection.bottom
break;
case 'KeyA':
case 'ArrowLeft':
this.direction = EnumDirection.left
break;
}
// 绘制模型
this.canvas.renderModels()
}
// 随机取用其中一个图片
getImage(): HTMLImageElement {
return image.get(`${this.name}${upperFirst(this.direction)}` as keyof typeof config.images)!
}
}
这里切换方向,会出现卡死。
修复时间无限绑定
在我们的模型代码中,调用this.canvas.renderModels()
,会执行模型的render()
方法,随着越按越多,模型会无限增加事件的绑定,最终导致卡死。
解决方法:增加一个事件是否绑定的标记位。
src/model/Player.ts
export default class ModelTank extends AbstractModel implements IModel {
name: string = 'player';
// 画布实例
canvas: ICanvas = player;
// 事件是否绑定
isBindEvent: boolean = false
// 继承父类抽象方法:渲染贴图
// 一些初始化自定义的动作、行为,都在这里进行
render(): void {
//初始化时永远朝上
// this.direction = EnumDirection.top
// 初始化模型
super.draw()
if (!this.isBindEvent) {
// 控制坦克移动,添加键盘监听事件
document.addEventListener('keydown', this.changeDirection.bind(this))
this.isBindEvent = true
}
}
......
}
玩家坦克碰撞检测
src/model/Player.ts
import utils from "../utils";
......
// 继承父类抽象方法:渲染贴图
// 一些初始化自定义的动作、行为,都在这里进行
render(): void {
//初始化时永远朝上
// this.direction = EnumDirection.top
// 初始化模型
super.draw()
if (!this.isBindEvent) {
// 控制坦克移动,添加键盘监听事件
document.addEventListener('keydown', this.changeDirection.bind(this))
document.addEventListener('keydown', this.move.bind(this))
this.isBindEvent = true
}
}
......
//移动
move(event: KeyboardEvent) {
// ********************* 坐标更新 *********************
let x = this.x;
let y = this.y;
switch (event.code) {
case 'KeyW':
case 'ArrowUp':
y -= 15
break;
case 'KeyD':
case 'ArrowRight':
x += 15
break;
case 'KeyS':
case 'ArrowDown':
y += 15
break;
case 'KeyA':
case 'ArrowLeft':
x -= 15
break;
}
if (utils.modelTouch(x, y, [
...water.models,// 水域
...wallBrick.models,// 砖墙
...wallSteel.models,// 钢墙
...boss.models, //boss
...tank.models //敌方坦克
]) || utils.isCanvasTouch(x, y)) {
// 随机获取方向
} else {
this.x = x;
this.y = y;
// 绘制模型
this.canvas.renderModels()
}
}
......
src/model/Tank.ts
......
// 坦克行动
protected move(): void {
while (true) {
......
if (utils.modelTouch(x, y, [
...water.models,// 水域
...wallBrick.models,// 砖墙
...wallSteel.models,// 钢墙
...player.models // 我方坦克
]) || utils.isCanvasTouch(x, y)) {
// 随机获取方向
this.randomDirection()
} else {
this.x = x;
this.y = y;
// 跳出while死循环
break;
}
}
......
玩家坦克子弹发射
/**
* 模型
* 敌方坦克
*/
......
import bullet from "../canvas/Bullet";
export default class ModelTank extends AbstractModel implements IModel {
name: string = 'player';
// 画布实例
canvas: ICanvas = player;
// 事件是否绑定
isBindEvent: boolean = false
// 继承父类抽象方法:渲染贴图
// 一些初始化自定义的动作、行为,都在这里进行
render(): void {
//初始化时永远朝上
// this.direction = EnumDirection.top
// 初始化模型
super.draw()
if (!this.isBindEvent) {
this.isBindEvent = true
// 添加键盘监听事件,坦克方向改变
document.addEventListener('keydown', this.changeDirection.bind(this))
// 添加键盘监听事件,坦克移动
document.addEventListener('keydown', this.move.bind(this))
// 添加键盘监听事件,坦克发射子弹
document.addEventListener('keydown', (event: KeyboardEvent) => {
console.log(event);
//发射子弹,空格,回车,小键盘回车
(event.code === 'Space'
|| event.code === 'Enter'
|| event.code === 'NumpadEnter') && bullet.addPlayerBullet();
}
)
}
}
......
}
src/canvas/Bullet.ts
......
// 我方坦克发射子弹
addPlayerBullet() {
this.models.push(new ModelBullet(player.models[0]))
}
......
坦克互战
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'
// 玩家坦克
import imgUrlPlayerTop from './static/images/player/top.gif'
import imgUrlPlayerRight from './static/images/player/right.gif'
import imgUrlPlayerBottom from './static/images/player/bottom.gif'
import imgUrlPlayerLeft from './static/images/player/left.gif'
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: 3// 速度,越大越快(1-100)
},
// 子弹的速度
bullet: {
num: 40,// 数量
speed: 8 // 速度,越大越快(1-100)
},
// 图片
images: {
// 草地
straw: imgUrlStraw,
// 砖墙
wallBrick: imgUrlWallBrick,
// 钢墙
wallSteel: imgUrlWallSteel,
// 水域
water: imgUrlWater,
// 敌方坦克
tankTop: imgUrlTankTop,
tankRight: imgUrlTankRight,
tankBottom: imgUrlTankBottom,
tankLeft: imgUrlTankLeft,
// 子弹
bullet: imgUrlBullet,
// boss,己方老巢
boss: imgUrlBoss,
//玩家坦克
playerTop: imgUrlPlayerTop,
playerRight: imgUrlPlayerRight,
playerBottom: imgUrlPlayerBottom,
playerLeft: imgUrlPlayerLeft
}
}
src/canvas/Tank.ts
......
/**
* 画布是单例模式
* 在一个图层,所以只需要new一个实例即可。
*/
export default new (class extends AbstractCanvas implements ICanvas {
render(): void {
// super:调用父类的方法
this.createModels()
// 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
super.renderModels();
console.log("tank", Number((100 / config.tank.speed).toFixed(3)))
// 让坦克画布实时刷新,每config.tank.speed毫秒擦写一次,等于速度。
setInterval(() => {
this.renderModels()
}, Number((100 / config.tank.speed).toFixed(3)))
}
......
}
src/canvas/Bullet.ts
......
/**
* 画布是单例模式
* 在一个图层,所以只需要new一个实例即可。
*/
export default new (class extends AbstractCanvas implements ICanvas {
render(): void {
// super:调用父类的方法
// super.createModels()
// 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
// super.renderModels();
console.log("bullet", Number((100 / config.bullet.speed).toFixed(3)))
//bind:绑定this
setInterval(() => {
// this.createBullet.bind(this)
//自定义子弹的渲染函数:子弹创建
this.createModelsBullet()
// 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
super.renderModels();
}, Number((100 / config.bullet.speed).toFixed(3)))
}
......
}
src/model/Bullet.ts
......
// 一些初始化自定义的动作、行为,都在这里进行
render(): void {
// super.draw()
// ********************* 坐标更新 *********************
let x = this.x;
let y = this.y;
// 子弹速度
let speed = this.tank.name === 'player' ? 4 : 2
switch (this.direction) {
case EnumDirection.top:
y -= speed
break;
case EnumDirection.right:
x += speed
break;
case EnumDirection.bottom:
y += speed
break;
case EnumDirection.left:
x -= speed
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,// 砖墙
...boss.models,// boss
],
config.model.bullet.width,
config.model.bullet.height
)
// 如果是我方坦克的子弹,需要判断是否打中地方坦克
let touchTankModel = undefined
let touchPlayerModel = undefined
if (this.tank.name === 'player') {
touchTankModel = utils.modelTouch(x, y, [
...tank.models,// 敌方坦克
],
config.model.bullet.width,
config.model.bullet.height
)
} else {
touchPlayerModel = utils.modelTouch(x, y, [
...player.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 (touchPlayerModel || touchTankModel || touchBlowModel) {
// 当前子弹模型消失
this.destroy()
// **************** 可打爆的障碍 ****************
// 展示爆炸效果
touchBlowModel && super.blast(touchBlowModel)
// 碰撞的模型消失
touchBlowModel && touchBlowModel.destroy();
// **************** 可打爆的障碍 ****************
// **************** 玩家坦克打爆敌方坦克 ****************
// 展示爆炸效果
touchTankModel && super.blast(touchTankModel)
// 碰撞的模型消失
touchTankModel && touchTankModel.destroy();
// **************** 玩家坦克打爆敌方坦克 ****************
// **************** 敌方坦克打爆玩家坦克 ****************
// 展示爆炸效果
touchPlayerModel && super.blast(touchPlayerModel)
// 碰撞的模型消失
touchPlayerModel && touchPlayerModel.destroy();
// **************** 敌方坦克打爆玩家坦克 ****************
} else {
this.x = x;
this.y = y;
// 画布重绘, 渲染坦克模型,在这里调用减少重绘次数
this.draw()
}
}
......
玩家坦克被打掉了:
发表评论 (审核通过后显示评论):