You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
315 lines
7.2 KiB
315 lines
7.2 KiB
/*
|
|
author: leeenx
|
|
@ 贪吃蛇的 view 类
|
|
*/
|
|
|
|
// 获取 Graphics 的 content-box
|
|
import "../lib/utils/getContentBoxSize";
|
|
|
|
// 不显示 PIXI 信息
|
|
import "../lib/utils/noHello";
|
|
|
|
// 链表类
|
|
import Chain from "../lib/utils/Chain";
|
|
|
|
// view 类
|
|
export default class view {
|
|
// 构造函数
|
|
constructor(dom, width, height) {
|
|
// 创建一个 app
|
|
let app = new PIXI.Application(width, height, {
|
|
transparent: true,
|
|
});
|
|
|
|
// canvas 添加到 page
|
|
dom.appendChild(app.view);
|
|
|
|
// 销毁pixijs的ticker
|
|
app.ticker.destroy();
|
|
|
|
// 创建 view 的蛇
|
|
this.snake = new Chain();
|
|
|
|
// shift & unshift & pop & push & insertAfter 自动关联 addNode & removeNode
|
|
let { shift, unshift, pop, push, insertAfter } = this.snake;
|
|
|
|
// 封闭 shift
|
|
this.snake.shift = () => {
|
|
// 回收尾节点
|
|
this.collect(shift.call(this.snake).node);
|
|
};
|
|
|
|
// 封装 unshift
|
|
this.snake.unshift = (data) => {
|
|
unshift.call(this.snake, data);
|
|
let node = (this.snake.first().node = this.calloc());
|
|
node.setPostion(...this.getPosition(data));
|
|
};
|
|
|
|
// 封闭 pop
|
|
this.snake.pop = () => {
|
|
// 回收尾节点
|
|
this.collect(pop.call(this.snake).node);
|
|
};
|
|
|
|
// 封装 push
|
|
this.snake.push = (data) => {
|
|
push.call(this.snake, data);
|
|
let node = (this.snake.last().node = this.calloc());
|
|
node.setPostion(...this.getPosition(data));
|
|
};
|
|
|
|
// 封装 insertAfter
|
|
this.snake.insertAfter = (index, data) => {
|
|
insertAfter.call(this.snake, index, data);
|
|
let node = (this.snake.at(index + 1).node = this.calloc());
|
|
node.setPostion(...this.getPosition(data));
|
|
};
|
|
|
|
// 回收节点
|
|
this.collection = [];
|
|
|
|
// 保证 updateTicker 指针永远指向 view
|
|
this.updateTicker = this.updateTicker.bind(this);
|
|
|
|
// 挂载到this
|
|
this.app = app;
|
|
this.stage = app.stage;
|
|
|
|
// 扩展 PIXI.Graphics
|
|
PIXI.Graphics.prototype.setPostion = function (x, y = x) {
|
|
this.x = x + this.pivot.x;
|
|
this.y = y + this.pivot.y;
|
|
};
|
|
}
|
|
|
|
// 初始化
|
|
init(config = {}) {
|
|
// pixijs
|
|
let {
|
|
app,
|
|
app: { stage, renderer },
|
|
} = this;
|
|
|
|
// 蛇的尺寸挂载到 config
|
|
config.size = {
|
|
width: config.width / config.column,
|
|
height: config.height / config.row,
|
|
};
|
|
console.log(config.size);
|
|
// 初化data
|
|
this.data = config.data;
|
|
|
|
// 全局 config 挂载
|
|
this.config = config;
|
|
|
|
// 游戏活动区
|
|
stage.addChild((this.zone = new PIXI.Container()));
|
|
|
|
// 绘制边界
|
|
// this.drawBounds();
|
|
|
|
// 食物
|
|
this.food = this.calloc();
|
|
this.food.visible = false;
|
|
|
|
// 食物动画 - blink
|
|
let [from, to] = [
|
|
{
|
|
alpha: 1,
|
|
},
|
|
{
|
|
alpha: 0,
|
|
},
|
|
];
|
|
TweenMax.fromTo(this.food, 0.2, from, to).repeat(-1).yoyo(true);
|
|
|
|
// 通过 model.zone 创建一张快速定位表
|
|
this.createQuickMap(this.data.zone);
|
|
|
|
// 同步 model 的初始数据
|
|
for (let { data } of this.data.snake) {
|
|
this.snake.push(data);
|
|
}
|
|
}
|
|
|
|
destroy() {
|
|
let {
|
|
app,
|
|
app: { stage },
|
|
} = this;
|
|
// 销毁所有子节点
|
|
for (let child of stage.children) {
|
|
child.destroy();
|
|
}
|
|
stage.removeChildren();
|
|
this.collection = [];
|
|
this.snake.clean();
|
|
}
|
|
// 绘制四条边界
|
|
drawBounds() {
|
|
let {
|
|
app,
|
|
app: { stage },
|
|
} = this,
|
|
{ border, width, height } = this.config,
|
|
thickness = 8;
|
|
let bounds = new PIXI.Graphics()
|
|
.beginFill(0xffffff, 1)
|
|
.lineStyle(thickness, border, 1)
|
|
.drawRect(0, 0, width + thickness, height + thickness);
|
|
|
|
bounds.x = bounds.y = (app.view.width - bounds.cwidth) / 2;
|
|
|
|
stage.addChild(bounds);
|
|
// bounds 的 index
|
|
stage.setChildIndex(bounds, 0);
|
|
|
|
// 活动空间定位
|
|
[this.zone.x, this.zone.y] = [
|
|
bounds.x + thickness / 2,
|
|
bounds.y + thickness / 2,
|
|
];
|
|
}
|
|
|
|
// 快速寻位表
|
|
createQuickMap(map) {
|
|
let { width, height } = this.config.size;
|
|
// 快速表
|
|
this.quickMap = [];
|
|
for (let { col, row } of map) {
|
|
this.quickMap.push([col * width, row * height]);
|
|
}
|
|
}
|
|
|
|
// 快速计算position
|
|
getPosition(index) {
|
|
return this.quickMap[index];
|
|
}
|
|
|
|
// 创建节点
|
|
calloc() {
|
|
let node;
|
|
if (this.collection.length === 0) {
|
|
node = new PIXI.Graphics();
|
|
let { width, height } = this.config.size;
|
|
node.beginFill(this.config.color, 1).drawRect(0, 0, width, height);
|
|
node.pivot.set(width / 2, height / 2);
|
|
node.setPostion(0);
|
|
} else {
|
|
node = this.collection.pop();
|
|
}
|
|
|
|
// 默认显示在容器里
|
|
this.zone.addChild(node);
|
|
return node;
|
|
}
|
|
|
|
// 回收节点
|
|
collect(node) {
|
|
node && this.collection.push(node) & this.zone.removeChild(node);
|
|
}
|
|
|
|
// 随机生成食物
|
|
feed(index) {
|
|
this.food.visible = 1;
|
|
this.food.graphicsData[0].fillColor = this.config.food;
|
|
this.food.dirty++;
|
|
this.food.clearDirty++;
|
|
this.food.setPostion(...this.getPosition(index));
|
|
}
|
|
|
|
// ticker update
|
|
updateTicker() {
|
|
// this.render();
|
|
}
|
|
|
|
// 状态更新
|
|
update(data) {
|
|
// 食物更新
|
|
this.food !== data.food && this.feed(data.food);
|
|
this.updateDelta(data.snake);
|
|
}
|
|
|
|
// 增量更新
|
|
updateDelta(snakeA, snakeB = this.snake) {
|
|
// snakeA === model.snake, snakeB === view.snake
|
|
this.updateTail(snakeA, snakeB)
|
|
.then(() => this.updateHead(snakeA, snakeB))
|
|
.catch(() => this.wholeUpdate(snakeA, snakeB))
|
|
.then(() => this.render());
|
|
}
|
|
|
|
// 检查蛇头
|
|
updateHead(snakeA, snakeB) {
|
|
return new Promise((resolve, reject) => {
|
|
// snakeA 与 snakeB 做头指针比较
|
|
let headA,
|
|
headB = snakeB.first();
|
|
// 指针指向头部
|
|
snakeA.setPointer(snakeA.HEAD);
|
|
while (snakeB.length <= snakeA.length) {
|
|
headA = snakeA.next();
|
|
// 头节点匹配
|
|
if (headA.data === headB.data) {
|
|
// 执行 then 通道
|
|
return resolve();
|
|
}
|
|
// 不匹配
|
|
else {
|
|
// 向snakeB插入头节点
|
|
if (snakeA.HEAD === headA.index) {
|
|
snakeB.unshift(headA.data);
|
|
}
|
|
// 向snakeB插入第二个节点
|
|
else {
|
|
snakeB.insertAfter(0, headA.data);
|
|
}
|
|
}
|
|
}
|
|
// 头指针未匹配上,走 catch 通道
|
|
reject();
|
|
});
|
|
}
|
|
|
|
// 检查蛇尾
|
|
updateTail(snakeA, snakeB) {
|
|
return new Promise((resolve, reject) => {
|
|
// snakeA 与 snakeB 做尾指针比较
|
|
let tailA = snakeA.last(),
|
|
tailB;
|
|
while (snakeB.length !== 0) {
|
|
tailB = snakeB.last();
|
|
// 尾节点匹配
|
|
if (tailA.data === tailB.data) {
|
|
// 执行 then 通道
|
|
return resolve();
|
|
}
|
|
// 不匹配
|
|
else {
|
|
snakeB.pop();
|
|
}
|
|
}
|
|
// 尾指针未匹配上,走 catch 通道
|
|
reject();
|
|
});
|
|
}
|
|
|
|
// 全量更新
|
|
wholeUpdate(snakeA, snakeB) {
|
|
console.log(">>>>>>>>>>>>>>>>>", "low performance");
|
|
// 把视图上的蛇回收
|
|
while (snakeB.length !== 0) {
|
|
snakeB.pop();
|
|
}
|
|
// 重头开始插入 snake
|
|
for (let { data } of snakeA) {
|
|
snakeB.unshift(data);
|
|
}
|
|
}
|
|
|
|
// 渲染
|
|
render() {
|
|
this.app.renderer.render(this.app.stage);
|
|
}
|
|
}
|
|
|