src/canvas/Bullet.ts
/**
* 画布
* 子弹
*/
import AbstractCanvas from "./abstract/AbstractCanvas";
import ModelBullet from "../model/Bullet";
/**
* 画布是单例模式
* 在一个图层,所以只需要new一个实例即可。
*/
export default new (class extends AbstractCanvas implements ICanvas {
render(): void {
// super:调用父类的方法
super.createModels()
// 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
super.renderModels();
}
// 抽象方法,返回模型
model(): ConstructorModel {
return ModelBullet;
}
// 抽象方法:返回模型数量,子弹数量的创建由坦克决定,这里默认给0
num(): number {
return 0;
}
}
)('bullet')
src/model/Bullet.ts
/**
* 模型
* 子弹
*/
import AbstractModel from "./abstract/AbstractModel";
import {image} from "../service/image";
import config from "../config";
import bullet from "../canvas/Bullet";
export default class ModelStraw extends AbstractModel implements IModel {
name: string = 'bullet';
// 画布实例
canvas: ICanvas = bullet;
// 继承父类抽象方法:渲染贴图
// 一些初始化自定义的动作、行为,都在这里进行
render(): void {
super.draw()
}
// 获取贴图
getImage(): HTMLImageElement {
return image.get(this.name as keyof typeof config.images)!;
}
}
如上,画布是单例模式,在一个图层,所以只需要new一个实例即可。都修改为export default new (xxxxx)(参数)的模式。
比如src/canvas/Tank.ts
/**
* 画布
* 坦克
*/
import AbstractCanvas from "./abstract/AbstractCanvas";
import ModelTank from "../model/Tank";
import config from "../config";
import position from "../service/position";
/**
* 画布是单例模式
* 在一个图层,所以只需要new一个实例即可。
*/
export default new (class extends AbstractCanvas implements ICanvas {
.....
})('tank')
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";
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() // 画布渲染:砖墙
canvasWater.render() // 画布渲染:水域
canvasWallSteel.render() // 画布渲染:钢墙
canvasTank.render() // 画布渲染:敌方坦克
bullet.render() // 画布渲染:子弹
}
void bootstrap()
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'
export default {
// 画布
canvas: {
width: 900,
height: 600,
},
// 模型
model: {
common: {
width: 30,
height: 30,
},
// 草地
// straw: {
// width: 30,
// height: 30,
// }
},
//草地
straw: {
num: 100,
},
// 砖墙
wallBrick: {
num: 100,
},
// 钢墙
wallSteel: {
num: 30,
},
// 水域
water: {
num: 40
},
// 敌方坦克
tank: {
num: 40,// 坦克数量
speed: 0.5 // 坦克速度,越小越快
},
// 图片
images: {
// 草地
straw: imgUrlStraw,
// 砖墙
wallBrick: imgUrlWallBrick,
// 钢墙
wallSteel: imgUrlWallSteel,
// 水域
water: imgUrlWater,
// 敌方坦克
tankTop: imgUrlTankTop,
tankRight: imgUrlTankRight,
tankBottom: imgUrlTankBottom,
tankLeft: imgUrlTankLeft,
// 子弹
bullet: imgUrlBullet
}
}
如下图,不仅效果一致,而且能够在html中区分每一个画布是什么。

进入子弹的画布,自定义子弹渲染。
src/canvas/Bullet.ts
/**
* 画布
* 子弹
*/
import AbstractCanvas from "./abstract/AbstractCanvas";
import ModelBullet from "../model/Bullet";
import tank from "./Tank";
/**
* 画布是单例模式
* 在一个图层,所以只需要new一个实例即可。
*/
export default new (class extends AbstractCanvas implements ICanvas {
render(): void {
// super:调用父类的方法
// super.createModels()
// 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
// super.renderModels();
//bind:绑定this
setTimeout(this.createBullet.bind(this), 100)
}
//自定义子弹的渲染函数:子弹创建
createBullet() {
// 子弹的创建需要根据坦克的画布中的坦克模型数量来决定需要发送多少颗子弹
tank.models.forEach(tItem => {
// 查询是否存在同样名称的坦克的子弹,如果有,表示该坦克已经发射子弹了
const isExists = this.models.some(model => model.tank == tItem)
//如果子弹不存在,放置子弹
if (!isExists) {
this.models.push(new ModelBullet(tItem))
}
})
console.log(this.models)
}
// 抽象方法,返回模型
model(): ConstructorModelBullet {
return ModelBullet;
}
// 抽象方法:返回模型数量,子弹数量的创建由坦克决定,这里默认给0
num(): number {
return 0;
}
}
)('bullet')
src/canvas/abstract/AbstractCanvas.ts
......
// 绘制模型,生成模型实例,只负责创建实例
// protected:子类可以调用,外部不能调用
//num: 渲染多少个数量
//model: 模型
protected createModels() {
position.getPositionCollection(this.num()).forEach((position) => {
const model = this.model() as ConstructorModel
const instance = new model(position.x, position.y)
this.models.push(instance)
})
}
......
src/model/Bullet.ts
/**
* 模型
* 子弹
*/
import AbstractModel from "./abstract/AbstractModel";
import {image} from "../service/image";
import config from "../config";
import bullet from "../canvas/Bullet";
export default class ModelStraw extends AbstractModel implements IModel {
name: string = 'bullet';
// 画布实例
canvas: ICanvas = bullet;
//tank: 属性:每一个子弹都有属于自己的坦克
constructor(public tank: IModel) {
// 子弹是坦克发的,所以子弹的坐标取的是坦克的x、y坐标为基准的,在坦克中间生成
super(
tank.x + Math.floor(config.model.common.width / 2),
tank.y + Math.floor(config.model.common.height / 2))
}
// 继承父类抽象方法:渲染贴图
// 一些初始化自定义的动作、行为,都在这里进行
render(): void {
super.draw()
}
// 获取贴图
getImage(): HTMLImageElement {
return image.get(this.name as keyof typeof config.images)!;
}
}
src/vite-env.d.ts
/// <reference types="vite/client" />
/**
* 全局声明
*/
/**
* 模型对象
*/
interface ConstructorModel {
new(x: number,
y: number): any
}
/**
* 子弹构造器类型声明
*/
interface ConstructorModelBullet {
new(tank: IModel): any
}
/**
* 模型实现的函数、方法
*/
interface IModel {
// 抽象属性:模型名称
name: string
// 坐标,x轴
x: number
// 坐标,y轴
y: number
// 宽度,碰撞判断需要跨模型调用,所以为public
width: number;
// 高度,碰撞判断需要跨模型调用,所以为public
height: number;
// 子弹画布活模型用,子弹所归属的坦克模型实例
tank?: IModel
// 抽象方法:渲染贴图
render(): void
// 抽象方法:获取贴图
getImage(): HTMLImageElement
}
/**
* 画布实现的函数、方法
*/
interface ICanvas {
// 抽象属性:画布实例
ctx: CanvasRenderingContext2D
// 抽象方法:渲染贴图
render(): void
// 抽象方法,返回模型
model(): ConstructorModel | ConstructorModelBullet
// 抽象方法:返回模型数量
num(): number
}
子弹模型已经全部生成,刚好是地方坦克的数量——40

子弹的方向就是坦克当下的方向
src/model/abstract/AbstractModel.ts
......
/**
* 抽象类
*/
export default abstract class AbstractModel {
......
// 方向,子弹的方向取决于坦克的方向,需要同层级调用
public direction: EnumDirection = EnumDirection.top
......
}
......
src/model/Bullet.ts
......
export default class ModelStraw extends AbstractModel implements IModel {
name: string = 'bullet';
// 画布实例
canvas: ICanvas = bullet;
//tank: 属性:每一个子弹都有属于自己的坦克
constructor(public tank: IModel) {
// 子弹是坦克发的,所以子弹的坐标取的是坦克的x、y坐标为基准的,在坦克中间生成
super(
tank.x + Math.floor(config.model.common.width / 2),
tank.y + Math.floor(config.model.common.height / 2))
//子弹的方向就是坦克当下的方向
this.direction = tank.direction as EnumDirection
}
......
}
......
src/vite-env.d.ts
......
/**
* 模型实现的函数、方法
*/
interface IModel {
// 抽象属性:模型名称
name: string
// 坐标,x轴
x: number
// 坐标,y轴
y: number
// 宽度,碰撞判断需要跨模型调用,所以为public
width: number;
// 高度,碰撞判断需要跨模型调用,所以为public
height: number;
// 子弹画布活模型用,子弹所归属的坦克模型实例
tank?: IModel
// 方向,子弹的方向取决于坦克的方向,需要同层级调用
direction: string
// 抽象方法:渲染贴图
render(): void
// 抽象方法:获取贴图
getImage(): HTMLImageElement
}
......
如下图,白色方块就是子弹,这里存在2个问题,一个是子弹还没移动,第二个是子弹特别大。

首先是子弹画布的创建。
/**
* 画布
* 子弹
*/
import AbstractCanvas from "./abstract/AbstractCanvas";
import ModelBullet from "../model/Bullet";
import tank from "./Tank";
import config from "../config";
/**
* 画布是单例模式
* 在一个图层,所以只需要new一个实例即可。
*/
export default new (class extends AbstractCanvas implements ICanvas {
render(): void {
// super:调用父类的方法
// super.createModels()
// 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
// super.renderModels();
//bind:绑定this
setInterval(()=>{
// this.createBullet.bind(this)
//自定义子弹的渲染函数:子弹创建
this.createModelsBullet()
// 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
super.renderModels();
}, config.bullet.speed)
}
//自定义子弹的渲染函数:子弹创建
createModelsBullet() {
// 子弹的创建需要根据坦克的画布中的坦克模型数量来决定需要发送多少颗子弹
tank.models.forEach(tItem => {
// 查询是否存在同样名称的坦克的子弹,如果有,表示该坦克已经发射子弹了
const isExists = this.models.some(model => model.tank === tItem)
//如果子弹不存在,放置子弹
if (!isExists) {
this.models.push(new ModelBullet(tItem))
}
})
// console.log(this.models)
}
// 抽象方法,返回模型
model(): ConstructorModelBullet {
return ModelBullet;
}
// 抽象方法:返回模型数量,子弹数量的创建由坦克决定,这里默认给0
num(): number {
return 0;
}
}
)('bullet')
子弹模型和运动控制。
src/model/Bullet.ts
/**
* 模型
* 子弹
*/
import AbstractModel from "./abstract/AbstractModel";
import {image} from "../service/image";
import config from "../config";
import bullet from "../canvas/Bullet";
import {EnumDirection} from "../enum/enumPosition";
import utils from "../utils";
import wallBrick from "../canvas/WallBrick";
import wallSteel from "../canvas/WallSteel";
export default class ModelStraw extends AbstractModel implements IModel {
name: string = 'bullet';
// 画布实例
canvas: ICanvas = bullet;
//tank: 属性:每一个子弹都有属于自己的坦克
constructor(public tank: IModel) {
// 子弹是坦克发的,所以子弹的坐标取的是坦克的x、y坐标为基准的,在坦克中间生成
super(
tank.x + Math.floor(config.model.common.width / 2),
tank.y + Math.floor(config.model.common.height / 2))
//子弹的方向就是坦克当下的方向
this.direction = tank.direction as EnumDirection
}
// 继承父类抽象方法:渲染贴图
// 一些初始化自定义的动作、行为,都在这里进行
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()
} else if (touchBlowModel) {
// 当前子弹模型消失
this.destroy()
// 碰撞的模型消失
touchBlowModel.destroy();
} else {
this.x = x;
this.y = y;
}
// 画布重绘, 渲染坦克模型,在这里调用减少重绘次数
this.draw()
}
// 函数:渲染模型
draw(): void {
this.canvas.ctx.drawImage(
this.getImage(),
this.x,
this.y,
config.model.bullet.width,
config.model.bullet.height
)
}
// 获取贴图
getImage(): HTMLImageElement {
return image.get(this.name as keyof typeof config.images)!;
}
}
这里涉及到碰撞检测的方法,和地方坦克的碰撞检测方法类似,可以抽象成工具类。
src/utils.ts
import config from "./config";
import water from "./canvas/Water";
import wallBrick from "./canvas/WallBrick";
import wallSteel from "./canvas/WallSteel";
export default {
/**
* 判断是否与画布触碰
* @param x 当前模型的X轴
* @param y 当前模型的Y轴
* @param width 当前模型的宽
* @param height 当前模型的高
*/
isCanvasTouch(
x: number,
y: number,
width: number = config.model.common.width,
height: number = config.model.common.height
): boolean {
// ********************* 坐标边界判断 *********************
//x最大的坐标边界
let maxX = config.canvas.width - width;
//x最大的坐标边界
let maxY = config.canvas.height - height;
return x < 0 || x > maxX || y < 0 || y > maxY;
// ********************* 坐标边界判断 *********************
},
/**
* 判断是否与模型触碰
* @param x 当前模型的X轴
* @param y 当前模型的Y轴
* @param width 当前模型的宽
* @param height 当前模型的高
* @param models 需要判断碰撞的其他模型集合
*/
modelTouch(
x: number,
y: number,
models: IModel[] = [
...water.models,// 水域
...wallBrick.models,// 砖墙
...wallSteel.models,// 钢墙
],
width: number = config.model.common.width,
height: number = config.model.common.height,
): IModel | undefined {
// ********************* 其他模型碰撞判断 *********************
return models.find(model => {
let leftX = model.x - width // 物体模型 左侧边碰撞判断
let rightX = model.x + model.width// 物体模型 右侧边碰撞判断
let topY = model.y - height// 物体模型 上侧边碰撞判断
let bottomY = model.y + model.height// 物体模型 下侧边碰撞判断
const state = x <= leftX || x >= rightX || y <= topY || y >= bottomY
return !state
})
// ********************* 其他模型碰撞判断 *********************
}
}
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";
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() // 画布渲染:子弹
}
void bootstrap()
对应的src/canvas/abstract/AbstractCanvas.ts
import config from "../../config";
import position from "../../service/position";
/**
* 抽象类
*/
export default abstract class AbstractCanvas {
// 元素实例:模型,元素碰撞需要取用,所以为public
public models: IModel[] = []
//构造函数渲染
constructor(
protected name: string,
protected app = document.querySelector('#app') as HTMLDivElement,
// @ts-ignore
protected el = document.createElement<HTMLCanvasElement>('canvas')!,
// @ts-ignore
public ctx = el.getContext('2d')!
) {
this.createCanvas()
}
// 抽象方法:渲染贴图
abstract render(): void
// 抽象方法,返回模型
abstract model(): ConstructorModel | ConstructorModelBullet
// 抽象方法:返回模型数量
abstract num(): number
// 画布移除模型
public removeModel(model:IModel): void {
this.models = this.models.filter(mItem => mItem !=model)
}
// 绘制模型,生成模型实例,只负责创建实例
// protected:子类可以调用,外部不能调用
//num: 渲染多少个数量
// 初始化canvas,创建画布
protected createCanvas() {
// 元素的宽高就是全局canvas得到宽高
// @ts-ignore
this.el.width = config.canvas.width
// @ts-ignore
this.el.height = config.canvas.height
// 测试画布
// 定义填充颜色
// this.canvas.fillStyle = '#16a085'
// 绘制矩形
// this.canvas.fillRect(0, 0, config.canvas.width, config.canvas.height)
// 最终元素要放到我们的app的div中
// @ts-ignore
this.el.setAttribute('name', this.name)
// @ts-ignore
this.app.insertAdjacentElement('afterbegin', this.el)
// @ts-ignore
// this.app.appendChild(this.el)
}
//model: 模型
protected createModels() {
position.getPositionCollection(this.num()).forEach((position) => {
const model = this.model() as ConstructorModel
const instance = new model(position.x, position.y)
this.models.push(instance)
// this.canvas.drawImage(
// image.get('straw')!,
// position.x,
// position.y,
// config.model.common.width,
// config.model.common.height
// );
})
// Array(num).fill('').forEach(() => {
// const position = this.position()
// this.canvas.drawImage(
// image.get('straw')!,
// position.x,
// position.y,
// config.model.common.width,
// config.model.common.height
// );
// })
// const img = document.createElement('img')
// img.src = imgUrl;
// //图片是异步加载,所以需要将图片加载完毕后,才进行渲染绘制
// img.onload = () => {
// const position = this.position()
// this.canvas.drawImage(img, position.x, position.y, config.model.common.width, config.model.common.height);
// }
}
// 画布渲染模型(将模型渲染到画布上)。public:模型需要主动渲染画布时需要调用,所以public
public renderModels() {
// 画布清理
this.ctx.clearRect(0, 0, config.canvas.width, config.canvas.height)
this.models.forEach(model => model.render())
}
}
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
)
}
}
src/canvas/Tank.ts
......
// 画布渲染模型(将模型渲染到画布上)
public renderModels() {
// 调用渲染模型,防止每次重新渲染时,又生成新的模型实例
super.renderModels();
}
......
src/model/Tank.ts
import {ICanvas, IModel} from "../vite-env";
......
// 坦克行动
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--
break;
case EnumDirection.right:
x++
break;
case EnumDirection.bottom:
y++
break;
case EnumDirection.left:
x--
break;
}
if (this.isTouch(x, y)) {
// 随机获取方向
this.randomDirection()
} else {
this.x = x;
this.y = y;
// 跳出while死循环
break;
}
}
// ********************* 坐标更新 *********************
// 画布重绘, 渲染坦克模型,在这里调用减少重绘次数
super.draw()
}
// 判断是否触碰
protected isTouch(x: number, y: number): boolean {
// ********************* 坐标边界判断 *********************
//x最大的坐标边界
let maxX = config.canvas.width - this.width;
//x最大的坐标边界
let maxY = config.canvas.height - this.height;
if (x < 0 || x > maxX || y < 0 || y > maxY) {
return true
}
// ********************* 坐标边界判断 *********************
// ********************* 其他模型碰撞判断 *********************
const models = [
...water.models,// 水域
...wallBrick.models,// 砖墙
...wallSteel.models,// 钢墙
]
return models.some(model => {
let leftX = model.x - this.width // 物体模型 左侧边碰撞判断
let rightX = model.x + model.width// 物体模型 右侧边碰撞判断
let topY = model.y - this.height// 物体模型 上侧边碰撞判断
let bottomY = model.y + model.height// 物体模型 下侧边碰撞判断
const state = x <= leftX || x >= rightX || y <= topY || y >= bottomY
return !state
})
// ********************* 其他模型碰撞判断 *********************
}
......
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'
export default {
// 画布
canvas: {
width: 900,
height: 600,
},
// 模型
model: {
common: {
width: 30,
height: 30,
},
// 子弹
bullet: {
width: 3,
height: 3,
}
},
//草地
straw: {
num: 100,
},
// 砖墙
wallBrick: {
num: 100,
},
// 钢墙
wallSteel: {
num: 30,
},
// 水域
water: {
num: 40
},
// 敌方坦克
tank: {
num: 40,// 坦克数量
speed: 10 // 坦克速度,越小越快
},
// 子弹的速度
bullet: {
num: 40,// 坦克数量
speed: 0.1 // 坦克速度,越小越快
},
// 图片
images: {
// 草地
straw: imgUrlStraw,
// 砖墙
wallBrick: imgUrlWallBrick,
// 钢墙
wallSteel: imgUrlWallSteel,
// 水域
water: imgUrlWater,
// 敌方坦克
tankTop: imgUrlTankTop,
tankRight: imgUrlTankRight,
tankBottom: imgUrlTankBottom,
tankLeft: imgUrlTankLeft,
// 子弹
bullet: imgUrlBullet
}
}

我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah
我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss
我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢
我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:
对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs
我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案
ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序
我正在使用Rails3.1并在一个论坛上工作。我有一个名为Topic的模型,每个模型都有许多Post。当用户创建新主题时,他们也应该创建第一个Post。但是,我不确定如何以相同的形式执行此操作。这是我的代码:classTopic:destroyaccepts_nested_attributes_for:postsvalidates_presence_of:titleendclassPost...但这似乎不起作用。有什么想法吗?谢谢! 最佳答案 @Pablo的回答似乎有你需要的一切。但更具体地说...首先改变你View中的这一行对此#