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
43d0a4c1
Commit
43d0a4c1
authored
Mar 10, 2021
by
邱旭
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
07.FlppyBird-背景循环滚动
parent
7306b5d0
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
558 additions
and
0 deletions
+558
-0
07.FlppyBird-背景循环滚动.html
07.FlppyBird-背景循环滚动/07.FlppyBird-背景循环滚动.html
+202
-0
07.FlppyBird-背景循环滚动.md
07.FlppyBird-背景循环滚动/07.FlppyBird-背景循环滚动.md
+227
-0
lib.js
lib/lib.js
+129
-0
No files found.
07.FlppyBird-背景循环滚动/07.FlppyBird-背景循环滚动.html
0 → 100644
View file @
43d0a4c1
<!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>
07.FlppyBird-背景循环滚动
</title>
<style>
html
,
body
{
margin
:
0
;
padding
:
0
;
width
:
100%
;
height
:
100%
;
overflow
:
hidden
;
}
</style>
<script
src=
"../lib/lib.js"
></script>
</head>
<body>
<div
id=
"bg"
>
<img
id=
"bg_1"
src=
"../images/bird/background.png"
>
<img
id=
"bg_2"
src=
"../images/bird/background.png"
>
</div>
<div
id=
"land"
>
<img
id=
"land_1"
src=
"../images/bird/land.png"
>
<img
id=
"land_2"
src=
"../images/bird/land.png"
>
</div>
<img
id=
"bird"
src=
"../images/bird/bird_01.png"
>
</body>
<script>
/**
* 屏幕宽高
* @type {{width: number, height: number}}
*/
const
winSize
=
{
width
:
document
.
body
.
clientWidth
,
height
:
document
.
body
.
clientHeight
,
}
</script>
<script>
class
Bird
extends
GameObject
{
speed
;
// bird的速度 每次移动多少距离
constructor
(
id
,
speed
=
15
)
{
super
(
id
);
this
.
speed
=
speed
;
// 保存speed
this
.
setPosition
(
150
,
150
);
// 给个默认位置
}
update
()
{
super
.
update
();
const
{
top
,
left
}
=
this
.
getPosition
();
const
speed
=
this
.
speed
;
// 计算新的top
if
(
top
!==
clickPos
.
top
)
{
const
dis
=
clickPos
.
top
-
top
;
// 计算差值
const
dir
=
dis
>
0
?
1
:
-
1
;
// 计算在top上移动的方向 1 正向移动 或 -1 反向移动;
// 如果速度过快,本次移动直接过头了(即差值
<
速度
)
,就直接移动到指定
top
if
(
Math
.
abs
(
dis
)
<
speed
)
{
this
.
transform
.
position
.
top
=
clickPos
.
top
;
}
else
{
this
.
transform
.
position
.
top
=
top
+
dir
*
speed
;
// 计算新的top,新的位置 = 之前的位置 + 方向 * 速度
}
}
// 用相同的方式计算left
if
(
left
!==
clickPos
.
left
)
{
const
dis
=
clickPos
.
left
-
left
;
const
dir
=
dis
>
0
?
1
:
-
1
;
if
(
Math
.
abs
(
dis
)
<
speed
)
{
this
.
transform
.
position
.
left
=
clickPos
.
left
;
}
else
{
this
.
transform
.
position
.
left
=
left
+
dir
*
speed
;
}
}
}
}
class
ScrollMgr
extends
GameObject
{
speed
;
// 滚动速度
bg1
;
// bg1
bg2
;
// bg2
constructor
(
id
,
bg1
,
bg2
,
speed
=
5
)
{
super
(
id
);
this
.
bg1
=
bg1
;
this
.
bg2
=
bg2
;
this
.
speed
=
speed
;
bg1
.
setPosition
(
winSize
.
height
-
bg1
.
getSize
().
height
);
// 放在底部
bg2
.
setPosition
(
winSize
.
height
-
bg2
.
getSize
().
height
);
// 放在底部
}
update
()
{
super
.
update
();
// 获取一些参数
let
{
top
:
bg1Top
,
left
:
bg1Left
}
=
this
.
bg1
.
getPosition
();
const
bg1Width
=
this
.
bg1
.
getSize
().
width
;
const
bg2Top
=
this
.
bg2
.
getPosition
().
top
;
// 计算位置
bg1Left
-=
this
.
speed
;
// 计算位置
this
.
bg1
.
setPosition
(
bg1Top
,
bg1Left
);
// 设置bg1的位置
this
.
bg2
.
setPosition
(
bg2Top
,
bg1Left
+
this
.
bg1
.
getSize
().
width
);
// bg2跟在bg1后面
// 如果超出屏幕则交换bg1和bg2,为了做到循环滚动
if
(
bg1Left
<=
-
bg1Width
)
{
const
temp
=
this
.
bg1
;
this
.
bg1
=
this
.
bg2
;
this
.
bg2
=
temp
;
}
}
}
</script>
<script>
const
clickPos
=
{
// 鼠标点击的位置 bird将要的到达的位置
top
:
150
,
left
:
150
,
}
// 使用mousedown监听鼠标按下,并获得鼠标点击的位置
const
mouseDown
=
(
e
)
=>
{
// 记录bird将要到达的位置,使用动画慢慢到达
clickPos
.
top
=
e
.
clientY
;
clickPos
.
left
=
e
.
clientX
;
}
document
.
addEventListener
(
'mousedown'
,
mouseDown
);
</script>
<script>
// 创建鸟
const
bird
=
new
Bird
(
"bird"
,
15
);
// 创建背景
const
bg1
=
new
GameObject
(
"bg_1"
);
const
bg2
=
new
GameObject
(
"bg_2"
);
const
bgMgr
=
new
ScrollMgr
(
"bg"
,
bg1
,
bg2
,
2
);
// 创建地面
const
land1
=
new
GameObject
(
"land_1"
);
const
land2
=
new
GameObject
(
"land_2"
);
const
landMgr
=
new
ScrollMgr
(
"land"
,
land1
,
land2
,
5
);
// 将背景放在地面的上面,因为默认top是0,子节点在内部定位在底部,所以只需要把背景定位在负的land的高度就可以了
bgMgr
.
setPosition
(
-
land1
.
getSize
().
height
);
/**
* 数据更新
*/
function
update
()
{
// bird更新
bird
.
update
();
// 背景更新
bgMgr
.
update
();
bg1
.
update
();
bg2
.
update
();
// 地面更新
landMgr
.
update
();
land1
.
update
();
land2
.
update
();
}
/**
* 渲染更新
*/
function
render
()
{
// bird渲染
bird
.
render
();
// 背景渲染
bgMgr
.
render
();
bg1
.
render
();
bg2
.
render
();
// 地面渲染
landMgr
.
render
();
land1
.
render
();
land2
.
render
();
}
function
loop
()
{
requestAnimationFrame
(
loop
);
// 循环调用requestAnimationFrame
update
();
// 先数据更新
render
();
// 后渲染更新
}
loop
();
</script>
</html>
07.FlppyBird-背景循环滚动/07.FlppyBird-背景循环滚动.md
0 → 100644
View file @
43d0a4c1
# FlppyBird - 背景循环滚动
引入概念:
`无缝滚动`
`组件抽象`
`游戏优化-节省内存`
经过之前的内容我们已经在游戏开发中实践了面向对象
按照这样的写法,游戏开发会变得简单,代码可维护性更强,在面向对象的开发模式上,可抽象出一些通用的东西,成为组件,还可以开发可视化编辑器,提高效率
本节开始将会带大家在之前的基础上一步一步开发一款曾经举世闻名的小游戏
`FlppyBird`
## 背景循环滚动
在游戏中内存是非常宝贵的,而占用内存的资源一般是图片,音频,视频,动画文件等。
FlppyBird占内存不过50MB,但是关卡背景图片却可以无限延长
> 图片占用内存的计算方式:`长 * 宽 * 每个像素的位数 / 8`
查阅图片内存计算公式,还以为百度在骗人
其实FlppyBird的背景图片只有320
*
640这么大,通过缩放等方式适配了你的手机,再通过两张图片循环滚动的方式达到背景图片无限长度的效果
本节将来教大家实现背景无限滚动的效果
在上一节课的基础上添加以下html代码
注意层级,bg应该在bird的下面
## 1.准备工作
```
html
<div
id=
"bg"
>
<img
id=
"bg_1"
src=
"../images/bird/background.png"
>
<img
id=
"bg_2"
src=
"../images/bird/background.png"
>
</div>
```
创建一个变量,让我们可以方便的拿到屏幕宽高
```
javascript
/**
* 屏幕宽高
* @type {{width: number, height: number}}
*/
const
winSize
=
{
width
:
document
.
body
.
clientWidth
,
height
:
document
.
body
.
clientHeight
,
}
```
在之前写好的GameObject类中添加一个方法,该方法用于获取这个游戏对象的宽高
```
javascript
/**
* 抽象了一个简单的GameObject
*/
class
GameObject
{
/* ... */
/**
* 获得宽高
* @returns {{width: number, height: number}}
*/
getSize
()
{
const
{
x
,
y
}
=
this
.
getScale
();
return
{
width
:
this
.
dom
.
clientWidth
,
height
:
this
.
dom
.
clientHeight
,
}
}
/* ... */
}
```
## 2.实现滚动
实现一个背景管理器,继承GameObject中通用的一些生命周期
```
javascript
class
BgMgr
extends
GameObject
{
speed
;
// 滚动速度
bg1
;
// bg1
bg2
;
// bg2
constructor
(
id
,
bg1
,
bg2
,
speed
=
5
)
{
super
(
id
);
this
.
bg1
=
bg1
;
this
.
bg2
=
bg2
;
this
.
speed
=
speed
;
bg1
.
setPosition
(
winSize
.
height
-
bg1
.
getSize
().
height
);
// 放在底部
bg2
.
setPosition
(
winSize
.
height
-
bg2
.
getSize
().
height
);
// 放在底部
}
update
()
{
super
.
update
();
// 获取一些参数
let
{
top
:
bg1Top
,
left
:
bg1Left
}
=
this
.
bg1
.
getPosition
();
const
bg1Width
=
this
.
bg1
.
getSize
().
width
;
const
bg2Top
=
this
.
bg2
.
getPosition
().
top
;
// 计算位置
bg1Left
-=
this
.
speed
;
// 计算位置
this
.
bg1
.
setPosition
(
bg1Top
,
bg1Left
);
// 设置bg1的位置
this
.
bg2
.
setPosition
(
bg2Top
,
bg1Left
+
this
.
bg1
.
getSize
().
width
);
// bg2跟在bg1后面
// 如果移出屏幕则交换bg1和bg2,为了做到循环滚动
if
(
bg1Left
<=
-
bg1Width
)
{
const
temp
=
this
.
bg1
;
this
.
bg1
=
this
.
bg2
;
this
.
bg2
=
temp
;
}
}
}
```
将两张背景图定位在最底下,bg1向左移动,bg2紧跟在背景1后面,如果bg1移出了屏幕,则交换变量bg1和bg2,那么bg1将跟在bg2后面移动,实现了循环滚动
将他们加入渲染和更新队列
```
javascript
/**
* 数据更新
*/
function
update
()
{
// bird更新
bird
.
update
();
// 背景更新
bgMgr
.
update
();
bg1
.
update
();
bg2
.
update
();
}
/**
* 渲染更新
*/
function
render
()
{
// bird渲染
bird
.
render
();
// 背景渲染
bgMgr
.
render
();
bg1
.
render
();
bg2
.
render
();
}
```
再次运行案例,发现效果已经实现
![
07_1.gif
](
../images/07_1.gif
)
## 3.地面滚动
在FlppyBird中还有一个地面也在滚动。
因为我们已经实现了背景滚动,地面滚动当然和背景的逻辑一样
但是,这就很巧了,刚才我们实现的背景滚动功能比较完善,只需要用相同的方式创建地面就可以做到地面滚动
于是乎,我们将BgMgr的名字改成ScrollMgr
```
javascript
class
ScrollMgr
extends
GameObject
{
/* ... */
}
```
并更新代码,然后创建我们的地面,同时加入渲染列表
> 为地面和背景赋予不同的滚动速度可以得到远处慢,近处快的效果
```
javascript
// 创建鸟
const
bird
=
new
Bird
(
"bird"
,
15
);
// 创建背景
const
bg1
=
new
GameObject
(
"bg_1"
);
const
bg2
=
new
GameObject
(
"bg_2"
);
const
bgMgr
=
new
ScrollMgr
(
"bg"
,
bg1
,
bg2
,
2
);
// 创建地面
const
land1
=
new
GameObject
(
"land_1"
);
const
land2
=
new
GameObject
(
"land_2"
);
const
landMgr
=
new
ScrollMgr
(
"land"
,
land1
,
land2
,
5
);
// 将背景放在地面的上面,因为默认top是0,子节点在内部定位在底部,所以只需要把背景定位在负的land的高度就可以了
bgMgr
.
setPosition
(
-
land1
.
getSize
().
height
);
/**
* 数据更新
*/
function
update
()
{
// bird更新
bird
.
update
();
// 背景更新
bgMgr
.
update
();
bg1
.
update
();
bg2
.
update
();
// 地面更新
landMgr
.
update
();
land1
.
update
();
land2
.
update
();
}
/**
* 渲染更新
*/
function
render
()
{
// bird渲染
bird
.
render
();
// 背景渲染
bgMgr
.
render
();
bg1
.
render
();
bg2
.
render
();
// 地面渲染
landMgr
.
render
();
land1
.
render
();
land2
.
render
();
}
```
运行案例,发现效果已经实现
![
07_2
](
../images/07_2.gif
)
lib/lib.js
0 → 100644
View file @
43d0a4c1
/*
* lib.js
* Created by 还有醋v on 2021/3/8.
* Copyright © 2021 haiyoucuv. All rights reserved.
*/
/**
* 抽象了一个简单的GameObject
*/
class
GameObject
{
id
;
// 绑定的dom元素的id
dom
;
// 绑定的dom元素
/**
* transform 表示显示对象的变换
* @type {{rotate: number, scale: {x: number, y: number}, position: {top: number, left: number}}}
*/
transform
=
{
position
:
{
top
:
0
,
left
:
0
},
// 位置
scale
:
{
x
:
1
,
y
:
1
},
// 缩放
rotate
:
0
,
// 旋转
}
constructor
(
id
)
{
this
.
id
=
id
;
this
.
dom
=
document
.
getElementById
(
id
);
// 在构造函数中绑定dom元素
this
.
dom
.
style
.
position
=
"absolute"
;
this
.
dom
.
style
.
transformOrigin
=
"center"
;
}
/**
* 获得宽高
* @returns {{width: number, height: number}}
*/
getSize
()
{
const
{
x
,
y
}
=
this
.
getScale
();
return
{
width
:
this
.
dom
.
clientWidth
,
height
:
this
.
dom
.
clientHeight
,
}
}
/**
* 设置Position
* @param top
* @param left
*/
setPosition
(
top
=
this
.
transform
.
position
.
top
,
left
=
this
.
transform
.
position
.
left
)
{
this
.
transform
.
position
.
top
=
top
;
this
.
transform
.
position
.
left
=
left
;
}
/**
* 设置Scale
* @param x
* @param y
*/
setScale
(
x
=
this
.
transform
.
scale
.
x
,
y
=
this
.
transform
.
scale
.
y
)
{
this
.
transform
.
scale
.
x
=
x
;
this
.
transform
.
scale
.
y
=
y
;
}
/**
* 设置Rotate
* @param rotate
*/
setRotate
(
rotate
=
this
.
transform
.
rotate
)
{
this
.
transform
.
rotate
=
rotate
;
}
/**
* 获得Position
* @returns {{top: number, left: number}}
*/
getPosition
()
{
return
{
top
:
this
.
transform
.
position
.
top
,
left
:
this
.
transform
.
position
.
left
,
}
}
/**
* 获得Scale
* @returns {{x: number, y: number}}
*/
getScale
()
{
return
{
x
:
this
.
transform
.
scale
.
x
,
y
:
this
.
transform
.
scale
.
y
,
}
}
/**
* 获得Rotate
* @returns {number}
*/
getRotate
()
{
return
this
.
transform
.
rotate
;
}
/**
* 抽离数据更新部分
*/
update
()
{
}
/**
* 抽离渲染部分
*/
render
()
{
const
{
top
,
left
}
=
this
.
getPosition
();
const
{
x
:
scaleX
,
y
:
scaleY
}
=
this
.
getScale
();
const
rotate
=
this
.
getRotate
();
this
.
dom
.
style
.
top
=
top
+
"px"
;
this
.
dom
.
style
.
left
=
left
+
"px"
;
this
.
dom
.
style
.
transform
=
`scale(
${
scaleX
}
,
${
scaleY
}
) rotate(
${
rotate
}
deg)`
;
}
/**
* 抽离销毁部分
*/
destroy
()
{
}
}
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