Commit 38d1a0d6 authored by 邱旭's avatar 邱旭

10.FlppyBird-创建障碍

parent 07fbd997
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
<title>11.FlppyBird-对象池</title>
<style>
html, body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
<script src="../lib/flppyBirdLib.js"></script>
</head>
<body>
</body>
<script>
/**
* 屏幕宽高
* @type {{width: number, height: number}}
*/
const winSize = {
width: document.body.clientWidth,
height: document.body.clientHeight,
}
</script>
<script>
/**
* Bird
*/
class Bird extends Sprite {
speed; // bird的速度 每次移动多少距离
gravity; // 重力加速度
constructor(gravity = 0.2, speed = 0) {
super("../images/bird/bird_01.png");
this.speed = speed; // 保存speed
this.gravity = gravity; // 保存gravity
}
ready() {
super.ready();
// 放到合适的位置
const { width, height } = this.size;
this.top = (winSize.height - height) / 2 - 100;
this.left = (winSize.width - width) / 2;
}
update() {
super.update();
// v = v0 + a * t²
this.speed += this.gravity; // 速度 = 速度 + 加速度 * 时间²
this.top += this.speed; // 更新位置
}
}
/**
* 滚动器
*/
class ScrollMgr extends GameObject {
speed; // 滚动速度
bg1; // bg1
bg2; // bg2
constructor(bg1, bg2, speed = 5) {
super();
this.bg1 = bg1;
this.bg2 = bg2;
this.speed = speed;
this.addChild(this.bg1);
this.addChild(this.bg2);
}
ready() {
super.ready();
this.bg1.top = winSize.height - this.bg1.size.height; // 放在底部
this.bg2.top = winSize.height - this.bg2.size.height; // 放在底部
}
update() {
super.update();
// 获取一些参数
let bg1Left = this.bg1.left;
const bg1Width = this.bg1.size.width;
// 计算位置
bg1Left -= this.speed; // 计算位置
this.bg1.left = bg1Left; // 设置bg1的位置
this.bg2.left = bg1Left + this.bg1.size.width; // bg2跟在bg1后面
// 如果超出屏幕则交换bg1和bg2,为了做到循环滚动
if (bg1Left <= -bg1Width) {
const temp = this.bg1;
this.bg1 = this.bg2;
this.bg2 = temp;
}
}
}
</script>
<script>
/**
* Pie
*/
class Pie extends GameObject {
up; // 钢管上部分
down; // 钢管下部分
constructor() {
super();
this.up = this.addChild(new Sprite("../images/bird/pie.png"));
this.down = this.addChild(new Sprite("../images/bird/pie.png"));
}
/**
* 注意此函数非真正的ready函数,在被addChild的时候都会执行一次
*/
ready() {
super.ready();
// 随机中间的距离
const dis = Math.random() * 80 + 100;
this.down.top = this.up.size.height + dis;
}
// 这里重写一下size,因为子节点使用绝对定位的不计算入包围盒内,真尴尬
get size() {
return {
width: this.up.size.width,
height: this.down.top + this.down.size.height
}
}
}
/**
* PieMgr
*/
class PieMgr extends GameObject {
pieArr = []; // 所有的Pie
speed; // Pie移动的速度
delay; // 添加Pie的延时
constructor(speed, delay) {
super();
this.speed = speed;
this.delay = delay;
}
ready() {
super.ready();
// 创建计时器来固定时间创建Pie
setInterval(() => this.createPie(), this.delay);
}
/**
* 创建Pie
*/
createPie() {
// 使用对象池 如果对象池中取不到,说明对象池空了,需要新创建
const pie = ObjectPool.get("pie") || new Pie();
this.addChild(pie);
this.pieArr.push(pie); // 加入列表统一管理
pie.top = Math.random() * -150; // 高度随机
pie.left = winSize.width; // 从屏幕左边出现
}
update() {
super.update();
// 所有的Pie同时向左移动
const { speed, pieArr } = this;
pieArr.forEach((pie) => {
pie.left -= speed;
if (pie.left <= -pie.size.width) { // 如果移出屏幕
this.pieArr.splice(this.pieArr.indexOf(pie), 1); // 从托管列表里移除
this.removeChild(pie); // 从子节点移除
ObjectPool.put("pie", pie); // 加入对象池
}
});
}
}
</script>
<script>
/**
* FlppyBird
*/
class FlppyBird extends GameStage {
bird;
async reloadRes() {
const path = "../images/bird/";
const promises = [
"bird_01.png", "bird_02.png", "bird_03.png", "pie.png",
"land.png", "background.png", "start_button.png"
].map((v) => {
return loadImgAsync(`${path}${v}`);
});
return Promise.all(promises);
}
async ready() {
// 创建鸟
const bird = this.bird = new Bird();
// 创建背景
const bg1 = new Sprite("../images/bird/background.png");
const bg2 = new Sprite("../images/bird/background.png");
const bgMgr = new ScrollMgr(bg1, bg2, 2);
// 创建地面
const land1 = new Sprite("../images/bird/land.png");
const land2 = new Sprite("../images/bird/land.png");
const landMgr = new ScrollMgr(land1, land2, 4);
const pieMgr = new PieMgr(4, 1000); // 创建PieMgr
this.addChild(bgMgr);
this.addChild(pieMgr); // 加在背景和地面的中间
this.addChild(landMgr);
this.addChild(bird);
// 将背景放在地面的上面,因为默认top是0,子节点在内部定位在底部,所以只需要把背景定位在负的land的高度就可以了
bgMgr.top = -land1.size.height;
// 使用mousedown监听鼠标按下,并获得鼠标点击的位置
document.addEventListener('mousedown', this.mouseDown);
}
mouseDown = () => {
this.bird.speed = -8;
}
destroy() {
super.destroy();
document.removeEventListener('mousedown', this.mouseDown);
}
}
// 创建游戏实例
new FlppyBird();
</script>
</html>
# FlppyBird-对象池
引入概念:`对象池`
上节我们完成了障碍的动态创建
但是运行一段时间发现,dom节点树上有n个障碍,卡得一批
这些移出屏幕的障碍,不仅占了大量内存,而且完全不需要更新和渲染
![11_1.png](../images/11_1.png)
## 对象池
使用对象池,回收对象,保存在池中,需要的时候不必再重新创建,只需要从池中获取就可以
对象移除显示列表的时候,放回池中就可以了
- 创建`ObjectPool`
- 用一个静态变量来保存对象
- 实现静态方法`put`接口,传入`name`来区分保存对象的类型,这样可以保存不同的类型的对象
- 实现静态方法`get`接口,传入`name`来获取相应的类型的对象
```javascript
/**
* 一个简单的通用对象池
*/
class ObjectPool {
static objs = {};
static put(name, obj) {
const pool = ObjectPool.objs[name] || (ObjectPool.objs[name] = []);
pool.push(obj);
}
static get(name) {
const pool = ObjectPool.objs[name] || (ObjectPool.objs[name] = []);
if (pool.length <= 0) {
return null;
}
return pool.shift();
}
}
```
## 改造`PieMgr`
- 创建`Pie`时先从对象池中获取,如果没有,则新创建
-`Pie`移出屏幕后,从托管列表中移除,从子节点移除,并且放回对象池
```javascript
class PieMgr extends GameObject {
/* ... */
/**
* 创建Pie
*/
createPie() {
// 使用对象池 如果对象池中取不到,说明对象池空了,需要新创建
const pie = ObjectPool.get("pie") || new Pie();
this.addChild(pie);
this.pieArr.push(pie); // 加入列表统一管理
pie.top = Math.random() * -150; // 高度随机
pie.left = winSize.width; // 从屏幕左边出现
}
update() {
super.update();
// 所有的Pie同时向左移动
const { speed, pieArr } = this;
pieArr.forEach((pie) => {
pie.left -= speed;
if (pie.left <= -pie.size.width) { // 如果移出屏幕
this.pieArr.splice(this.pieArr.indexOf(pie), 1); // 从托管列表里移除
this.removeChild(pie); // 从子节点移除
ObjectPool.put("pie", pie); // 加入对象池
}
});
}
}
```
运行案例,挂机10分钟,一点也不卡,显示列表最多也只有两个`Pie`同时存在
# 挂机一天也不卡,牛逼
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment