Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
G
game-stydy
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
谌继荃
game-stydy
Commits
aa9c2c59
Commit
aa9c2c59
authored
Mar 10, 2021
by
邱旭
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
08.FlppyBird-模拟重力
parent
5ca7642d
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
545 additions
and
0 deletions
+545
-0
09.FlppyBird-节点树.html
09.FlppyBird-节点树/09.FlppyBird-节点树.html
+202
-0
09.FlppyBird-节点树.md
09.FlppyBird-节点树/09.FlppyBird-节点树.md
+343
-0
No files found.
09.FlppyBird-节点树/09.FlppyBird-节点树.html
0 → 100644
View file @
aa9c2c59
<!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>
09.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
;
}
async
start
()
{
super
.
start
();
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>
/**
* 异步加载图片方法
* @param src 图片路径
* @returns {Promise<HTMLImageElement | null>}
*/
function
loadImgAsync
(
src
)
{
return
new
Promise
((
resolve
)
=>
{
const
img
=
new
Image
();
img
.
onload
=
()
=>
resolve
(
img
);
img
.
onerror
=
()
=>
{
console
.
error
(
`加载资源
${
src
}
失败`
);
resolve
(
null
);
};
img
.
src
=
src
;
});
}
/**
* FlppyBird
*/
class
FlppyBird
extends
GameStage
{
bird
;
async
reloadRes
()
{
const
path
=
"../images/bird/"
;
const
promises
=
[
"bird_01.png"
,
"bird_02.png"
,
"bird_03.png"
,
"land.png"
,
"pie_down.png"
,
"pie_up.png"
,
"background.png"
,
"start_button.png"
].
map
((
v
)
=>
{
return
loadImgAsync
(
`
${
path
}${
v
}
`
);
});
return
Promise
.
all
(
promises
);
}
async
start
()
{
// 创建鸟
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
,
5
);
this
.
addChild
(
bgMgr
);
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>
09.FlppyBird-节点树/09.FlppyBird-节点树.md
0 → 100644
View file @
aa9c2c59
# FlppyBird - 节点树
引入概念:
`节点树`
`封装游戏对象`
在游戏开发中可能会有千千万万个游戏对象,如果按照当前的开发模式,每个都在dom中预制,那是不可能的。
所以一般在游戏开发中,游戏有自己的节点树,统一管理节点的生命周期,数据更新和渲染。
本节点内容将要对之前的内容进行大量改造和封装,如果之前没有游戏开发经验,可能会很难理解
修改后的
`lib.js`
请看
`flppyBirdLib.js`
## 1.改造GameObject
既然叫节点树,那么每个游戏对象应该为树上的一个节点,有子节点,和父节点
改造
`GameObject`
-
1.添加保存子节点的变量
`children`
,和父节点的变量
`parent`
-
2.添加生命周期函数
`start`
`ready`
-
3.封装添加子节点
`addChild`
和删除子节点函数
`removeChild`
-
4.在数据更新和渲染更新中加入子节点的更新和渲染
-
5.将dom节点改为动态创建并支持在构造函数中传入类型,支持节点创建不同类型的dom元素
```
javascript
class
GameObject
{
dom
;
// 绑定的dom元素
children
=
[];
// 子节点
parent
;
// 父节点
/* ... */
constructor
(
type
=
"div"
)
{
this
.
dom
=
document
.
createElement
(
type
);
// 基础GameObject为div,Sprite为img
this
.
dom
.
style
.
position
=
"absolute"
;
}
/**
* 生命周期 start 所有构造函数完成,执行此函数
*/
async
start
()
{
}
/**
* 生命周期 start 加入显示列表执行此函数
*/
ready
()
{
}
/**
* 添加子节点
* @param child
*/
addChild
(
child
)
{
// 如果是别人的子节点,则先移除再添加到自己下面
if
(
child
.
parent
)
{
child
.
parent
.
removeChild
(
child
);
}
// 执行添加
this
.
dom
.
appendChild
(
child
.
dom
);
this
.
children
.
push
(
child
);
child
.
parent
=
this
;
// 容错:防止子类重写的start不是async函数
// TODO dom无法在节点不在渲染树的上的时候拿到clientWidth等属性,故将start和ready放在这里
(
async
()
=>
{
await
child
.
start
();
child
.
ready
();
})();
return
child
;
}
/**
* 删除子节点
* @param child
*/
removeChild
(
child
)
{
// 不是自己的子节点就提示错误
if
(
child
.
parent
!==
this
)
{
console
.
warn
(
"移除的节点必须是其子集"
);
return
null
;
}
// 执行销毁和移除
child
.
destroy
();
this
.
dom
.
removeChild
(
child
.
dom
);
this
.
children
.
splice
(
this
.
children
.
indexOf
(
child
),
1
);
child
.
parent
=
null
;
return
child
;
}
/**
* 抽离数据更新部分,并更新子节点
*/
update
()
{
this
.
children
.
forEach
((
child
)
=>
{
child
.
update
();
});
}
/**
* 抽离渲染部分,并渲染子节点
*/
render
()
{
/* ... */
// 添加渲染子节点部分
this
.
children
.
forEach
((
child
)
=>
{
child
.
render
();
});
}
}
```
## 2.封装`Sprite`
之前的Sprite只是简单的继承与GameObject并且绑定节点变为一个
`<img/>`
因为我们的GameObject已经经过改造,dom节点动态创建,所以,先还要封装一个
`Sprite`
-
创建
`Sprite`
类,继承
`GameObject`
-
支持在构造函数中传入
`src`
参数,即图片的链接
-
在构造父类的时候传入
`"img"`
作为
`type`
参数,这样父类会创建一个img标签
-
其他方法暂不重写
```
javascript
/**
* 抽象精灵Sprite
*/
class
Sprite
extends
GameObject
{
constructor
(
src
=
""
)
{
super
(
"img"
);
this
.
dom
.
src
=
src
;
}
}
```
## 3.封装`GameStage`
GameStage将作为游戏的主画布,默认创建一个
`div`
容器,并添加到
`body`
中
GameStage中包含游戏的主控逻辑,比如,游戏主循环,事件冒泡,事件循环,资源预加载等
-
创建
`GameStage`
类,继承
`GameObject`
-
在构造函数中吧创建的dom节点加入到
`body`
中
```
javascript
class
GameStage
extends
GameObject
{
constructor
()
{
super
();
document
.
body
.
appendChild
(
this
.
dom
);
this
.
_gameStart
();
this
.
loop
();
}
async
_gameStart
()
{
await
this
.
reloadRes
();
this
.
start
();
}
/**
* 预加载资源
* @returns {Promise<void>}
*/
async
reloadRes
()
{
}
/**
* 主循环
*/
loop
=
()
=>
{
requestAnimationFrame
(
this
.
loop
);
// 循环调用requestAnimationFrame
this
.
update
();
// 先数据更新
this
.
render
();
// 后渲染更新
}
}
```
## 4.修改 `Bird` 和 背景 的创建
-
删除预制的dom节点
-
修改
`Bird`
的构造函数,在
`super()`
中传入图片链接并实现
`ready`
方法
```
javascript
class
Bird
extends
Sprite
{
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
;
}
/* ... */
}
```
-
修改
`ScrollMgr`
的构造函数,实现
`start`
,将传入的两个元素加入到自己的子节点 实现
`ready`
,设置两个元素的位置
```
javascript
class
ScrollMgr
extends
GameObject
{
speed
;
// 滚动速度
bg1
;
// bg1
bg2
;
// bg2
constructor
(
bg1
,
bg2
,
speed
=
5
)
{
super
();
this
.
bg1
=
bg1
;
this
.
bg2
=
bg2
;
this
.
speed
=
speed
;
}
async
start
()
{
super
.
start
();
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
;
// 放在底部
}
/* ... */
}
```
## 5.创建`GameStage`
-
创建
`FlppyBird`
类继承
`GameStage`
-
实现
`reloadRes`
方法,预加载资源
-
实现
`start`
方法,将之前创建游戏对象的代码搬进来
-
创建
`FlppyBird`
实例
> 下面是一个异步加载图片的方法,可以用来预加载资源
```
javascript
/**
* 异步加载图片方法
* @param src 图片路径
* @returns {Promise<HTMLImageElement | null>}
*/
function
loadImgAsync
(
src
)
{
return
new
Promise
((
resolve
)
=>
{
const
img
=
new
Image
();
img
.
onload
=
()
=>
resolve
(
img
);
img
.
onerror
=
()
=>
{
console
.
error
(
`加载资源
${
src
}
失败`
);
resolve
(
null
);
};
img
.
src
=
src
;
});
}
```
```
javascript
/**
* FlppyBird
*/
class
FlppyBird
extends
GameStage
{
bird
;
async
reloadRes
()
{
const
path
=
"../images/bird/"
;
const
promises
=
[
"bird_01.png"
,
"bird_02.png"
,
"bird_03.png"
,
"land.png"
,
"pie_down.png"
,
"pie_up.png"
,
"background.png"
,
"start_button.png"
].
map
((
v
)
=>
{
return
loadImgAsync
(
`
${
path
}${
v
}
`
);
});
return
Promise
.
all
(
promises
);
}
async
start
()
{
// 创建鸟
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
,
5
);
this
.
addChild
(
bgMgr
);
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
();
```
# 再次运行案例,发现效果和刚才一样,牛逼!!
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment