Skip to content

Commit 301ac51

Browse files
authored
Merge pull request #9 from keguigong/dev
Dev
2 parents 1bfff9d + 7ca4046 commit 301ac51

File tree

11 files changed

+590
-74
lines changed

11 files changed

+590
-74
lines changed

game/DistanceMeter.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,11 @@ export default class DistanceMeter {
191191
this.highScore = "AB " + highScoreStr
192192
}
193193

194+
reset() {
195+
this.update(0, 0)
196+
this.achievement = false
197+
}
198+
194199
static config = {
195200
MAX_DISTANCE_UNITS: 5, // 分数的最大位数
196201
ACHIEVEMENT_DISTANCE: 100, // 每 100 米触发一次闪动特效

game/GameOverPanel.ts

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import Runner from "./Runner"
2+
import { IS_HIDPI } from "./varibles"
3+
4+
export default class GameOverPanel {
5+
canvas!: HTMLCanvasElement
6+
ctx!: CanvasRenderingContext2D
7+
canvasDimensions!: Dimensions
8+
textImgPos!: Position
9+
restartImgPos!: Position
10+
11+
currentFrame = 0
12+
frameTimeStamp = 0
13+
animTimer = 0
14+
flashTime = 0
15+
flashCounter = 0
16+
gameOverRafId: number | null = null
17+
18+
constructor(canvas: HTMLCanvasElement, textImgPos: Position, restartImgPos: Position, dimensions: any) {
19+
this.canvas = canvas
20+
this.ctx = canvas.getContext("2d")!
21+
this.canvasDimensions = dimensions
22+
this.textImgPos = textImgPos
23+
this.restartImgPos = restartImgPos
24+
25+
this.draw()
26+
}
27+
28+
/**
29+
* Update the panel dimensions.
30+
*/
31+
updateDimensions(width: number, optHeight?: number) {
32+
this.canvasDimensions.WIDTH = width
33+
if (optHeight) {
34+
this.canvasDimensions.HEIGHT = optHeight
35+
}
36+
this.currentFrame = GameOverPanel.animConfig.frames.length - 1
37+
}
38+
39+
drawGameOverText(dimensions: any) {
40+
let centerX = this.canvasDimensions.WIDTH / 2
41+
42+
let textSourceX = dimensions.TEXT_X
43+
let textSourceY = dimensions.TEXT_Y
44+
let textSourceWidth = dimensions.TEXT_WIDTH
45+
let textSourceHeight = dimensions.TEXT_HEIGHT
46+
47+
const textTargetX = Math.round(centerX - dimensions.TEXT_WIDTH / 2)
48+
const textTargetY = Math.round((this.canvasDimensions.HEIGHT - 25) / 3)
49+
const textTargetWidth = dimensions.TEXT_WIDTH
50+
const textTargetHeight = dimensions.TEXT_HEIGHT
51+
52+
if (IS_HIDPI) {
53+
textSourceX *= 2
54+
textSourceY *= 2
55+
textSourceWidth *= 2
56+
textSourceHeight *= 2
57+
}
58+
textSourceX += this.textImgPos.x
59+
textSourceY += this.textImgPos.y
60+
61+
this.ctx.save()
62+
63+
this.ctx.drawImage(
64+
Runner.imageSprite,
65+
textSourceX,
66+
textSourceY,
67+
textSourceWidth,
68+
textSourceHeight,
69+
textTargetX,
70+
textTargetY,
71+
textTargetWidth,
72+
textTargetHeight
73+
)
74+
75+
this.ctx.restore()
76+
}
77+
78+
drawRestartButton() {
79+
const dimensions = GameOverPanel.dimensions
80+
let framePosX = GameOverPanel.animConfig.frames[this.currentFrame]
81+
let restartSourceWidth = dimensions.RESTART_WIDTH
82+
let restartSourceHeight = dimensions.RESTART_HEIGHT
83+
const restartTargetX = this.canvasDimensions.WIDTH / 2 - dimensions.RESTART_WIDTH / 2
84+
const restartTargetY = this.canvasDimensions.HEIGHT / 2
85+
86+
if (IS_HIDPI) {
87+
restartSourceWidth *= 2
88+
restartSourceHeight *= 2
89+
framePosX *= 2
90+
}
91+
92+
this.ctx.save()
93+
94+
this.ctx.drawImage(
95+
Runner.imageSprite,
96+
this.restartImgPos.x + framePosX,
97+
this.restartImgPos.y,
98+
restartSourceWidth,
99+
restartSourceHeight,
100+
restartTargetX,
101+
restartTargetY,
102+
dimensions.RESTART_WIDTH,
103+
dimensions.RESTART_HEIGHT
104+
)
105+
106+
this.ctx.restore()
107+
}
108+
109+
draw() {
110+
this.drawGameOverText(GameOverPanel.dimensions)
111+
this.drawRestartButton()
112+
this.update()
113+
}
114+
115+
/**
116+
* Update animation frames.
117+
*/
118+
update() {
119+
const now = Date.now()
120+
const deltaTime = now - (this.frameTimeStamp || now)
121+
122+
this.frameTimeStamp = now
123+
this.animTimer += deltaTime
124+
this.flashTime += deltaTime
125+
126+
// Restart Button
127+
if (this.currentFrame == 0 && this.animTimer > GameOverPanel.LOGO_PAUSE_DURATION) {
128+
this.animTimer = 0
129+
this.currentFrame++
130+
this.drawRestartButton()
131+
} else if (this.currentFrame > 0 && this.currentFrame < GameOverPanel.animConfig.frames.length) {
132+
if (this.animTimer >= GameOverPanel.animConfig.msPerFrame) {
133+
this.currentFrame++
134+
this.drawRestartButton()
135+
}
136+
} else if (this.currentFrame == GameOverPanel.animConfig.frames.length) {
137+
this.reset()
138+
return
139+
}
140+
141+
this.gameOverRafId = requestAnimationFrame(this.update.bind(this))
142+
}
143+
144+
reset() {
145+
if (this.gameOverRafId) {
146+
cancelAnimationFrame(this.gameOverRafId)
147+
this.gameOverRafId = null
148+
}
149+
this.animTimer = 0
150+
this.frameTimeStamp = 0
151+
this.currentFrame = 0
152+
this.flashTime = 0
153+
this.flashCounter = 0
154+
}
155+
156+
static RESTART_ANIM_DURATION = 875
157+
static LOGO_PAUSE_DURATION = 875
158+
static FLASH_ITERATIONS = 5
159+
160+
static animConfig = {
161+
frames: [0, 36, 72, 108, 144, 180, 216, 252],
162+
msPerFrame: GameOverPanel.RESTART_ANIM_DURATION / 8
163+
}
164+
165+
static dimensions = {
166+
TEXT_X: 0, // 文字 "Game Over" 的 x 坐标
167+
TEXT_Y: 13,
168+
TEXT_WIDTH: 191, // 文字 "Game Over" 的宽度
169+
TEXT_HEIGHT: 11,
170+
RESTART_WIDTH: 36, // 重置按钮的宽度
171+
RESTART_HEIGHT: 32
172+
}
173+
}

game/Horizon.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,10 @@ export default class Horizon {
139139
this.addNewObstacle(currentSpeed)
140140
}
141141
}
142+
143+
reset() {
144+
this.obstacles = []
145+
this.horizonLine.reset()
146+
this.nightMode.reset()
147+
}
142148
}

game/HorizonLine.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ export default class HorizonLine {
8585
return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0
8686
}
8787

88+
reset() {
89+
this.xPos[0] = 0
90+
this.xPos[1] = HorizonLine.dimensions.WIDTH
91+
}
92+
8893
static dimensions = {
8994
WIDTH: 600,
9095
HEIGHT: 12,

game/Obstacle.ts

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,32 @@
1+
import CollisionBox from "./CollisionBox"
12
import Runner from "./Runner"
23
import { FPS, IS_HIDPI, getRandomNum } from "./varibles"
34

45
export default class Obstacle {
56
canvas!: HTMLCanvasElement
67
ctx!: CanvasRenderingContext2D
7-
88
spritePos!: Position
99
typeConfig!: ConfigDict
1010
gapCoefficient!: number
11-
// 每组障碍物的数量(随机 1~3 个)
12-
size!: number
11+
12+
// #obstacles in each obstacle group
13+
size = getRandomNum(1, Obstacle.MAX_OBSTACLE_LENGTH)
1314
dimensions!: Dimensions
14-
remove!: boolean
15-
xPos!: number
16-
yPos!: number
17-
width!: number
18-
gap!: number
19-
speedOffset!: number
15+
remove = false
16+
xPos = 0
17+
yPos = 0
18+
width = 0
19+
gap = 0
20+
speedOffset = 0
2021

21-
currentFrame!: number
22-
timer!: number
22+
// Non-static obstacles
23+
currentFrame = 0
24+
timer = 0
2325

2426
followingObstacleCreated = false
2527

28+
collisionBoxes: CollisionBox[] = []
29+
2630
constructor(
2731
canvas: HTMLCanvasElement,
2832
spritePos: Position,
@@ -37,29 +41,25 @@ export default class Obstacle {
3741
this.spritePos = spritePos
3842
this.typeConfig = type
3943
this.gapCoefficient = gapCoefficient
40-
// #obstacles in each obstacle group
41-
this.size = getRandomNum(1, Obstacle.MAX_OBSTACLE_LENGTH)
4244
this.dimensions = dimensions
43-
this.remove = false
45+
4446
this.xPos = dimensions.WIDTH + (optXOffset || 0)
4547
this.yPos = 0
4648

47-
this.gap = 0
48-
this.speedOffset = 0
49-
50-
// Non-static obstacles
51-
this.currentFrame = 0
52-
this.timer = 0
53-
5449
this.init(speed)
5550
}
5651

5752
init(speed: number) {
53+
this.cloneCollisionBoxes()
54+
55+
// Only allow sizing if we're at the right speed.
5856
if (this.size > 1 && this.typeConfig.multipleSpeed > speed) {
5957
this.size = 1
6058
}
59+
6160
this.width = this.typeConfig.width + this.size
6261

62+
// Check if obstacle can be positioned at various heights.
6363
if (Array.isArray(this.typeConfig.yPos)) {
6464
let yPosConfig = this.typeConfig.yPos
6565
this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)]
@@ -68,8 +68,20 @@ export default class Obstacle {
6868
}
6969
this.draw()
7070

71-
// 对于速度与地面不同的障碍物(翼龙)进行速度修正
72-
// 使得有的速度看起来快一些,有的看起来慢一些
71+
// Make collision box adjustments,
72+
// Central box is adjusted to the size as one box.
73+
// ____ ______ ________
74+
// _| |-| _| |-| _| |-|
75+
// | |<->| | | |<--->| | | |<----->| |
76+
// | | 1 | | | | 2 | | | | 3 | |
77+
// |_|___|_| |_|_____|_| |_|_______|_|
78+
//
79+
if (this.size > 1) {
80+
this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width - this.collisionBoxes[2].width
81+
this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width
82+
}
83+
84+
// For obstacles that go at a different speed from the horizon.
7385
if (this.typeConfig.speedOffset) {
7486
this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset : -this.typeConfig.speedOffset
7587
}
@@ -145,17 +157,31 @@ export default class Obstacle {
145157
return this.xPos + this.width > 0
146158
}
147159

160+
cloneCollisionBoxes() {
161+
let collisionBoxes = this.typeConfig.collisionBoxes
162+
163+
for (let i = collisionBoxes.length - 1; i >= 0; i--) {
164+
this.collisionBoxes[i] = new CollisionBox(
165+
collisionBoxes[i].x,
166+
collisionBoxes[i].y,
167+
collisionBoxes[i].width,
168+
collisionBoxes[i].height
169+
)
170+
}
171+
}
172+
148173
static MAX_GAP_COEFFICIENT = 1.5
149174
static MAX_OBSTACLE_LENGTH = 3
150-
static types: ConfigDict[] = [
175+
static types = [
151176
{
152177
type: "CACTUS_SMALL",
153178
width: 17,
154179
height: 35,
155180
yPos: 105,
156181
multipleSpeed: 4,
157182
minGap: 120,
158-
minSpeed: 0
183+
minSpeed: 0,
184+
collisionBoxes: [new CollisionBox(0, 7, 5, 27), new CollisionBox(4, 0, 6, 34), new CollisionBox(10, 4, 7, 14)]
159185
},
160186
{
161187
type: "CACTUS_LARGE",
@@ -164,16 +190,25 @@ export default class Obstacle {
164190
yPos: 90,
165191
multipleSpeed: 7,
166192
minGap: 120,
167-
minSpeed: 0
193+
minSpeed: 0,
194+
collisionBoxes: [new CollisionBox(0, 12, 7, 38), new CollisionBox(8, 0, 7, 49), new CollisionBox(13, 10, 10, 38)]
168195
},
169196
{
170197
type: "PTERODACTYL",
171198
width: 46,
172199
height: 40,
173-
yPos: [100, 75, 50],
200+
yPos: [100, 75, 50], // Variable height.
201+
yPosMobile: [100, 50], // Variable height mobile.
174202
multipleSpeed: 999,
175203
minSpeed: 8.5,
176204
minGap: 150,
205+
collisionBoxes: [
206+
new CollisionBox(15, 15, 16, 5),
207+
new CollisionBox(18, 21, 24, 6),
208+
new CollisionBox(2, 14, 4, 3),
209+
new CollisionBox(6, 10, 4, 7),
210+
new CollisionBox(10, 8, 6, 9)
211+
],
177212
numFrames: 2,
178213
frameRate: 1000 / 6,
179214
speedOffset: 0.8

0 commit comments

Comments
 (0)