Initial commit
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
[
|
||||||
|
"@babel/preset-env",
|
||||||
|
{
|
||||||
|
"targets": {
|
||||||
|
"browsers": [
|
||||||
|
"ios >= 9",
|
||||||
|
"android >= 4"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
node_modules
|
||||||
|
.idea/
|
||||||
|
npm-debug.log
|
||||||
|
yarn.lock
|
||||||
|
.DS_Store
|
||||||
|
|
20
LICENSE
|
@ -1,9 +1,21 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) <year> <copyright holders>
|
Copyright (c) 2018 BMQB, Inc
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
134
README.md
|
@ -1,3 +1,133 @@
|
||||||
# towergame
|
[![](http://www.writebug.com/myres/static/uploads/2021/10/22/9c92c17a3018530ae6c17b70c748ed01.writebug)](LICENSE)
|
||||||
|
|
||||||
盖楼游戏 html5 canvas tower building game
|
<h1 align="center">盖楼游戏</h1>
|
||||||
|
<p align="center"><img src="https://o2qq673j2.qnssl.com/tower-loading.gif"/></p>
|
||||||
|
|
||||||
|
> 一个基于 Canvas 的盖楼游戏
|
||||||
|
|
||||||
|
> Tower Building Game (Tower Bloxx Deluxe Skyscraper)
|
||||||
|
|
||||||
|
## Demo 预览
|
||||||
|
|
||||||
|
<p align="center"><img src="https://user-images.githubusercontent.com/17680888/47480922-93a20c00-d864-11e8-8f7c-6d1d60184730.gif"/></p>
|
||||||
|
<h2 align="center"><a href="https://iamkun.github.io/tower_game">在线预览地址 (Demo Link)</a></h2>
|
||||||
|
<h4 align="center">手机设备可以扫描下方二维码</h4>
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://user-images.githubusercontent.com/17680888/47480646-abc55b80-d863-11e8-9337-4ea768ebe55d.png" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Game Rule 游戏规则
|
||||||
|
|
||||||
|
以下为默认游戏规则,也可参照下节自定义游戏参数
|
||||||
|
|
||||||
|
- 每局游戏生命值为 3,掉落一块楼层生命值减 1,掉落 3 块后游戏结束,单局游戏无时间限制
|
||||||
|
- 成功盖楼加 25 分,完美盖楼加 50 分,连续完美盖楼额外加 25 分,楼层掉落扣除生命值 1,单局游戏共有 3 次掉落机会
|
||||||
|
|
||||||
|
栗子:第一块完美盖楼加 50 分,第二块连续完美盖楼加 75 分,第三块连续完美盖楼加 100 分,依此类推……
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://o2qq673j2.qnssl.com/Fv7ewqHHXeAnUAlF7AI9ndQulEOC" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Customise 自定义
|
||||||
|
|
||||||
|
```
|
||||||
|
git http://git.writebug.com/NGQwMzBkYmY3/towergame.git
|
||||||
|
cd tower_game
|
||||||
|
npm install
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
打开 `http://localhost:8082`
|
||||||
|
|
||||||
|
- 图片、音频资源可以直接替换 `assets` 目录下对应的资源文件
|
||||||
|
- 游戏规则可以修改 `index.html` 文件 `L480` 的 `option` 对象
|
||||||
|
|
||||||
|
## Option 自定义选项
|
||||||
|
|
||||||
|
可以使用以下 `option` 表格里的参数,完成游戏自定义,**所有参数都是非必填项**
|
||||||
|
|
||||||
|
| Option | Type | Description |
|
||||||
|
| -------------------------------------------- | -------- | --------------------- |
|
||||||
|
| width | number | 游戏主画面宽度 |
|
||||||
|
| height | number | 游戏主画面高度 |
|
||||||
|
| canvasId | string | Canvas 的 DOM ID |
|
||||||
|
| soundOn | boolean | 是否开启声音 |
|
||||||
|
| successScore | number | 成功盖楼分数 |
|
||||||
|
| perfectScore | number | 完美盖楼额外奖励分数 |
|
||||||
|
| <a href="#hookspeed">hookSpeed</a> | function | 钩子平移速度 |
|
||||||
|
| <a href="#hookangle">hookAngle</a> | function | 钩子摆动角度 |
|
||||||
|
| <a href="#landblockspeed">landBlockSpeed</a> | function | 下方楼房横向速度 |
|
||||||
|
| <a href="#setgamescore">setGameScore</a> | function | 当前游戏分数 hook |
|
||||||
|
| <a href="#setgamesuccess">setGameSuccess</a> | function | 当前游戏成功次数 hook |
|
||||||
|
| <a href="#setgamefailed">setGameFailed</a> | function | 当前游戏失败次数 hook |
|
||||||
|
|
||||||
|
#### hookSpeed
|
||||||
|
|
||||||
|
钩子平移速度
|
||||||
|
函数接收两个参数,当前成功楼层和当前分数,返回速度数值
|
||||||
|
|
||||||
|
```
|
||||||
|
function(currentFloor, currentScore) {
|
||||||
|
return number
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### hookAngle
|
||||||
|
|
||||||
|
钩子摆动角度
|
||||||
|
函数接收两个参数,当前成功楼层和当前分数,返回角度数值
|
||||||
|
|
||||||
|
```
|
||||||
|
function(currentFloor, currentScore) {
|
||||||
|
return number
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### landBlockSpeed
|
||||||
|
|
||||||
|
下方楼房平移速度
|
||||||
|
函数接收两个参数,当前成功楼层和当前分数,返回速度数值
|
||||||
|
|
||||||
|
```
|
||||||
|
function(currentFloor, currentScore) {
|
||||||
|
return number
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### setGameScore
|
||||||
|
|
||||||
|
当前游戏分数 hook
|
||||||
|
函数接收一个参数,当前游戏分数
|
||||||
|
|
||||||
|
```
|
||||||
|
function(score) {
|
||||||
|
// your logic
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### setGameSuccess
|
||||||
|
|
||||||
|
当前游戏成功次数 hook
|
||||||
|
函数接收一个参数,当前游戏成功次数
|
||||||
|
|
||||||
|
```
|
||||||
|
function(successCount) {
|
||||||
|
// your logic
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### setGameFailed
|
||||||
|
|
||||||
|
当前游戏失败次数 hook
|
||||||
|
函数接收一个参数,当前游戏失败次数
|
||||||
|
|
||||||
|
```
|
||||||
|
function(failedCount) {
|
||||||
|
// your logic
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT license.
|
||||||
|
|
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 273 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 928 B |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 6.1 KiB |
|
@ -0,0 +1,571 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0,maximum-scale=1.0, user-scalable=no,minimal-ui">
|
||||||
|
<title>Tower</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
background: #FFF;
|
||||||
|
height: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #F95240 url(./assets/main-bg.png)
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-height: 560px) {
|
||||||
|
html {
|
||||||
|
font-size: 100px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-height: 640px) {
|
||||||
|
html {
|
||||||
|
font-size: 112.5px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-height: 720px) {
|
||||||
|
html {
|
||||||
|
font-size: 125px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-height: 800px) {
|
||||||
|
html {
|
||||||
|
font-size: 137.5px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-height: 880px) {
|
||||||
|
html {
|
||||||
|
font-size: 150px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-height: 960px) {
|
||||||
|
html {
|
||||||
|
font-size: 162.5px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-height: 1040px) {
|
||||||
|
html {
|
||||||
|
font-size: 180px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-height: 1200px) {
|
||||||
|
html {
|
||||||
|
font-size: 200px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 17.6vh
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none
|
||||||
|
}
|
||||||
|
|
||||||
|
li, ul, ol {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear {
|
||||||
|
clear: both
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
background-color: #F05A50;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading .main {
|
||||||
|
width: 60%;
|
||||||
|
margin: 0 auto;
|
||||||
|
color: #FFF
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading .main img {
|
||||||
|
width: 60%;
|
||||||
|
margin: 1rem auto 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading .main .title {
|
||||||
|
font-size: .3rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading .main .text {
|
||||||
|
font-size: .15rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading .main .bar {
|
||||||
|
height: .12rem;
|
||||||
|
width: 100%;
|
||||||
|
border: 3px solid #FFF;
|
||||||
|
border-radius: .6rem;
|
||||||
|
margin: .1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading .main .bar .sub {
|
||||||
|
height: .1rem;
|
||||||
|
width: 98%;
|
||||||
|
margin: .008rem auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading .main .bar .percent {
|
||||||
|
height: 100%;
|
||||||
|
width: 0;
|
||||||
|
background-color: #FFF;
|
||||||
|
border-radius: .6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading .logo {
|
||||||
|
position: absolute;
|
||||||
|
bottom: .3rem;
|
||||||
|
left: 0;
|
||||||
|
right: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading .logo img {
|
||||||
|
width: 1rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing .title {
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing .logo {
|
||||||
|
width: 30%;
|
||||||
|
position: absolute;
|
||||||
|
right: .2rem;
|
||||||
|
top: .2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing .action-2 {
|
||||||
|
position: absolute;
|
||||||
|
bottom: .2rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing .start {
|
||||||
|
width: 65%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slideTop {
|
||||||
|
-webkit-animation: st 1s ease-in-out;
|
||||||
|
animation: st 1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes st {
|
||||||
|
0% {
|
||||||
|
transform: translateZ(0)
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate3d(0, -100%, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes st {
|
||||||
|
0% {
|
||||||
|
transform: translateZ(0)
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate3d(0, -100%, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.slideBottom {
|
||||||
|
-webkit-animation: sb 1s ease-in-out;
|
||||||
|
animation: sb 1s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes sb {
|
||||||
|
0% {
|
||||||
|
transform: translateZ(0)
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate3d(0, 200%, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sb {
|
||||||
|
0% {
|
||||||
|
transform: translateZ(0)
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate3d(0, 200%, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.swing {
|
||||||
|
-webkit-animation: sw 2s ease-in-out alternate infinite;
|
||||||
|
animation: sw 2s ease-in-out alternate infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes sw {
|
||||||
|
0% {
|
||||||
|
transform: rotate(5deg);
|
||||||
|
transform-origin: top center;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(-5deg);
|
||||||
|
transform-origin: top center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sw {
|
||||||
|
0% {
|
||||||
|
transform: rotate(5deg);
|
||||||
|
transform-origin: top center;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(-5deg);
|
||||||
|
transform-origin: top center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .mask {
|
||||||
|
background-color: #000;
|
||||||
|
opacity: .6;
|
||||||
|
position: fixed;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .modal-content {
|
||||||
|
position: fixed;
|
||||||
|
height: 100%;
|
||||||
|
width: 90%;
|
||||||
|
margin-top: .3rem;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .main {
|
||||||
|
width: 85%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .container {
|
||||||
|
position: relative
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .bg {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .modal-main {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
margin-top: -0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .over-img {
|
||||||
|
width: 45%;
|
||||||
|
margin: .8rem auto 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .over-score {
|
||||||
|
margin-top: -0.2rem;
|
||||||
|
font-size: .5rem;
|
||||||
|
color: #FF735C;
|
||||||
|
text-shadow: -2px -2px 0 #FFF, 2px -2px 0 #FFF, -2px 2px 0 #FFF, 2px 2px 0 #FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .tip {
|
||||||
|
font-size: .16rem;
|
||||||
|
color: #9B724E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .over-button-b {
|
||||||
|
width: 70%;
|
||||||
|
margin: 0.1rem auto 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.wxShare {
|
||||||
|
background: #000;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 11;
|
||||||
|
opacity: .9
|
||||||
|
}
|
||||||
|
|
||||||
|
.wxShare img {
|
||||||
|
width: 50%;
|
||||||
|
float: right;
|
||||||
|
margin: 10px 10px 0 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'wenxue';
|
||||||
|
src: url('./assets/wenxue.eot');
|
||||||
|
src: url('./assets/wenxue.eot'),
|
||||||
|
url('./assets/wenxue.woff'),
|
||||||
|
url('./assets/wenxue.ttf'),
|
||||||
|
url('./assets/wenxue.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.font-wenxue {
|
||||||
|
font-family: 'wenxue';
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="canvas" class="hide"></canvas>
|
||||||
|
<div class="content">
|
||||||
|
<div class="loading">
|
||||||
|
<div class="main"><img
|
||||||
|
src="./assets/main-loading.gif">
|
||||||
|
<div class="progress">
|
||||||
|
<div class="title font-wenxue">0%</div>
|
||||||
|
<div class="bar">
|
||||||
|
<div class="sub">
|
||||||
|
<div class="percent"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text">加载中</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="landing hide">
|
||||||
|
<div class="action-1">
|
||||||
|
<img
|
||||||
|
src="./assets/main-index-title.png"
|
||||||
|
class="title swing">
|
||||||
|
</div>
|
||||||
|
<div class="action-2"><img id="start"
|
||||||
|
src="./assets/main-index-start.png"
|
||||||
|
class="start"></div>
|
||||||
|
</div>
|
||||||
|
<div id="modal" class="modal hide">
|
||||||
|
<div class="mask"></div>
|
||||||
|
<div class="js-modal-content modal-content">
|
||||||
|
<div class="main">
|
||||||
|
<div class="container"><img
|
||||||
|
src="./assets/main-modal-bg.png"
|
||||||
|
class="bg">
|
||||||
|
<div class="modal-main">
|
||||||
|
<div id="over-modal" class="hide js-modal-card"><img
|
||||||
|
src="./assets/main-modal-over.png"
|
||||||
|
class="over-img">
|
||||||
|
<div id="score" class="over-score font-wenxue"></div>
|
||||||
|
<div id="over-zero" class="hide">
|
||||||
|
<div class="tip"><p>再来一次吧!</p>
|
||||||
|
<img
|
||||||
|
src="./assets/main-modal-again-b.png"
|
||||||
|
class="over-button-b js-reload"><img
|
||||||
|
src="./assets/main-modal-invite-b.png"
|
||||||
|
class="over-button-b js-invite"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="wxShare hide">
|
||||||
|
<img src="./assets/main-share-icon.png">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="./dist/main.js"></script>
|
||||||
|
<script src="./assets/zepto-1.1.6.min.js"></script>
|
||||||
|
<script>
|
||||||
|
var domReady, loadFinish, canvasReady, loadError, gameStart, game, score, successCount
|
||||||
|
// init window height and width
|
||||||
|
var gameWidth = window.innerWidth
|
||||||
|
var gameHeight = window.innerHeight
|
||||||
|
var ratio = 1.5
|
||||||
|
if (gameHeight / gameWidth < ratio) {
|
||||||
|
gameWidth = Math.ceil(gameHeight / ratio)
|
||||||
|
}
|
||||||
|
$('.content').css({ "height": gameHeight + "px", "width": gameWidth + "px" })
|
||||||
|
$('.js-modal-content').css({ "width": gameWidth + "px" })
|
||||||
|
|
||||||
|
// loading animation
|
||||||
|
function hideLoading() {
|
||||||
|
if (domReady && canvasReady) {
|
||||||
|
$('#canvas').show()
|
||||||
|
loadFinish = true
|
||||||
|
setTimeout(function () {
|
||||||
|
$('.loading').hide()
|
||||||
|
$('.landing').show()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLoading(status) {
|
||||||
|
var success = status.success
|
||||||
|
var total = status.total
|
||||||
|
var failed = status.failed
|
||||||
|
if (failed > 0 && !loadError) {
|
||||||
|
loadError = true
|
||||||
|
alert('加载失败 请刷新后重试')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var percent = parseInt((success / total) * 100);
|
||||||
|
if (percent === 100 && !canvasReady) {
|
||||||
|
canvasReady = true
|
||||||
|
hideLoading()
|
||||||
|
}
|
||||||
|
percent = percent > 98 ? 98 : percent
|
||||||
|
percent = percent + '%'
|
||||||
|
$('.loading .title').text(percent);
|
||||||
|
$('.loading .percent').css({
|
||||||
|
'width': percent
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function overShowOver() {
|
||||||
|
$('#modal').show()
|
||||||
|
$('#over-modal').show()
|
||||||
|
$('#over-zero').show()
|
||||||
|
}
|
||||||
|
|
||||||
|
// game customization options
|
||||||
|
const option = {
|
||||||
|
width: gameWidth,
|
||||||
|
height: gameHeight,
|
||||||
|
canvasId: 'canvas',
|
||||||
|
soundOn: true,
|
||||||
|
setGameScore: function (s) {
|
||||||
|
score = s
|
||||||
|
},
|
||||||
|
setGameSuccess: function (s) {
|
||||||
|
successCount = s
|
||||||
|
},
|
||||||
|
setGameFailed: function (f) {
|
||||||
|
$('#score').text(score)
|
||||||
|
if (f >= 3) overShowOver()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// game init with option
|
||||||
|
function gameReady() {
|
||||||
|
game = TowerGame(option)
|
||||||
|
|
||||||
|
game.load(function () {
|
||||||
|
|
||||||
|
game.playBgm()
|
||||||
|
game.init()
|
||||||
|
|
||||||
|
}, updateLoading)
|
||||||
|
}
|
||||||
|
|
||||||
|
var isWechat = navigator.userAgent.toLowerCase().indexOf("micromessenger") !== -1
|
||||||
|
if (isWechat) {
|
||||||
|
document.addEventListener("WeixinJSBridgeReady", gameReady, false)
|
||||||
|
} else {
|
||||||
|
gameReady()
|
||||||
|
}
|
||||||
|
|
||||||
|
function indexHide() {
|
||||||
|
$('.landing .action-1').addClass('slideTop')
|
||||||
|
$('.landing .action-2').addClass('slideBottom')
|
||||||
|
setTimeout(function () {
|
||||||
|
$('.landing').hide()
|
||||||
|
}, 950)
|
||||||
|
}
|
||||||
|
|
||||||
|
// click event
|
||||||
|
$('#start').on('click', function () {
|
||||||
|
if (gameStart) return
|
||||||
|
gameStart = true
|
||||||
|
indexHide()
|
||||||
|
setTimeout(game.start, 400)
|
||||||
|
})
|
||||||
|
|
||||||
|
$('.js-reload').on('click', function () {
|
||||||
|
window.location.href = window.location.href + '?s=' + (+new Date())
|
||||||
|
})
|
||||||
|
|
||||||
|
$('.js-invite').on('click', function () {
|
||||||
|
$('.wxShare').show()
|
||||||
|
})
|
||||||
|
|
||||||
|
$('.wxShare').on('click', function () {
|
||||||
|
$('.wxShare').hide()
|
||||||
|
})
|
||||||
|
|
||||||
|
// listener
|
||||||
|
window.addEventListener('load', function () {
|
||||||
|
domReady = true
|
||||||
|
hideLoading()
|
||||||
|
}, false);
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
(function (i, s, o, g, r, a, m) {
|
||||||
|
i['GoogleAnalyticsObject'] = r;
|
||||||
|
i[r] = i[r] || function () {
|
||||||
|
(i[r].q = i[r].q || []).push(arguments)
|
||||||
|
}, i[r].l = 1 * new Date();
|
||||||
|
a = s.createElement(o),
|
||||||
|
m = s.getElementsByTagName(o)[0];
|
||||||
|
a.async = 1;
|
||||||
|
a.src = g;
|
||||||
|
m.parentNode.insertBefore(a, m)
|
||||||
|
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
|
||||||
|
|
||||||
|
ga('create', 'UA-46444752-20', 'auto');
|
||||||
|
ga('send', 'pageview');
|
||||||
|
|
||||||
|
var _hmt = _hmt || [];
|
||||||
|
(function () {
|
||||||
|
var hm = document.createElement("script");
|
||||||
|
hm.src = "https://hm.baidu.com/hm.js?c1b044f909411ac4213045f0478e96fc";
|
||||||
|
var s = document.getElementsByTagName("script")[0];
|
||||||
|
s.parentNode.insertBefore(hm, s);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,17 @@
|
||||||
|
const express = require('express')
|
||||||
|
const path = require('path')
|
||||||
|
const opn = require('opn')
|
||||||
|
|
||||||
|
const server = express()
|
||||||
|
const host = 'http://localhost:8082'
|
||||||
|
server.use('/assets', express.static(path.resolve(__dirname, './assets')))
|
||||||
|
server.use('/dist', express.static(path.resolve(__dirname, './dist')))
|
||||||
|
|
||||||
|
server.get('*', (req, res) => {
|
||||||
|
res.sendFile(path.resolve(__dirname, './index.html'));
|
||||||
|
})
|
||||||
|
|
||||||
|
server.listen(8082, () => {
|
||||||
|
console.log(`server started at ${host}`)
|
||||||
|
opn(host)
|
||||||
|
})
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"name": "tower_game",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "npm run build && node index.js",
|
||||||
|
"build": "webpack --mode production --module-bind js=babel-loader"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/bmqb/tower_game.git"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/bmqb/tower_game/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/bmqb/tower_game#readme",
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.0.0-beta.42",
|
||||||
|
"@babel/preset-env": "^7.0.0-beta.42",
|
||||||
|
"babel-loader": "^8.0.0-beta",
|
||||||
|
"webpack": "^4.0.1",
|
||||||
|
"webpack-cli": "^2.0.9"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cooljs": "^1.0.2",
|
||||||
|
"express": "^4.16.3",
|
||||||
|
"opn": "^5.3.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
import { Instance } from 'cooljs'
|
||||||
|
import { blockAction, blockPainter } from './block'
|
||||||
|
import {
|
||||||
|
checkMoveDown,
|
||||||
|
getMoveDownValue,
|
||||||
|
drawYellowString,
|
||||||
|
getAngleBase
|
||||||
|
} from './utils'
|
||||||
|
import { addFlight } from './flight'
|
||||||
|
import * as constant from './constant'
|
||||||
|
|
||||||
|
export const endAnimate = (engine) => {
|
||||||
|
const gameStartNow = engine.getVariable(constant.gameStartNow)
|
||||||
|
if (!gameStartNow) return
|
||||||
|
const successCount = engine.getVariable(constant.successCount, 0)
|
||||||
|
const failedCount = engine.getVariable(constant.failedCount)
|
||||||
|
const gameScore = engine.getVariable(constant.gameScore, 0)
|
||||||
|
const threeFiguresOffset = Number(successCount) > 99 ? engine.width * 0.1 : 0
|
||||||
|
|
||||||
|
drawYellowString(engine, {
|
||||||
|
string: '层',
|
||||||
|
size: engine.width * 0.06,
|
||||||
|
x: (engine.width * 0.24) + threeFiguresOffset,
|
||||||
|
y: engine.width * 0.12,
|
||||||
|
textAlign: 'left'
|
||||||
|
})
|
||||||
|
drawYellowString(engine, {
|
||||||
|
string: successCount,
|
||||||
|
size: engine.width * 0.17,
|
||||||
|
x: (engine.width * 0.22) + threeFiguresOffset,
|
||||||
|
y: engine.width * 0.2,
|
||||||
|
textAlign: 'right'
|
||||||
|
})
|
||||||
|
const score = engine.getImg('score')
|
||||||
|
const scoreWidth = score.width
|
||||||
|
const scoreHeight = score.height
|
||||||
|
const zoomedWidth = engine.width * 0.35
|
||||||
|
const zoomedHeight = (scoreHeight * zoomedWidth) / scoreWidth
|
||||||
|
engine.ctx.drawImage(
|
||||||
|
score,
|
||||||
|
engine.width * 0.61,
|
||||||
|
engine.width * 0.038,
|
||||||
|
zoomedWidth,
|
||||||
|
zoomedHeight
|
||||||
|
)
|
||||||
|
drawYellowString(engine, {
|
||||||
|
string: gameScore,
|
||||||
|
size: engine.width * 0.06,
|
||||||
|
x: engine.width * 0.9,
|
||||||
|
y: engine.width * 0.11,
|
||||||
|
textAlign: 'right'
|
||||||
|
})
|
||||||
|
const { ctx } = engine
|
||||||
|
const heart = engine.getImg('heart')
|
||||||
|
const heartWidth = heart.width
|
||||||
|
const heartHeight = heart.height
|
||||||
|
const zoomedHeartWidth = engine.width * 0.08
|
||||||
|
const zoomedHeartHeight = (heartHeight * zoomedHeartWidth) / heartWidth
|
||||||
|
for (let i = 1; i <= 3; i += 1) {
|
||||||
|
ctx.save()
|
||||||
|
if (i <= failedCount) {
|
||||||
|
ctx.globalAlpha = 0.2
|
||||||
|
}
|
||||||
|
ctx.drawImage(
|
||||||
|
heart,
|
||||||
|
(engine.width * 0.66) + ((i - 1) * zoomedHeartWidth),
|
||||||
|
engine.width * 0.16,
|
||||||
|
zoomedHeartWidth,
|
||||||
|
zoomedHeartHeight
|
||||||
|
)
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const startAnimate = (engine) => {
|
||||||
|
const gameStartNow = engine.getVariable(constant.gameStartNow)
|
||||||
|
if (!gameStartNow) return
|
||||||
|
const lastBlock = engine.getInstance(`block_${engine.getVariable(constant.blockCount)}`)
|
||||||
|
if (!lastBlock || [constant.land, constant.out].indexOf(lastBlock.status) > -1) {
|
||||||
|
if (checkMoveDown(engine) && getMoveDownValue(engine)) return
|
||||||
|
if (engine.checkTimeMovement(constant.hookUpMovement)) return
|
||||||
|
const angleBase = getAngleBase(engine)
|
||||||
|
const initialAngle = (Math.PI
|
||||||
|
* engine.utils.random(angleBase, angleBase + 5)
|
||||||
|
* engine.utils.randomPositiveNegative()
|
||||||
|
) / 180
|
||||||
|
engine.setVariable(constant.blockCount, engine.getVariable(constant.blockCount) + 1)
|
||||||
|
engine.setVariable(constant.initialAngle, initialAngle)
|
||||||
|
engine.setTimeMovement(constant.hookDownMovement, 500)
|
||||||
|
const block = new Instance({
|
||||||
|
name: `block_${engine.getVariable(constant.blockCount)}`,
|
||||||
|
action: blockAction,
|
||||||
|
painter: blockPainter
|
||||||
|
})
|
||||||
|
engine.addInstance(block)
|
||||||
|
}
|
||||||
|
const successCount = Number(engine.getVariable(constant.successCount, 0))
|
||||||
|
switch (successCount) {
|
||||||
|
case 2:
|
||||||
|
addFlight(engine, 1, 'leftToRight')
|
||||||
|
break
|
||||||
|
case 6:
|
||||||
|
addFlight(engine, 2, 'rightToLeft')
|
||||||
|
break
|
||||||
|
case 8:
|
||||||
|
addFlight(engine, 3, 'leftToRight')
|
||||||
|
break
|
||||||
|
case 14:
|
||||||
|
addFlight(engine, 4, 'bottomToTop')
|
||||||
|
break
|
||||||
|
case 18:
|
||||||
|
addFlight(engine, 5, 'bottomToTop')
|
||||||
|
break
|
||||||
|
case 22:
|
||||||
|
addFlight(engine, 6, 'bottomToTop')
|
||||||
|
break
|
||||||
|
case 25:
|
||||||
|
addFlight(engine, 7, 'rightTopToLeft')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
import { checkMoveDown, getMoveDownValue } from './utils'
|
||||||
|
import * as constant from './constant'
|
||||||
|
|
||||||
|
export const backgroundImg = (engine) => {
|
||||||
|
const bg = engine.getImg('background')
|
||||||
|
const bgWidth = bg.width
|
||||||
|
const bgHeight = bg.height
|
||||||
|
const zoomedHeight = (bgHeight * engine.width) / bgWidth
|
||||||
|
let offsetHeight = engine.getVariable(constant.bgImgOffset, engine.height - zoomedHeight)
|
||||||
|
if (offsetHeight > engine.height) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
engine.getTimeMovement(
|
||||||
|
constant.moveDownMovement,
|
||||||
|
[[offsetHeight, offsetHeight + (getMoveDownValue(engine, { pixelsPerFrame: s => s / 2 }))]],
|
||||||
|
(value) => {
|
||||||
|
offsetHeight = value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'background'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
engine.getTimeMovement(
|
||||||
|
constant.bgInitMovement,
|
||||||
|
[[offsetHeight, offsetHeight + (zoomedHeight / 4)]],
|
||||||
|
(value) => {
|
||||||
|
offsetHeight = value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
engine.setVariable(constant.bgImgOffset, offsetHeight)
|
||||||
|
engine.setVariable(constant.lineInitialOffset, engine.height - (zoomedHeight * 0.394))
|
||||||
|
engine.ctx.drawImage(
|
||||||
|
bg,
|
||||||
|
0, offsetHeight,
|
||||||
|
engine.width, zoomedHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getLinearGradientColorRgb = (colorArr, colorIndex, proportion) => {
|
||||||
|
const currentIndex = colorIndex + 1 >= colorArr.length ? colorArr.length - 1 : colorIndex
|
||||||
|
const colorCurrent = colorArr[currentIndex]
|
||||||
|
const nextIndex = currentIndex + 1 >= colorArr.length - 1 ? currentIndex : currentIndex + 1
|
||||||
|
const colorNext = colorArr[nextIndex]
|
||||||
|
const calRgbValue = (index) => {
|
||||||
|
const current = colorCurrent[index]
|
||||||
|
const next = colorNext[index]
|
||||||
|
return Math.round(current + ((next - current) * proportion))
|
||||||
|
}
|
||||||
|
return `rgb(${calRgbValue(0)}, ${calRgbValue(1)}, ${calRgbValue(2)})`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const backgroundLinearGradient = (engine) => {
|
||||||
|
const grad = engine.ctx.createLinearGradient(0, 0, 0, engine.height)
|
||||||
|
const colorArr = [
|
||||||
|
[200, 255, 150],
|
||||||
|
[105, 230, 240],
|
||||||
|
[90, 190, 240],
|
||||||
|
[85, 100, 190],
|
||||||
|
[55, 20, 35],
|
||||||
|
[75, 25, 35],
|
||||||
|
[25, 0, 10]
|
||||||
|
]
|
||||||
|
const offsetHeight = engine.getVariable(constant.bgLinearGradientOffset, 0)
|
||||||
|
if (checkMoveDown(engine)) {
|
||||||
|
engine.setVariable(
|
||||||
|
constant.bgLinearGradientOffset
|
||||||
|
, offsetHeight + (getMoveDownValue(engine) * 1.5)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const colorIndex = parseInt(offsetHeight / engine.height, 10)
|
||||||
|
const calOffsetHeight = offsetHeight % engine.height
|
||||||
|
const proportion = calOffsetHeight / engine.height
|
||||||
|
const colorBase = getLinearGradientColorRgb(colorArr, colorIndex, proportion)
|
||||||
|
const colorTop = getLinearGradientColorRgb(colorArr, colorIndex + 1, proportion)
|
||||||
|
grad.addColorStop(0, colorTop)
|
||||||
|
grad.addColorStop(1, colorBase)
|
||||||
|
engine.ctx.fillStyle = grad
|
||||||
|
engine.ctx.beginPath()
|
||||||
|
engine.ctx.rect(0, 0, engine.width, engine.height)
|
||||||
|
engine.ctx.fill()
|
||||||
|
|
||||||
|
// lightning
|
||||||
|
const lightning = () => {
|
||||||
|
engine.ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'
|
||||||
|
engine.ctx.fillRect(0, 0, engine.width, engine.height)
|
||||||
|
}
|
||||||
|
engine.getTimeMovement(
|
||||||
|
constant.lightningMovement, [], () => {},
|
||||||
|
{
|
||||||
|
before: lightning,
|
||||||
|
after: lightning
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const background = (engine) => {
|
||||||
|
backgroundLinearGradient(engine)
|
||||||
|
backgroundImg(engine)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,250 @@
|
||||||
|
import {
|
||||||
|
getMoveDownValue,
|
||||||
|
getLandBlockVelocity,
|
||||||
|
getSwingBlockVelocity,
|
||||||
|
touchEventHandler,
|
||||||
|
addSuccessCount,
|
||||||
|
addFailedCount,
|
||||||
|
addScore
|
||||||
|
} from './utils'
|
||||||
|
import * as constant from './constant'
|
||||||
|
|
||||||
|
const checkCollision = (block, line) => {
|
||||||
|
// 0 goon 1 drop 2 rotate left 3 rotate right 4 ok 5 perfect
|
||||||
|
if (block.y + block.height >= line.y) {
|
||||||
|
if (block.x < line.x - block.calWidth || block.x > line.collisionX + block.calWidth) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if (block.x < line.x) {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
if (block.x > line.collisionX) {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
if (block.x > line.x + (block.calWidth * 0.8) && block.x < line.x + (block.calWidth * 1.2)) {
|
||||||
|
// -10% +10%
|
||||||
|
return 5
|
||||||
|
}
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
const swing = (instance, engine, time) => {
|
||||||
|
const ropeHeight = engine.getVariable(constant.ropeHeight)
|
||||||
|
if (instance.status !== constant.swing) return
|
||||||
|
const i = instance
|
||||||
|
const initialAngle = engine.getVariable(constant.initialAngle)
|
||||||
|
i.angle = initialAngle *
|
||||||
|
getSwingBlockVelocity(engine, time)
|
||||||
|
i.weightX = i.x +
|
||||||
|
(Math.sin(i.angle) * ropeHeight)
|
||||||
|
i.weightY = i.y +
|
||||||
|
(Math.cos(i.angle) * ropeHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkBlockOut = (instance, engine) => {
|
||||||
|
if (instance.status === constant.rotateLeft) {
|
||||||
|
// 左转 要等右上角消失才算消失
|
||||||
|
if (instance.y - instance.width >= engine.height) {
|
||||||
|
instance.visible = false
|
||||||
|
instance.status = constant.out
|
||||||
|
addFailedCount(engine)
|
||||||
|
}
|
||||||
|
} else if (instance.y >= engine.height) {
|
||||||
|
instance.visible = false
|
||||||
|
instance.status = constant.out
|
||||||
|
addFailedCount(engine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const blockAction = (instance, engine, time) => {
|
||||||
|
const i = instance
|
||||||
|
const ropeHeight = engine.getVariable(constant.ropeHeight)
|
||||||
|
if (!i.visible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!i.ready) {
|
||||||
|
i.ready = true
|
||||||
|
i.status = constant.swing
|
||||||
|
instance.updateWidth(engine.getVariable(constant.blockWidth))
|
||||||
|
instance.updateHeight(engine.getVariable(constant.blockHeight))
|
||||||
|
instance.x = engine.width / 2
|
||||||
|
instance.y = ropeHeight * -1.5
|
||||||
|
}
|
||||||
|
const line = engine.getInstance('line')
|
||||||
|
switch (i.status) {
|
||||||
|
case constant.swing:
|
||||||
|
engine.getTimeMovement(
|
||||||
|
constant.hookDownMovement,
|
||||||
|
[[instance.y, instance.y + ropeHeight]],
|
||||||
|
(value) => {
|
||||||
|
instance.y = value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'block'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
swing(instance, engine, time)
|
||||||
|
break
|
||||||
|
case constant.beforeDrop:
|
||||||
|
i.x = instance.weightX - instance.calWidth
|
||||||
|
i.y = instance.weightY + (0.3 * instance.height) // add rope height
|
||||||
|
i.rotate = 0
|
||||||
|
i.ay = engine.pixelsPerFrame(0.0003 * engine.height) // acceleration of gravity
|
||||||
|
i.startDropTime = time
|
||||||
|
i.status = constant.drop
|
||||||
|
break
|
||||||
|
case constant.drop:
|
||||||
|
const deltaTime = time - i.startDropTime
|
||||||
|
i.startDropTime = time
|
||||||
|
i.vy += i.ay * deltaTime
|
||||||
|
i.y += (i.vy * deltaTime) + (0.5 * i.ay * (deltaTime ** 2))
|
||||||
|
const collision = checkCollision(instance, line)
|
||||||
|
const blockY = line.y - instance.height
|
||||||
|
const calRotate = (ins) => {
|
||||||
|
ins.originOutwardAngle = Math.atan(ins.height / ins.outwardOffset)
|
||||||
|
ins.originHypotenuse = Math.sqrt((ins.height ** 2)
|
||||||
|
+ (ins.outwardOffset ** 2))
|
||||||
|
engine.playAudio('rotate')
|
||||||
|
}
|
||||||
|
switch (collision) {
|
||||||
|
case 1:
|
||||||
|
checkBlockOut(instance, engine)
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
i.status = constant.rotateLeft
|
||||||
|
instance.y = blockY
|
||||||
|
instance.outwardOffset = (line.x + instance.calWidth) - instance.x
|
||||||
|
calRotate(instance)
|
||||||
|
break
|
||||||
|
case 3:
|
||||||
|
i.status = constant.rotateRight
|
||||||
|
instance.y = blockY
|
||||||
|
instance.outwardOffset = (line.collisionX + instance.calWidth) - instance.x
|
||||||
|
calRotate(instance)
|
||||||
|
break
|
||||||
|
case 4:
|
||||||
|
case 5:
|
||||||
|
i.status = constant.land
|
||||||
|
const lastSuccessCount = engine.getVariable(constant.successCount)
|
||||||
|
addSuccessCount(engine)
|
||||||
|
engine.setTimeMovement(constant.moveDownMovement, 500)
|
||||||
|
if (lastSuccessCount === 10 || lastSuccessCount === 15) {
|
||||||
|
engine.setTimeMovement(constant.lightningMovement, 150)
|
||||||
|
}
|
||||||
|
instance.y = blockY
|
||||||
|
line.y = blockY
|
||||||
|
line.x = i.x - i.calWidth
|
||||||
|
line.collisionX = line.x + i.width
|
||||||
|
// 作弊检测 超出左边或右边1/3
|
||||||
|
const cheatWidth = i.width * 0.3
|
||||||
|
if (i.x > engine.width - (cheatWidth * 2)
|
||||||
|
|| i.x < -cheatWidth) {
|
||||||
|
engine.setVariable(constant.hardMode, true)
|
||||||
|
}
|
||||||
|
if (collision === 5) {
|
||||||
|
instance.perfect = true
|
||||||
|
addScore(engine, true)
|
||||||
|
engine.playAudio('drop-perfect')
|
||||||
|
} else {
|
||||||
|
addScore(engine)
|
||||||
|
engine.playAudio('drop')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case constant.land:
|
||||||
|
engine.getTimeMovement(
|
||||||
|
constant.moveDownMovement,
|
||||||
|
[[instance.y, instance.y + (getMoveDownValue(engine, { pixelsPerFrame: s => s / 2 }))]],
|
||||||
|
(value) => {
|
||||||
|
if (!instance.visible) return
|
||||||
|
instance.y = value
|
||||||
|
if (instance.y > engine.height) {
|
||||||
|
instance.visible = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: instance.name
|
||||||
|
}
|
||||||
|
)
|
||||||
|
instance.x += getLandBlockVelocity(engine, time)
|
||||||
|
break
|
||||||
|
case constant.rotateLeft:
|
||||||
|
case constant.rotateRight:
|
||||||
|
const isRight = i.status === constant.rotateRight
|
||||||
|
const rotateSpeed = engine.pixelsPerFrame(Math.PI * 4)
|
||||||
|
const isShouldFall = isRight ? instance.rotate > 1.3 : instance.rotate < -1.3// 75度
|
||||||
|
const leftFix = isRight ? 1 : -1
|
||||||
|
if (isShouldFall) {
|
||||||
|
instance.rotate += (rotateSpeed / 8) * leftFix
|
||||||
|
instance.y += engine.pixelsPerFrame(engine.height * 0.7)
|
||||||
|
instance.x += engine.pixelsPerFrame(engine.width * 0.3) * leftFix
|
||||||
|
} else {
|
||||||
|
let rotateRatio = (instance.calWidth - instance.outwardOffset)
|
||||||
|
/ instance.calWidth
|
||||||
|
rotateRatio = rotateRatio > 0.5 ? rotateRatio : 0.5
|
||||||
|
instance.rotate += rotateSpeed * rotateRatio * leftFix
|
||||||
|
const angle = instance.originOutwardAngle + instance.rotate
|
||||||
|
const rotateAxisX = isRight ? line.collisionX + instance.calWidth
|
||||||
|
: line.x + instance.calWidth
|
||||||
|
const rotateAxisY = line.y
|
||||||
|
instance.x = rotateAxisX -
|
||||||
|
(Math.cos(angle) * instance.originHypotenuse)
|
||||||
|
instance.y = rotateAxisY -
|
||||||
|
(Math.sin(angle) * instance.originHypotenuse)
|
||||||
|
}
|
||||||
|
checkBlockOut(instance, engine)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawSwingBlock = (instance, engine) => {
|
||||||
|
const bl = engine.getImg('blockRope')
|
||||||
|
engine.ctx.drawImage(
|
||||||
|
bl, instance.weightX - instance.calWidth
|
||||||
|
, instance.weightY
|
||||||
|
, instance.width, instance.height * 1.3
|
||||||
|
)
|
||||||
|
const leftX = instance.weightX - instance.calWidth
|
||||||
|
engine.debugLineY(leftX)
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawBlock = (instance, engine) => {
|
||||||
|
const { perfect } = instance
|
||||||
|
const bl = engine.getImg(perfect ? 'block-perfect' : 'block')
|
||||||
|
engine.ctx.drawImage(bl, instance.x, instance.y, instance.width, instance.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
const drawRotatedBlock = (instance, engine) => {
|
||||||
|
const { ctx } = engine
|
||||||
|
ctx.save()
|
||||||
|
ctx.translate(instance.x, instance.y)
|
||||||
|
ctx.rotate(instance.rotate)
|
||||||
|
ctx.translate(-instance.x, -instance.y)
|
||||||
|
drawBlock(instance, engine)
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const blockPainter = (instance, engine) => {
|
||||||
|
const { status } = instance
|
||||||
|
switch (status) {
|
||||||
|
case constant.swing:
|
||||||
|
drawSwingBlock(instance, engine)
|
||||||
|
break
|
||||||
|
case constant.drop:
|
||||||
|
case constant.land:
|
||||||
|
drawBlock(instance, engine)
|
||||||
|
break
|
||||||
|
case constant.rotateLeft:
|
||||||
|
case constant.rotateRight:
|
||||||
|
drawRotatedBlock(instance, engine)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
import { checkMoveDown, getMoveDownValue } from './utils'
|
||||||
|
import * as constant from './constant'
|
||||||
|
|
||||||
|
const randomCloudImg = (instance) => {
|
||||||
|
const { count } = instance
|
||||||
|
const clouds = ['c1', 'c2', 'c3']
|
||||||
|
const stones = ['c4', 'c5', 'c6', 'c7', 'c8']
|
||||||
|
const randomImg = array => (array[Math.floor(Math.random() * array.length)])
|
||||||
|
instance.imgName = count > 6 ? randomImg(stones) : randomImg(clouds)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cloudAction = (instance, engine) => {
|
||||||
|
if (!instance.ready) {
|
||||||
|
instance.ready = true
|
||||||
|
randomCloudImg(instance)
|
||||||
|
instance.width = engine.getVariable(constant.cloudSize)
|
||||||
|
instance.height = engine.getVariable(constant.cloudSize)
|
||||||
|
const engineW = engine.width
|
||||||
|
const engineH = engine.height
|
||||||
|
const positionArr = [
|
||||||
|
{ x: engineW * 0.1, y: -engineH * 0.66 },
|
||||||
|
{ x: engineW * 0.65, y: -engineH * 0.33 },
|
||||||
|
{ x: engineW * 0.1, y: 0 },
|
||||||
|
{ x: engineW * 0.65, y: engineH * 0.33 }
|
||||||
|
]
|
||||||
|
const position = positionArr[instance.index - 1]
|
||||||
|
instance.x = engine.utils.random(position.x, (position.x * 1.2))
|
||||||
|
instance.originX = instance.x
|
||||||
|
instance.ax = engine.pixelsPerFrame(instance.width * engine.utils.random(0.05, 0.08)
|
||||||
|
* engine.utils.randomPositiveNegative())
|
||||||
|
instance.y = engine.utils.random(position.y, (position.y * 1.2))
|
||||||
|
}
|
||||||
|
instance.x += instance.ax
|
||||||
|
if (instance.x >= instance.originX + instance.width
|
||||||
|
|| instance.x <= instance.originX - instance.width) {
|
||||||
|
instance.ax *= -1
|
||||||
|
}
|
||||||
|
if (checkMoveDown(engine)) {
|
||||||
|
instance.y += getMoveDownValue(engine) * 1.2
|
||||||
|
}
|
||||||
|
if (instance.y >= engine.height) {
|
||||||
|
instance.y = -engine.height * 0.66
|
||||||
|
instance.count += 4
|
||||||
|
randomCloudImg(instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cloudPainter = (instance, engine) => {
|
||||||
|
const { ctx } = engine
|
||||||
|
const cloud = engine.getImg(instance.imgName)
|
||||||
|
ctx.drawImage(cloud, instance.x, instance.y, instance.width, instance.height)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
export const gameStartNow = 'GAME_START_NOW'
|
||||||
|
export const gameUserOption = 'GAME_USER_OPTION'
|
||||||
|
export const hardMode = 'HARD_MODE'
|
||||||
|
|
||||||
|
export const successCount = 'SUCCESS_COUNT'
|
||||||
|
export const failedCount = 'FAILED_COUNT'
|
||||||
|
export const perfectCount = 'PERFECT_COUNT'
|
||||||
|
export const gameScore = 'GAME_SCORE'
|
||||||
|
|
||||||
|
export const hookDown = 'HOOK_DOWN'
|
||||||
|
export const hookUp = 'HOOK_UP'
|
||||||
|
export const hookNormal = 'HOOK_NORMAL'
|
||||||
|
|
||||||
|
export const bgImgOffset = 'BACKGROUND_IMG_OFFSET_HEIGHT'
|
||||||
|
export const lineInitialOffset = 'LINE_INITIAL_OFFSET'
|
||||||
|
export const bgLinearGradientOffset = 'BACKGROUND_LINEAR_GRADIENT_OFFSET_HEIGHT'
|
||||||
|
|
||||||
|
|
||||||
|
export const blockCount = 'BLOCK_COUNT'
|
||||||
|
export const blockWidth = 'BLOCK_WIDTH'
|
||||||
|
export const blockHeight = 'BLOCK_HEIGHT'
|
||||||
|
export const cloudSize = 'CLOUD_SIZE'
|
||||||
|
export const ropeHeight = 'ROPE_HEIGHT'
|
||||||
|
export const flightCount = 'FLIGHT_COUNT'
|
||||||
|
export const flightLayer = 'FLIGHT_LAYER'
|
||||||
|
|
||||||
|
export const rotateRight = 'ROTATE_RIGHT'
|
||||||
|
export const rotateLeft = 'ROTATE_LEFT'
|
||||||
|
export const swing = 'SWING'
|
||||||
|
export const beforeDrop = 'BEFORE_DROP'
|
||||||
|
export const drop = 'DROP'
|
||||||
|
export const land = 'LAND'
|
||||||
|
export const out = 'OUT'
|
||||||
|
|
||||||
|
export const initialAngle = 'INITIAL_ANGLE'
|
||||||
|
|
||||||
|
export const bgInitMovement = 'BG_INIT_MOVEMENT'
|
||||||
|
export const hookDownMovement = 'HOOK_DOWN_MOVEMENT'
|
||||||
|
export const hookUpMovement = 'HOOK_UP_MOVEMENT'
|
||||||
|
export const lightningMovement = 'LIGHTNING_MOVEMENT'
|
||||||
|
export const tutorialMovement = 'TUTORIAL_MOVEMENT'
|
||||||
|
export const moveDownMovement = 'MOVE_DOWN_MOVEMENT'
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { Instance } from 'cooljs'
|
||||||
|
import * as constant from './constant'
|
||||||
|
|
||||||
|
const getActionConfig = (engine, type) => {
|
||||||
|
const {
|
||||||
|
width, height, utils
|
||||||
|
} = engine
|
||||||
|
const { random } = utils
|
||||||
|
const size = engine.getVariable(constant.cloudSize)
|
||||||
|
const actionTypes = {
|
||||||
|
bottomToTop: {
|
||||||
|
x: width * random(0.3, 0.7),
|
||||||
|
y: height,
|
||||||
|
vx: 0,
|
||||||
|
vy: engine.pixelsPerFrame(height) * 0.7 * -1
|
||||||
|
},
|
||||||
|
leftToRight: {
|
||||||
|
x: size * -1,
|
||||||
|
y: height * random(0.3, 0.6),
|
||||||
|
vx: engine.pixelsPerFrame(width) * 0.4,
|
||||||
|
vy: engine.pixelsPerFrame(height) * 0.1 * -1
|
||||||
|
},
|
||||||
|
rightToLeft: {
|
||||||
|
x: width,
|
||||||
|
y: height * random(0.2, 0.5),
|
||||||
|
vx: engine.pixelsPerFrame(width) * 0.4 * -1,
|
||||||
|
vy: engine.pixelsPerFrame(height) * 0.1
|
||||||
|
},
|
||||||
|
rightTopToLeft: {
|
||||||
|
x: width,
|
||||||
|
y: 0,
|
||||||
|
vx: engine.pixelsPerFrame(width) * 0.6 * -1,
|
||||||
|
vy: engine.pixelsPerFrame(height) * 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actionTypes[type]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const flightAction = (instance, engine) => {
|
||||||
|
const { visible, ready, type } = instance
|
||||||
|
if (!visible) return
|
||||||
|
const size = engine.getVariable(constant.cloudSize)
|
||||||
|
if (!ready) {
|
||||||
|
const action = getActionConfig(engine, type)
|
||||||
|
instance.ready = true
|
||||||
|
instance.width = size
|
||||||
|
instance.height = size
|
||||||
|
instance.x = action.x
|
||||||
|
instance.y = action.y
|
||||||
|
instance.vx = action.vx
|
||||||
|
instance.vy = action.vy
|
||||||
|
}
|
||||||
|
instance.x += instance.vx
|
||||||
|
instance.y += instance.vy
|
||||||
|
if (instance.y + size < 0
|
||||||
|
|| instance.y > engine.height
|
||||||
|
|| instance.x + size < 0
|
||||||
|
|| instance.x > engine.width) {
|
||||||
|
instance.visible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const flightPainter = (instance, engine) => {
|
||||||
|
const { ctx } = engine
|
||||||
|
const flight = engine.getImg(instance.imgName)
|
||||||
|
ctx.drawImage(flight, instance.x, instance.y, instance.width, instance.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addFlight = (engine, number, type) => {
|
||||||
|
const flightCount = engine.getVariable(constant.flightCount)
|
||||||
|
if (flightCount === number) return
|
||||||
|
const flight = new Instance({
|
||||||
|
name: `flight_${number}`,
|
||||||
|
action: flightAction,
|
||||||
|
painter: flightPainter
|
||||||
|
})
|
||||||
|
flight.imgName = `f${number}`
|
||||||
|
flight.type = type
|
||||||
|
engine.addInstance(flight, constant.flightLayer)
|
||||||
|
engine.setVariable(constant.flightCount, number)
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { getSwingBlockVelocity } from './utils'
|
||||||
|
import * as constant from './constant'
|
||||||
|
|
||||||
|
export const hookAction = (instance, engine, time) => {
|
||||||
|
const ropeHeight = engine.getVariable(constant.ropeHeight)
|
||||||
|
if (!instance.ready) {
|
||||||
|
instance.x = engine.width / 2
|
||||||
|
instance.y = ropeHeight * -1.5
|
||||||
|
instance.ready = true
|
||||||
|
}
|
||||||
|
engine.getTimeMovement(
|
||||||
|
constant.hookUpMovement,
|
||||||
|
[[instance.y, instance.y - ropeHeight]],
|
||||||
|
(value) => {
|
||||||
|
instance.y = value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
after: () => {
|
||||||
|
instance.y = ropeHeight * -1.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
engine.getTimeMovement(
|
||||||
|
constant.hookDownMovement,
|
||||||
|
[[instance.y, instance.y + ropeHeight]],
|
||||||
|
(value) => {
|
||||||
|
instance.y = value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'hook'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const initialAngle = engine.getVariable(constant.initialAngle)
|
||||||
|
instance.angle = initialAngle *
|
||||||
|
getSwingBlockVelocity(engine, time)
|
||||||
|
instance.weightX = instance.x +
|
||||||
|
(Math.sin(instance.angle) * ropeHeight)
|
||||||
|
instance.weightY = instance.y +
|
||||||
|
(Math.cos(instance.angle) * ropeHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hookPainter = (instance, engine) => {
|
||||||
|
const { ctx } = engine
|
||||||
|
const ropeHeight = engine.getVariable(constant.ropeHeight)
|
||||||
|
const ropeWidth = ropeHeight * 0.1
|
||||||
|
const hook = engine.getImg('hook')
|
||||||
|
ctx.save()
|
||||||
|
ctx.translate(instance.x, instance.y)
|
||||||
|
ctx.rotate((Math.PI * 2) - instance.angle)
|
||||||
|
ctx.translate(-instance.x, -instance.y)
|
||||||
|
engine.ctx.drawImage(hook, instance.x - (ropeWidth / 2), instance.y, ropeWidth, ropeHeight + 5)
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
import { Engine, Instance } from 'cooljs'
|
||||||
|
import { touchEventHandler } from './utils'
|
||||||
|
import { background } from './background'
|
||||||
|
import { lineAction, linePainter } from './line'
|
||||||
|
import { cloudAction, cloudPainter } from './cloud'
|
||||||
|
import { hookAction, hookPainter } from './hook'
|
||||||
|
import { tutorialAction, tutorialPainter } from './tutorial'
|
||||||
|
import * as constant from './constant'
|
||||||
|
import { startAnimate, endAnimate } from './animateFuncs'
|
||||||
|
|
||||||
|
window.TowerGame = (option = {}) => {
|
||||||
|
const {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
canvasId,
|
||||||
|
soundOn
|
||||||
|
} = option
|
||||||
|
const game = new Engine({
|
||||||
|
canvasId,
|
||||||
|
highResolution: true,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
soundOn
|
||||||
|
})
|
||||||
|
const pathGenerator = (path) => `./assets/${path}`
|
||||||
|
|
||||||
|
game.addImg('background', pathGenerator('background.png'))
|
||||||
|
game.addImg('hook', pathGenerator('hook.png'))
|
||||||
|
game.addImg('blockRope', pathGenerator('block-rope.png'))
|
||||||
|
game.addImg('block', pathGenerator('block.png'))
|
||||||
|
game.addImg('block-perfect', pathGenerator('block-perfect.png'))
|
||||||
|
for (let i = 1; i <= 8; i += 1) {
|
||||||
|
game.addImg(`c${i}`, pathGenerator(`c${i}.png`))
|
||||||
|
}
|
||||||
|
game.addLayer(constant.flightLayer)
|
||||||
|
for (let i = 1; i <= 7; i += 1) {
|
||||||
|
game.addImg(`f${i}`, pathGenerator(`f${i}.png`))
|
||||||
|
}
|
||||||
|
game.swapLayer(0, 1)
|
||||||
|
game.addImg('tutorial', pathGenerator('tutorial.png'))
|
||||||
|
game.addImg('tutorial-arrow', pathGenerator('tutorial-arrow.png'))
|
||||||
|
game.addImg('heart', pathGenerator('heart.png'))
|
||||||
|
game.addImg('score', pathGenerator('score.png'))
|
||||||
|
game.addAudio('drop-perfect', pathGenerator('drop-perfect.mp3'))
|
||||||
|
game.addAudio('drop', pathGenerator('drop.mp3'))
|
||||||
|
game.addAudio('game-over', pathGenerator('game-over.mp3'))
|
||||||
|
game.addAudio('rotate', pathGenerator('rotate.mp3'))
|
||||||
|
game.addAudio('bgm', pathGenerator('bgm.mp3'))
|
||||||
|
game.setVariable(constant.blockWidth, game.width * 0.25)
|
||||||
|
game.setVariable(constant.blockHeight, game.getVariable(constant.blockWidth) * 0.71)
|
||||||
|
game.setVariable(constant.cloudSize, game.width * 0.3)
|
||||||
|
game.setVariable(constant.ropeHeight, game.height * 0.4)
|
||||||
|
game.setVariable(constant.blockCount, 0)
|
||||||
|
game.setVariable(constant.successCount, 0)
|
||||||
|
game.setVariable(constant.failedCount, 0)
|
||||||
|
game.setVariable(constant.gameScore, 0)
|
||||||
|
game.setVariable(constant.hardMode, false)
|
||||||
|
game.setVariable(constant.gameUserOption, option)
|
||||||
|
for (let i = 1; i <= 4; i += 1) {
|
||||||
|
const cloud = new Instance({
|
||||||
|
name: `cloud_${i}`,
|
||||||
|
action: cloudAction,
|
||||||
|
painter: cloudPainter
|
||||||
|
})
|
||||||
|
cloud.index = i
|
||||||
|
cloud.count = 5 - i
|
||||||
|
game.addInstance(cloud)
|
||||||
|
}
|
||||||
|
const line = new Instance({
|
||||||
|
name: 'line',
|
||||||
|
action: lineAction,
|
||||||
|
painter: linePainter
|
||||||
|
})
|
||||||
|
game.addInstance(line)
|
||||||
|
const hook = new Instance({
|
||||||
|
name: 'hook',
|
||||||
|
action: hookAction,
|
||||||
|
painter: hookPainter
|
||||||
|
})
|
||||||
|
game.addInstance(hook)
|
||||||
|
|
||||||
|
game.startAnimate = startAnimate
|
||||||
|
game.endAnimate = endAnimate
|
||||||
|
game.paintUnderInstance = background
|
||||||
|
game.addKeyDownListener('enter', () => {
|
||||||
|
if (game.debug) game.togglePaused()
|
||||||
|
})
|
||||||
|
game.touchStartListener = () => {
|
||||||
|
touchEventHandler(game)
|
||||||
|
}
|
||||||
|
|
||||||
|
game.playBgm = () => {
|
||||||
|
game.playAudio('bgm', true)
|
||||||
|
}
|
||||||
|
|
||||||
|
game.pauseBgm = () => {
|
||||||
|
game.pauseAudio('bgm')
|
||||||
|
}
|
||||||
|
|
||||||
|
game.start = () => {
|
||||||
|
const tutorial = new Instance({
|
||||||
|
name: 'tutorial',
|
||||||
|
action: tutorialAction,
|
||||||
|
painter: tutorialPainter
|
||||||
|
})
|
||||||
|
game.addInstance(tutorial)
|
||||||
|
const tutorialArrow = new Instance({
|
||||||
|
name: 'tutorial-arrow',
|
||||||
|
action: tutorialAction,
|
||||||
|
painter: tutorialPainter
|
||||||
|
})
|
||||||
|
game.addInstance(tutorialArrow)
|
||||||
|
game.setTimeMovement(constant.bgInitMovement, 500)
|
||||||
|
game.setTimeMovement(constant.tutorialMovement, 500)
|
||||||
|
game.setVariable(constant.gameStartNow, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return game
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { getMoveDownValue, getLandBlockVelocity } from './utils'
|
||||||
|
import * as constant from './constant'
|
||||||
|
|
||||||
|
export const lineAction = (instance, engine, time) => {
|
||||||
|
const i = instance
|
||||||
|
if (!i.ready) {
|
||||||
|
i.y = engine.getVariable(constant.lineInitialOffset)
|
||||||
|
i.ready = true
|
||||||
|
i.collisionX = engine.width - engine.getVariable(constant.blockWidth)
|
||||||
|
}
|
||||||
|
engine.getTimeMovement(
|
||||||
|
constant.moveDownMovement,
|
||||||
|
[[instance.y, instance.y + (getMoveDownValue(engine, { pixelsPerFrame: s => s / 2 }))]],
|
||||||
|
(value) => {
|
||||||
|
instance.y = value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'line'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const landBlockVelocity = getLandBlockVelocity(engine, time)
|
||||||
|
instance.x += landBlockVelocity
|
||||||
|
instance.collisionX += landBlockVelocity
|
||||||
|
}
|
||||||
|
|
||||||
|
export const linePainter = (instance, engine) => {
|
||||||
|
const { ctx, debug } = engine
|
||||||
|
if (!debug) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.save()
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.strokeStyle = 'red'
|
||||||
|
ctx.moveTo(instance.x, instance.y)
|
||||||
|
ctx.lineTo(instance.collisionX, instance.y)
|
||||||
|
ctx.lineWidth = 1
|
||||||
|
ctx.stroke()
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { getHookStatus } from './utils'
|
||||||
|
import * as constant from './constant'
|
||||||
|
|
||||||
|
export const tutorialAction = (instance, engine, time) => {
|
||||||
|
const { width, height } = engine
|
||||||
|
const { name } = instance
|
||||||
|
if (!instance.ready) {
|
||||||
|
instance.ready = true
|
||||||
|
const tutorialWidth = width * 0.2
|
||||||
|
instance.updateWidth(tutorialWidth)
|
||||||
|
instance.height = tutorialWidth * 0.46
|
||||||
|
instance.x = engine.calWidth - instance.calWidth
|
||||||
|
instance.y = height * 0.45
|
||||||
|
if (name !== 'tutorial') {
|
||||||
|
instance.y += instance.height * 1.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name !== 'tutorial') {
|
||||||
|
instance.y += Math.cos(time / 200) * instance.height * 0.01
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tutorialPainter = (instance, engine) => {
|
||||||
|
if (engine.checkTimeMovement(constant.tutorialMovement)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (getHookStatus(engine) !== constant.hookNormal) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { ctx } = engine
|
||||||
|
const { name } = instance
|
||||||
|
const t = engine.getImg(name)
|
||||||
|
ctx.drawImage(t, instance.x, instance.y, instance.width, instance.height)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,177 @@
|
||||||
|
import * as constant from './constant'
|
||||||
|
|
||||||
|
export const checkMoveDown = engine =>
|
||||||
|
(engine.checkTimeMovement(constant.moveDownMovement))
|
||||||
|
|
||||||
|
export const getMoveDownValue = (engine, store) => {
|
||||||
|
const pixelsPerFrame = store ? store.pixelsPerFrame : engine.pixelsPerFrame.bind(engine)
|
||||||
|
const successCount = engine.getVariable(constant.successCount)
|
||||||
|
const calHeight = engine.getVariable(constant.blockHeight) * 2
|
||||||
|
if (successCount <= 4) {
|
||||||
|
return pixelsPerFrame(calHeight * 1.25)
|
||||||
|
}
|
||||||
|
return pixelsPerFrame(calHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAngleBase = (engine) => {
|
||||||
|
const successCount = engine.getVariable(constant.successCount)
|
||||||
|
const gameScore = engine.getVariable(constant.gameScore)
|
||||||
|
const { hookAngle } = engine.getVariable(constant.gameUserOption)
|
||||||
|
if (hookAngle) {
|
||||||
|
return hookAngle(successCount, gameScore)
|
||||||
|
}
|
||||||
|
if (engine.getVariable(constant.hardMode)) {
|
||||||
|
return 90
|
||||||
|
}
|
||||||
|
switch (true) {
|
||||||
|
case successCount < 10:
|
||||||
|
return 30
|
||||||
|
case successCount < 20:
|
||||||
|
return 60
|
||||||
|
default:
|
||||||
|
return 80
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getSwingBlockVelocity = (engine, time) => {
|
||||||
|
const successCount = engine.getVariable(constant.successCount)
|
||||||
|
const gameScore = engine.getVariable(constant.gameScore)
|
||||||
|
const { hookSpeed } = engine.getVariable(constant.gameUserOption)
|
||||||
|
if (hookSpeed) {
|
||||||
|
return hookSpeed(successCount, gameScore)
|
||||||
|
}
|
||||||
|
let hard
|
||||||
|
switch (true) {
|
||||||
|
case successCount < 1:
|
||||||
|
hard = 0
|
||||||
|
break
|
||||||
|
case successCount < 10:
|
||||||
|
hard = 1
|
||||||
|
break
|
||||||
|
case successCount < 20:
|
||||||
|
hard = 0.8
|
||||||
|
break
|
||||||
|
case successCount < 30:
|
||||||
|
hard = 0.7
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
hard = 0.74
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (engine.getVariable(constant.hardMode)) {
|
||||||
|
hard = 1.1
|
||||||
|
}
|
||||||
|
return Math.sin(time / (200 / hard))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getLandBlockVelocity = (engine, time) => {
|
||||||
|
const successCount = engine.getVariable(constant.successCount)
|
||||||
|
const gameScore = engine.getVariable(constant.gameScore)
|
||||||
|
const { landBlockSpeed } = engine.getVariable(constant.gameUserOption)
|
||||||
|
if (landBlockSpeed) {
|
||||||
|
return landBlockSpeed(successCount, gameScore)
|
||||||
|
}
|
||||||
|
const { width } = engine
|
||||||
|
let hard
|
||||||
|
switch (true) {
|
||||||
|
case successCount < 5:
|
||||||
|
hard = 0
|
||||||
|
break
|
||||||
|
case successCount < 13:
|
||||||
|
hard = 0.001
|
||||||
|
break
|
||||||
|
case successCount < 23:
|
||||||
|
hard = 0.002
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
hard = 0.003
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return Math.cos(time / 200) * hard * width
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getHookStatus = (engine) => {
|
||||||
|
if (engine.checkTimeMovement(constant.hookDownMovement)) {
|
||||||
|
return constant.hookDown
|
||||||
|
}
|
||||||
|
if (engine.checkTimeMovement(constant.hookUpMovement)) {
|
||||||
|
return constant.hookUp
|
||||||
|
}
|
||||||
|
return constant.hookNormal
|
||||||
|
}
|
||||||
|
|
||||||
|
export const touchEventHandler = (engine) => {
|
||||||
|
if (!engine.getVariable(constant.gameStartNow)) return
|
||||||
|
if (engine.debug && engine.paused) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (getHookStatus(engine) !== constant.hookNormal) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
engine.removeInstance('tutorial')
|
||||||
|
engine.removeInstance('tutorial-arrow')
|
||||||
|
const b = engine.getInstance(`block_${engine.getVariable(constant.blockCount)}`)
|
||||||
|
if (b && b.status === constant.swing) {
|
||||||
|
engine.setTimeMovement(constant.hookUpMovement, 500)
|
||||||
|
b.status = constant.beforeDrop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addSuccessCount = (engine) => {
|
||||||
|
const { setGameSuccess } = engine.getVariable(constant.gameUserOption)
|
||||||
|
const lastSuccessCount = engine.getVariable(constant.successCount)
|
||||||
|
const success = lastSuccessCount + 1
|
||||||
|
engine.setVariable(constant.successCount, success)
|
||||||
|
if (engine.getVariable(constant.hardMode)) {
|
||||||
|
engine.setVariable(constant.ropeHeight, engine.height * engine.utils.random(0.35, 0.55))
|
||||||
|
}
|
||||||
|
if (setGameSuccess) setGameSuccess(success)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addFailedCount = (engine) => {
|
||||||
|
const { setGameFailed } = engine.getVariable(constant.gameUserOption)
|
||||||
|
const lastFailedCount = engine.getVariable(constant.failedCount)
|
||||||
|
const failed = lastFailedCount + 1
|
||||||
|
engine.setVariable(constant.failedCount, failed)
|
||||||
|
engine.setVariable(constant.perfectCount, 0)
|
||||||
|
if (setGameFailed) setGameFailed(failed)
|
||||||
|
if (failed >= 3) {
|
||||||
|
engine.pauseAudio('bgm')
|
||||||
|
engine.playAudio('game-over')
|
||||||
|
engine.setVariable(constant.gameStartNow, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addScore = (engine, isPerfect) => {
|
||||||
|
const { setGameScore, successScore, perfectScore } = engine.getVariable(constant.gameUserOption)
|
||||||
|
const lastPerfectCount = engine.getVariable(constant.perfectCount, 0)
|
||||||
|
const lastGameScore = engine.getVariable(constant.gameScore)
|
||||||
|
const perfect = isPerfect ? lastPerfectCount + 1 : 0
|
||||||
|
const score = lastGameScore + (successScore || 25) + ((perfectScore || 25) * perfect)
|
||||||
|
engine.setVariable(constant.gameScore, score)
|
||||||
|
engine.setVariable(constant.perfectCount, perfect)
|
||||||
|
if (setGameScore) setGameScore(score)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const drawYellowString = (engine, option) => {
|
||||||
|
const {
|
||||||
|
string, size, x, y, textAlign
|
||||||
|
} = option
|
||||||
|
const { ctx } = engine
|
||||||
|
const fontName = 'wenxue'
|
||||||
|
const fontSize = size
|
||||||
|
const lineSize = fontSize * 0.1
|
||||||
|
ctx.save()
|
||||||
|
ctx.beginPath()
|
||||||
|
const gradient = ctx.createLinearGradient(0, 0, 0, y)
|
||||||
|
gradient.addColorStop(0, '#FAD961')
|
||||||
|
gradient.addColorStop(1, '#F76B1C')
|
||||||
|
ctx.fillStyle = gradient
|
||||||
|
ctx.lineWidth = lineSize
|
||||||
|
ctx.strokeStyle = '#FFF'
|
||||||
|
ctx.textAlign = textAlign || 'center'
|
||||||
|
ctx.font = `${fontSize}px ${fontName}`
|
||||||
|
ctx.strokeText(string, x, y)
|
||||||
|
ctx.fillText(string, x, y)
|
||||||
|
ctx.restore()
|
||||||
|
}
|