add tower builing game

This commit is contained in:
hamid zarghami
2025-12-09 10:48:48 +03:30
parent 8119203506
commit 53b9139506
79 changed files with 15363 additions and 3 deletions
@@ -0,0 +1,126 @@
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: 'floor',
size: engine.width * 0.06,
x: (engine.width * 0.24) + threeFiguresOffset,
y: engine.width * 0.12,
textAlign: 'left',
fontName: 'Arial',
fontWeight: 'bold'
})
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
}
}
+100
View File
@@ -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)
}
+250
View File
@@ -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
// 作弊检测 超出左边或右边13
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
}
}
+53
View File
@@ -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)
}
+42
View File
@@ -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'
+82
View File
@@ -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)
}
+54
View File
@@ -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()
}
+119
View File
@@ -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
}
+40
View File
@@ -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()
}
+35
View File
@@ -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)
}
+176
View File
@@ -0,0 +1,176 @@
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, fontName = 'wenxue', fontWeight = 'normal'
} = option
const { ctx } = engine
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 = `${fontWeight} ${fontSize}px ${fontName}`
ctx.strokeText(string, x, y)
ctx.fillText(string, x, y)
ctx.restore()
}