Skip to content

Commit a8d9506

Browse files
feat(ecs): add camera zoom in/out (#629)
feat(ecs): add transform scale property --------- Co-authored-by: Ruggero Visintin <9091842+RuggeroVisintin@users.noreply.github.com>
1 parent 98c41ac commit a8d9506

10 files changed

Lines changed: 68 additions & 44 deletions

File tree

.github/workflows/visual-tests-only.yml

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -60,24 +60,10 @@ jobs:
6060
path: test-results/visual/snapshots/
6161
if_no_artifact_found: ignore
6262

63-
- name: Download baselines from main (fallback or for PRs)
64-
if: (inputs.event_name == 'pull_request' && steps.download_updated_pr_baseline.outcome == 'failure')
65-
uses: dawidd6/action-download-artifact@v6
66-
with:
67-
github_token: ${{ secrets.GITHUB_TOKEN }}
68-
workflow: ci.yml
69-
branch: main
70-
name: visual-baseline
71-
path: test-results/visual/snapshots/
72-
if_no_artifact_found: fail
73-
74-
- name: Install dependencies
75-
run: npm ci
76-
- name: Build project
77-
run: npm run build
78-
7963
# Verify baselines exist (only for PRs)
8064
- name: Verify baselines exist
65+
id: verify_baselines
66+
continue-on-error: true
8167
if: inputs.pr_number || inputs.event_name == 'pull_request'
8268
run: |
8369
if [ ! -d "test-results/visual/snapshots" ] || [ "$(find test-results/visual/snapshots -name "*-ci-snapshots" -type d | wc -l)" -eq 0 ]; then
@@ -100,6 +86,22 @@ jobs:
10086
echo "Baseline files:"
10187
find test-results/visual/snapshots -name "*.png" | head -5
10288
fi
89+
90+
- name: Download baselines from main
91+
if: (inputs.event_name == 'pull_request' && steps.verify_baselines.outcome == 'failure')
92+
uses: dawidd6/action-download-artifact@v6
93+
with:
94+
github_token: ${{ secrets.GITHUB_TOKEN }}
95+
workflow: ci.yml
96+
branch: main
97+
name: visual-baseline
98+
path: test-results/visual/snapshots/
99+
if_no_artifact_found: fail
100+
101+
- name: Install dependencies
102+
run: npm ci
103+
- name: Build project
104+
run: npm run build
103105

104106
# Run visual tests
105107
- name: Run Visual Tests

examples/cameraMovement/index.html

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
resolution: { width: 1920, height: 1080 }
1212
});
1313

14-
const scene = engine.createScene();
14+
const scene = new SparkEngine.Scene();
1515

1616
const leftWall = new SparkEngine.StaticObject();
1717
leftWall.transform.size = { width: 20, height: 150 };
@@ -31,7 +31,7 @@
3131
const playerCamera = new SparkEngine.CameraComponent();
3232
camera.addComponent(playerCamera);
3333

34-
cameraController.onInputEventCb = (({ KeyW, KeyS, KeyA, KeyD }) => {
34+
cameraController.onInputEventCb = (({ KeyW, KeyS, KeyA, KeyD, KeyQ, KeyE }) => {
3535
const parentTransform = cameraController.getContainer().getComponent('TransformComponent');
3636

3737
if (KeyW === SparkEngine.KeyStatus.Down) {
@@ -52,6 +52,12 @@
5252
} else {
5353
parentTransform.velocity.x = -0;
5454
}
55+
56+
if (KeyQ === SparkEngine.KeyStatus.Down) {
57+
parentTransform.scale += 0.3;
58+
} else if (KeyE === SparkEngine.KeyStatus.Down) {
59+
parentTransform.scale -= 0.3;
60+
}
5561
});
5662

5763

@@ -85,6 +91,7 @@
8591
scene.registerEntity(camera);
8692

8793

94+
scene.draw(engine);
8895
engine.run();
8996
</script>
9097
</body>

src/ecs/components/CameraComponent.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ import { IDrawableComponent } from "./interfaces/IDrawableComponent";
1010
*/
1111
@Type('CameraComponent')
1212
export class CameraComponent extends BaseDrawableComponent implements IDrawableComponent {
13-
1413
public draw(renderer: Renderer): void {
15-
renderer.pushRenderCommand(new SetTransformMatrixCommand([-this.transform.position.x, -this.transform.position.y]));
14+
renderer.pushRenderCommand(new SetTransformMatrixCommand([-this.transform.position.x, -this.transform.position.y], [this.transform.scale, this.transform.scale]));
1615
}
1716
}

src/ecs/components/TransformComponent.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface TransformComponentProps extends ComponentProps {
1414
size?: Size2D;
1515
depthIndex?: number;
1616
velocity?: Vec2;
17+
scale?: number;
1718
}
1819

1920
/**
@@ -25,6 +26,7 @@ export class TransformComponent extends BaseComponent {
2526
public size: Size2D = { width: 0, height: 0 };
2627
public depthIndex: number = 0;
2728
public velocity: Vec2 = new Vec2();
29+
public scale: number = 1;
2830

2931
constructor(props?: TransformComponentProps) {
3032
super();
@@ -33,6 +35,7 @@ export class TransformComponent extends BaseComponent {
3335
if (props?.size) this.size = props.size;
3436
if (props?.depthIndex) this.depthIndex = props.depthIndex;
3537
if (props?.velocity) this.velocity = Vec2.from(props.velocity);
38+
if (props?.scale) this.scale = props.scale;
3639
}
3740

3841
public update(deltaTime?: number): void {
@@ -46,7 +49,8 @@ export class TransformComponent extends BaseComponent {
4649
position: this.position,
4750
size: this.size,
4851
depthIndex: this.depthIndex,
49-
velocity: this.velocity
52+
velocity: this.velocity,
53+
scale: this.scale
5054
};
5155
}
5256
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import { CanvasDevice } from "../../platform";
22
import { RenderCommand, RenderCommandID } from "./RenderCommand";
33

4+
/** @internal */
45
export class SetTransformMatrixCommand implements RenderCommand {
56
public readonly renderCommandID: RenderCommandID = RenderCommandID.RC_SetTransformMatrix;
67

78
public constructor(
8-
public readonly position: [number, number]
9+
public readonly translate: [number, number],
10+
public readonly scale: [number, number] = [1, 1],
911
) { }
1012

1113
public execute(ctx: CanvasRenderingContext2D, gfx: CanvasDevice): void {
1214
// Apply resolution scaling to the transformation matrix where the 1s are
13-
gfx.setTransform(ctx, [1, 0, 0, 1, this.position[0], this.position[1]]);
15+
gfx.setTransform(ctx, [this.scale[0], 0, 0, this.scale[1], this.translate[0], this.translate[1]]);
1416
}
1517
}

test/unit/__mocks__/assets/EntitiesWithComponents.scene.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"size": {
1313
"width": 100,
1414
"height": 50
15-
}
15+
},
16+
"scale": 1
1617
}
1718
]
1819
},
@@ -28,7 +29,8 @@
2829
"size": {
2930
"width": 100,
3031
"height": 50
31-
}
32+
},
33+
"scale": 1
3234
},
3335
{
3436
"__type": "MaterialComponent",

test/unit/ecs/components/CameraComponent.test.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,19 @@ describe('ecs/components/CameraComponent', () => {
55
let cameraComponent = new CameraComponent();
66

77
beforeEach(() => {
8-
renderer = new Renderer(new CanvasDevice(), {width: 1920, height: 1080}, new CanvasRenderingContext2D());
8+
renderer = new Renderer(new CanvasDevice(), { width: 1920, height: 1080 }, new CanvasRenderingContext2D());
99
cameraComponent = new CameraComponent();
1010
});
1111

1212
describe('.draw()', () => {
13-
it('Should push the right draw command to the renderer', () => {
14-
cameraComponent.draw(renderer);
15-
16-
expect(renderer.commandBuffer).toEqual([new SetTransformMatrixCommand([-0, -0])])
17-
})
18-
19-
it('Should push the camera position into the render command', () => {
13+
it('Should push the camera transform position and scale into the renderer', () => {
2014
cameraComponent.transform.position.x = 10;
2115
cameraComponent.transform.position.y = 20;
16+
cameraComponent.transform.scale = 2;
2217

2318
cameraComponent.draw(renderer);
2419

25-
expect(renderer.commandBuffer).toEqual([new SetTransformMatrixCommand([-10, -20])])
20+
expect(renderer.commandBuffer).toEqual([new SetTransformMatrixCommand([-10, -20], [2, 2])])
2621
})
2722
})
2823

@@ -62,6 +57,4 @@ describe('ecs/components/CameraComponent', () => {
6257
})
6358
})
6459
})
65-
66-
6760
})

test/unit/ecs/components/TransformComponent.test.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,20 @@ describe('ecs/components/TransformComponent', () => {
1818
width: 0,
1919
height: 0
2020
})
21-
})
21+
});
22+
23+
it('Should have a default scale of 1', () => {
24+
const transformComponent = new TransformComponent();
25+
expect(transformComponent.scale).toEqual(1);
26+
});
2227

2328
it('Should init from a props object', () => {
2429
const init: TransformComponentProps = {
2530
position: new Vec2(0, 2),
2631
depthIndex: 1,
2732
velocity: new Vec2(3, 4),
28-
size: { width: 10, height: 15 }
33+
size: { width: 10, height: 15 },
34+
scale: 2
2935
}
3036

3137
const transformComponent = new TransformComponent(init);
@@ -38,7 +44,7 @@ describe('ecs/components/TransformComponent', () => {
3844
it('Should apply the velocity to the position of the component', () => {
3945
const transformComponent = new TransformComponent();
4046
transformComponent.velocity.x = 3;
41-
47+
4248
transformComponent.update();
4349

4450
expect(transformComponent.position.x).toEqual(3);
@@ -63,7 +69,8 @@ describe('ecs/components/TransformComponent', () => {
6369
size: {
6470
width: 0,
6571
height: 0
66-
}
72+
},
73+
scale: 1
6774
})
6875
})
6976
})

test/unit/engine/Scene.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { defaultEntitiesScene, entitiesWithComponents } from "../__mocks__/scene
55
describe('/game/Scene', () => {
66
let scene: Scene;
77
let engine: GameEngine;
8-
8+
99

1010
beforeEach(() => {
1111
engine = new GameEngine({
@@ -15,14 +15,14 @@ describe('/game/Scene', () => {
1515
width: 800,
1616
height: 600
1717
},
18-
additionalRenderSystems: (renderer, imageLoader) => {
18+
additionalRenderSystems: (renderer, imageLoader) => {
1919
return [
2020
new RenderSystem(renderer, imageLoader),
2121
new RenderSystem(renderer, imageLoader)
2222
];
2323
}
2424
});
25-
25+
2626
scene = new Scene();
2727
})
2828

@@ -204,7 +204,7 @@ describe('/game/Scene', () => {
204204
expect(engine.soundSystem.components).not.toContain(soundComponent);
205205
});
206206
});
207-
207+
208208

209209
it('Should throw if the same entity is added twice', () => {
210210
const entity = new StaticObject();

test/unit/engine/__snapshots__/Scene.test.ts.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ exports[`/game/Scene .loadFromFile() Should load the entities configuration as w
1313
"x": 1,
1414
"y": 2,
1515
},
16+
"scale": 1,
1617
"size": {
1718
"height": 50,
1819
"width": 100,
@@ -33,6 +34,7 @@ exports[`/game/Scene .loadFromFile() Should load the entities configuration as w
3334
"x": 1,
3435
"y": 2,
3536
},
37+
"scale": 1,
3638
"size": {
3739
"height": 50,
3840
"width": 100,
@@ -66,6 +68,7 @@ exports[`/game/Scene .loadFromFile() Should load the entities configuration as w
6668
"x": 10,
6769
"y": 20,
6870
},
71+
"scale": 1,
6972
"size": {
7073
"height": 50,
7174
"width": 100,
@@ -96,6 +99,7 @@ exports[`/game/Scene .loadFromFile() Should load the entities configuration as w
9699
"x": 10,
97100
"y": 20,
98101
},
102+
"scale": 1,
99103
"size": {
100104
"height": 50,
101105
"width": 100,
@@ -126,6 +130,7 @@ exports[`/game/Scene .loadFromJson() Should load the entities configuration as w
126130
"x": 1,
127131
"y": 2,
128132
},
133+
"scale": 1,
129134
"size": {
130135
"height": 50,
131136
"width": 100,
@@ -146,6 +151,7 @@ exports[`/game/Scene .loadFromJson() Should load the entities configuration as w
146151
"x": 1,
147152
"y": 2,
148153
},
154+
"scale": 1,
149155
"size": {
150156
"height": 50,
151157
"width": 100,
@@ -179,6 +185,7 @@ exports[`/game/Scene .loadFromJson() Should load the entities configuration as w
179185
"x": 10,
180186
"y": 20,
181187
},
188+
"scale": 1,
182189
"size": {
183190
"height": 50,
184191
"width": 100,
@@ -209,6 +216,7 @@ exports[`/game/Scene .loadFromJson() Should load the entities configuration as w
209216
"x": 10,
210217
"y": 20,
211218
},
219+
"scale": 1,
212220
"size": {
213221
"height": 50,
214222
"width": 100,

0 commit comments

Comments
 (0)