Skip to content

Commit e957c23

Browse files
committed
docs: more to aviator demo but positioning as well as attraction logic need revisit
1 parent 96bef83 commit e957c23

17 files changed

+1738
-51
lines changed
121 KB
Binary file not shown.

apps/kitchen-sink/public/helvetiker_regular.typeface.json

Lines changed: 1281 additions & 0 deletions
Large diffs are not rendered by default.

apps/kitchen-sink/public/magnet.glb

291 KB
Binary file not shown.

apps/kitchen-sink/src/app/app.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, computed, inject } from '@angular/core';
1+
import { Component, computed, inject, signal } from '@angular/core';
22
import { toSignal } from '@angular/core/rxjs-interop';
33
import { NavigationEnd, Router, RouterOutlet } from '@angular/router';
44
import { filter } from 'rxjs';

apps/kitchen-sink/src/app/misc/aviator/aviator.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
11
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
22
import { NgtCanvas } from 'angular-three';
3-
import { CollectiblesStore } from './collectible/collectibles.store';
3+
import { CollectiblesStore, POWER_UP_TYPES } from './collectible/collectibles.store';
44
import { Experience } from './experience';
55
import { GameStore } from './game.store';
6+
import { CoinOverlay } from './overlays/coin-overlay';
7+
import { HealthOverlay } from './overlays/health-overlay';
8+
import { PowerUpsOverlay } from './overlays/power-ups-overlay';
69

710
@Component({
811
standalone: true,
912
template: `
1013
<ngt-canvas [sceneGraph]="sceneGraph" [camera]="{ fov: 50, near: 0.1, far: 10000 }" [shadows]="true" />
11-
<span class="absolute top-4 right-4 font-mono text-lg text-[#ffd700] font-bold">
12-
Coins: {{ gameStore.coins() }}
13-
</span>
14+
<div class="absolute top-4 right-1/2 translate-x-1/2 flex gap-4 items-center">
15+
<app-health-overlay />
16+
<div class="w-0.5 h-4 bg-white"></div>
17+
<app-coin-overlay />
18+
<div class="w-0.5 h-4 bg-white"></div>
19+
<app-power-ups-overlay />
20+
<!-- <button (click)="onTestPowerUp()" class="text-white font-mono">Test powerup</button>-->
21+
<!-- <button (click)="onTestHealth()" class="text-white font-mono">Test health</button>-->
22+
</div>
1423
`,
1524
changeDetection: ChangeDetectionStrategy.OnPush,
16-
imports: [NgtCanvas],
25+
imports: [NgtCanvas, CoinOverlay, HealthOverlay, PowerUpsOverlay],
1726
providers: [GameStore, CollectiblesStore],
1827
styles: `
1928
:host {
@@ -27,4 +36,13 @@ import { GameStore } from './game.store';
2736
export default class Aviator {
2837
protected sceneGraph = Experience;
2938
protected gameStore = inject(GameStore);
39+
40+
onTestPowerUp() {
41+
const random = POWER_UP_TYPES[Math.floor(Math.random() * 3)];
42+
this.gameStore.acquirePowerUp(random);
43+
}
44+
45+
onTestHealth() {
46+
this.gameStore.health.update((prev) => prev - 1);
47+
}
3048
}

apps/kitchen-sink/src/app/misc/aviator/collectible/coin.ts renamed to apps/kitchen-sink/src/app/misc/aviator/collectible/collectible-coin.component.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import { injectBeforeRender, NgtArgs } from 'angular-three';
1010
import { CylinderGeometry, Mesh, MeshPhongMaterial } from 'three';
1111
import { COIN_DISTANCE_TOLERANCE, COLOR_COINS } from '../constants';
1212
import { GameStore } from '../game.store';
13-
import { Spawnable, SPAWNABLE_DISTANCE_TOLERANCE, SPAWNABLE_PARTICLE_COLOR } from '../spawnable/spawnables.store';
13+
import {
14+
Spawnable,
15+
SPAWNABLE_ATTRACTABLE,
16+
SPAWNABLE_DISTANCE_TOLERANCE,
17+
SPAWNABLE_PARTICLE_COLOR,
18+
} from '../spawnable/spawnables.store';
1419
import { Collectible } from './collectibles.store';
1520

1621
const coinGeometry = new CylinderGeometry(4, 4, 1, 10);
@@ -22,7 +27,7 @@ const coinMaterial = new MeshPhongMaterial({
2227
});
2328

2429
@Component({
25-
selector: 'app-coin',
30+
selector: 'app-collectible-coin',
2631
standalone: true,
2732
template: `
2833
<ngt-mesh
@@ -40,9 +45,10 @@ const coinMaterial = new MeshPhongMaterial({
4045
providers: [
4146
{ provide: SPAWNABLE_DISTANCE_TOLERANCE, useValue: COIN_DISTANCE_TOLERANCE },
4247
{ provide: SPAWNABLE_PARTICLE_COLOR, useValue: COLOR_COINS },
48+
{ provide: SPAWNABLE_ATTRACTABLE, useValue: true },
4349
],
4450
})
45-
export class Coin {
51+
export class CollectibleCoin {
4652
protected coinGeometry = coinGeometry;
4753
protected coinMaterial = coinMaterial;
4854

@@ -52,7 +58,7 @@ export class Coin {
5258
protected spawnable = inject(Spawnable, { host: true });
5359

5460
constructor() {
55-
this.spawnable.spawnable = this.coinRef;
61+
this.spawnable.assignSpawnable(this.coinRef);
5662
this.spawnable.onCollide(() => this.gameStore.incrementCoin());
5763

5864
injectBeforeRender(() => {
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
computed,
5+
CUSTOM_ELEMENTS_SCHEMA,
6+
ElementRef,
7+
inject,
8+
input,
9+
viewChild,
10+
} from '@angular/core';
11+
import { injectBeforeRender, NgtArgs } from 'angular-three';
12+
import { NgtsText3D } from 'angular-three-soba/abstractions';
13+
import { injectGLTF } from 'angular-three-soba/loaders';
14+
import { NgtsMeshTransmissionMaterial } from 'angular-three-soba/materials';
15+
import { Color, Mesh, SphereGeometry } from 'three';
16+
import { COLOR_POWER_UPS, POWER_UP_DISTANCE_TOLERANCE } from '../constants';
17+
import { GameStore } from '../game.store';
18+
import { Spawnable, SPAWNABLE_DISTANCE_TOLERANCE, SPAWNABLE_PARTICLE_COLOR } from '../spawnable/spawnables.store';
19+
import { Collectible, PowerUpType } from './collectibles.store';
20+
21+
injectGLTF.preload(() => ['./dragon_chestplate.glb', './magnet.glb']);
22+
23+
const powerUpGeometry = new SphereGeometry(1, 32, 32);
24+
const background = new Color('white');
25+
26+
@Component({
27+
selector: 'app-collectible-power-up',
28+
standalone: true,
29+
template: `
30+
<ngt-mesh
31+
#power
32+
[geometry]="powerUpGeometry"
33+
[castShadow]="true"
34+
[scale]="6"
35+
[position]="[spawnable.positionX(), spawnable.positionY(), 0]"
36+
>
37+
<ngts-mesh-transmission-material [options]="{ thickness: 0.5, transmission: 1, roughness: 0, background }" />
38+
39+
@switch (powerUp()) {
40+
@case ('armor') {
41+
<ngt-primitive *args="[armor()]" [parameters]="{ position: [0, -1.25, 0] }" />
42+
}
43+
@case ('magnet') {
44+
<ngt-primitive *args="[magnet()]" [parameters]="{ scale: 0.35 }" />
45+
}
46+
@case ('doubleCoin') {
47+
<ngts-text-3d
48+
text="x2"
49+
font="helvetiker_regular.typeface.json"
50+
[options]="{ position: [-0.5, -0.35, 0], size: 0.75, letterSpacing: -0.1 }"
51+
>
52+
<ngt-mesh-standard-material color="black" />
53+
</ngts-text-3d>
54+
}
55+
}
56+
</ngt-mesh>
57+
`,
58+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
59+
changeDetection: ChangeDetectionStrategy.OnPush,
60+
imports: [NgtsMeshTransmissionMaterial, NgtArgs, NgtsText3D],
61+
hostDirectives: [{ directive: Collectible, inputs: ['state'], outputs: ['stateChange'] }],
62+
providers: [
63+
{ provide: SPAWNABLE_DISTANCE_TOLERANCE, useValue: POWER_UP_DISTANCE_TOLERANCE },
64+
{ provide: SPAWNABLE_PARTICLE_COLOR, useValue: COLOR_POWER_UPS },
65+
],
66+
})
67+
export class CollectiblePowerUp {
68+
powerUp = input.required<PowerUpType>();
69+
70+
protected powerUpGeometry = powerUpGeometry;
71+
protected background = background;
72+
73+
// https://sketchfab.com/tonicarre35
74+
private armorGLTF = injectGLTF(() => './dragon_chestplate.glb');
75+
protected armor = computed(() => {
76+
const gltf = this.armorGLTF();
77+
if (!gltf) return null;
78+
return gltf.scene.clone();
79+
});
80+
81+
// https://sketchfab.com/AlienDev
82+
private magnetGLTF = injectGLTF(() => './magnet.glb');
83+
protected magnet = computed(() => {
84+
const gltf = this.magnetGLTF();
85+
if (!gltf) return null;
86+
return gltf.scene.clone();
87+
});
88+
89+
private powerRef = viewChild.required<ElementRef<Mesh>>('power');
90+
private gameStore = inject(GameStore);
91+
protected spawnable = inject(Spawnable, { host: true });
92+
93+
constructor() {
94+
this.spawnable.assignSpawnable(this.powerRef);
95+
// this.spawnable.onCollide(() => {
96+
// this.gameStore.acquirePowerUp(this.powerUp());
97+
// });
98+
99+
injectBeforeRender(() => {
100+
const power = this.powerRef().nativeElement;
101+
power.rotation.y += 0.05;
102+
});
103+
}
104+
}

apps/kitchen-sink/src/app/misc/aviator/collectible/collectibles.store.ts

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,28 @@
1-
import { DestroyRef, Directive, inject, model, signal, untracked, WritableSignal } from '@angular/core';
1+
import { Directive, inject, Injectable, model, signal, untracked, WritableSignal } from '@angular/core';
22
import { PLANE_AMP_HEIGHT, PLANE_DEFAULT_HEIGHT, SEA_RADIUS } from '../constants';
33
import { GameStore } from '../game.store';
44
import { Spawnable } from '../spawnable/spawnables.store';
55

6+
export const POWER_UP_TYPES = ['armor', 'magnet', 'doubleCoin'] as const;
7+
export type PowerUpType = (typeof POWER_UP_TYPES)[number];
8+
69
export type CollectibleState = 'spawned' | 'collected' | 'skipped';
10+
export interface CollectibleItem {
11+
state: WritableSignal<CollectibleState>;
12+
angle: number;
13+
distance: number;
14+
positionY: number;
15+
positionX: number;
16+
}
17+
18+
export interface CollectibleCoin extends CollectibleItem {
19+
key: number;
20+
}
21+
22+
export interface CollectiblePowerUp extends CollectibleItem {
23+
type: PowerUpType;
24+
key: string;
25+
}
726

827
@Directive({
928
standalone: true,
@@ -12,36 +31,24 @@ export type CollectibleState = 'spawned' | 'collected' | 'skipped';
1231
export class Collectible {
1332
state = model.required<CollectibleState>();
1433

15-
private destroyRef = inject(DestroyRef);
16-
private collectiblesStore = inject(CollectiblesStore);
1734
private spawnable = inject(Spawnable, { host: true });
1835

1936
constructor() {
2037
this.spawnable.onCollide(() => this.state.set('collected'));
2138
this.spawnable.onSkip(() => this.state.set('skipped'));
22-
23-
this.collectiblesStore.collectibles.add(this);
24-
this.destroyRef.onDestroy(() => {
25-
this.collectiblesStore.collectibles.delete(this);
26-
});
2739
}
2840
}
2941

42+
@Injectable()
3043
export class CollectiblesStore {
31-
collectibles = new Set<Collectible>();
32-
3344
private gameStore = inject(GameStore);
3445

35-
coins = signal<
36-
Array<{
37-
key: number;
38-
state: WritableSignal<CollectibleState>;
39-
angle: number;
40-
distance: number;
41-
positionY: number;
42-
positionX: number;
43-
}>
44-
>([]);
46+
coins = signal<Array<CollectibleCoin>>([]);
47+
powerUps = signal<Array<CollectiblePowerUp>>([]);
48+
49+
state = {
50+
lastPowerUpSpawnedAt: 0,
51+
};
4552

4653
spawnCoins() {
4754
const nCoins = 1 + Math.floor(Math.random() * 10);
@@ -68,6 +75,25 @@ export class CollectiblesStore {
6875
this.gameStore.statistics.coinsSpawned += nCoins;
6976
}
7077

78+
spawnPowerUps() {
79+
const type = POWER_UP_TYPES[Math.floor(Math.random() * 3)];
80+
81+
this.powerUps.update((prev) => [
82+
...prev.filter((powerUp) => untracked(powerUp.state) === 'spawned'),
83+
{
84+
key: this.gameStore.statistics.powerUpsSpawned + type,
85+
type,
86+
state: signal<CollectibleState>('spawned'),
87+
angle: 0,
88+
distance: 1,
89+
positionY: -SEA_RADIUS,
90+
positionX: SEA_RADIUS + PLANE_DEFAULT_HEIGHT,
91+
},
92+
]);
93+
this.gameStore.statistics.powerUpsSpawned += 1;
94+
this.state.lastPowerUpSpawnedAt = this.gameStore.statistics.coinsCollected;
95+
}
96+
7197
private randomFromRange(min: number, max: number) {
7298
return min + Math.random() * (max - min);
7399
}

apps/kitchen-sink/src/app/misc/aviator/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const WAVES_MAX_SPEED = 0.003;
3333
export const CAMERA_SENSITIVITY = 0.002;
3434

3535
export const COIN_DISTANCE_TOLERANCE = 15;
36+
export const POWER_UP_DISTANCE_TOLERANCE = 15;
3637
export const COINS_SPEED = 0.5;
3738
export const DISTANCE_FOR_COINS_SPAWN = 50;
3839

@@ -45,6 +46,8 @@ export const ENEMY_DISTANCE_TOLERANCE = 10;
4546
export const ENEMIES_SPEED = 0.6;
4647
export const DISTANCE_FOR_ENEMIES_SPAWN = 50;
4748

49+
export const ATTRACTION_FACTOR = 0.75;
50+
4851
export const COLORS = {
4952
red: 0xf25346,
5053
orange: 0xffa500,
@@ -66,4 +69,5 @@ export const COLOR_SEA_LEVEL = [
6669
];
6770

6871
export const COLOR_COINS = 0xffd700; // 0x009999
72+
export const COLOR_POWER_UPS = 0x8f9992;
6973
export const COLOR_COLLECTIBLE_BUBBLE = COLOR_COINS;

0 commit comments

Comments
 (0)