camera.test.js
3.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import { describe, it, expect } from 'vitest'
import { createCamera } from '../src/camera.js'
const vp = () => ({ w: 1920, h: 1080 })
describe('camera 镜头数学', () => {
it('cut 瞬变状态(验收 6)', () => {
const cam = createCamera({ getViewport: vp })
cam.applyCommand({ type: 'cut', target: { x: 100, y: 50 }, zoom: 2 })
expect(cam.cam).toEqual({ x: 100, y: 50, zoom: 2 })
})
it('pan 在 duration 内单调逼近目标,中点介于起止(验收 6)', () => {
const cam = createCamera({ getViewport: vp })
cam.applyCommand({ type: 'cut', target: { x: 0, y: 0 }, zoom: 1 })
cam.applyCommand({ type: 'pan', target: { x: 1000, y: 0 }, duration: 1000, easing: 'linear' })
cam.update(500)
expect(cam.cam.x).toBeGreaterThan(0)
expect(cam.cam.x).toBeLessThan(1000)
expect(cam.cam.x).toBeCloseTo(500, 6) // linear 中点
cam.update(500)
expect(cam.cam.x).toBeCloseTo(1000, 6)
})
it('zoom 补间逼近目标(验收 6)', () => {
const cam = createCamera({ getViewport: vp })
cam.applyCommand({ type: 'cut', zoom: 1 })
cam.applyCommand({ type: 'zoom', zoom: 3, duration: 1000, easing: 'linear' })
cam.update(1000)
expect(cam.cam.zoom).toBeCloseTo(3, 6)
})
it('follow 阻尼跟随帧率无关:不同 dt 切分到同总时长结果一致(验收 6)', () => {
const lookup = () => ({ x: 1000, y: 0 })
const makeFollow = () => {
const c = createCamera({ getViewport: vp })
c.applyCommand({ type: 'cut', target: { x: 0, y: 0 }, zoom: 1 })
c.applyCommand({ type: 'follow', follow: 'hero' }, lookup)
return c
}
const cA = makeFollow()
for (let i = 0; i < 100; i++) cA.update(10) // 100×10ms
const cB = makeFollow()
for (let i = 0; i < 20; i++) cB.update(50) // 20×50ms(同 1000ms 总时长)
expect(cA.cam.x).toBeCloseTo(cB.cam.x, 0) // 帧率无关,容差内一致
})
it('shake 随时间衰减、duration 后归零(验收 6)', () => {
let seq = [0.9, 0.9, 0.9, 0.9, 0.9, 0.9]
let i = 0
const cam = createCamera({ getViewport: vp, random: () => seq[i++ % seq.length] })
cam.applyCommand({ type: 'shake', magnitude: 20, duration: 100 })
cam.update(10)
const s1 = Math.abs(cam.getRenderState().shakeX)
cam.update(50)
const s2 = Math.abs(cam.getRenderState().shakeX)
expect(s2).toBeLessThan(s1) // 衰减
cam.update(100) // 过 duration
const rs = cam.getRenderState()
expect(rs.shakeX).toBe(0)
expect(rs.shakeY).toBe(0)
})
it('reset 归零跟随/补间/抖动', () => {
const cam = createCamera({ getViewport: vp })
cam.applyCommand({ type: 'pan', target: { x: 500, y: 0 }, duration: 1000 })
cam.reset({ x: 7, y: 8 })
expect(cam.tween).toBeNull()
expect(cam.cam).toEqual({ x: 7, y: 8, zoom: 1 })
})
it('getRenderState 只产状态,含视口尺寸(验收 6:只产状态)', () => {
const cam = createCamera({ getViewport: vp })
cam.applyCommand({ type: 'cut', target: { x: 3, y: 4 }, zoom: 1.5 })
cam.update(16)
const rs = cam.getRenderState()
expect(rs).toMatchObject({ x: 3, y: 4, zoom: 1.5, viewW: 1920, viewH: 1080 })
expect(typeof rs.shakeX).toBe('number')
})
})