pxt-common-packages
Version:
Microsoft MakeCode (PXT) common packages
256 lines (226 loc) • 9.13 kB
text/typescript
interface SparseArray<T> {
[index: number]: T;
}
/**
* Control the background, tiles and camera
*/
namespace scene {
export enum Flag {
NeedsSorting = 1 << 0, // indicates the sprites in the scene need to be sorted before rendering
SeeThrough = 1 << 1, // if set, render the previous scene 'below' this one as the background
IsRendering = 1 << 2, // if set, the scene is currently being rendered to the screen
}
export class SpriteHandler {
constructor(
public kind: number,
public handler: (sprite: Sprite) => void
) { }
}
export class OverlapHandler {
constructor(
public kind: number,
public otherKind: number,
public handler: (sprite: Sprite, otherSprite: Sprite) => void
) { }
}
export class TileWallHandler {
constructor(
public spriteKind: number,
public handler: (sprite: Sprite, location: tiles.Location) => void
) { }
}
export class TileOverlapHandler {
constructor(
public spriteKind: number,
public tileKind: Image,
public handler: (sprite: Sprite, location: tiles.Location) => void
) { }
}
export class GameForeverHandler {
public lock: boolean;
constructor(
public handler: () => void
) { }
}
// frame handler priorities
export const CONTROLLER_PRIORITY = 8;
export const UPDATE_CONTROLLER_PRIORITY = 13;
export const FOLLOW_SPRITE_PRIORITY = 14;
export const PHYSICS_PRIORITY = 15;
export const ANIMATION_UPDATE_PRIORITY = 15;
export const CONTROLLER_SPRITES_PRIORITY = 13;
export const UPDATE_INTERVAL_PRIORITY = 19;
export const UPDATE_PRIORITY = 20;
export const PRE_RENDER_UPDATE_PRIORITY = 55;
export const RENDER_BACKGROUND_PRIORITY = 60;
export const RENDER_SPRITES_PRIORITY = 90;
export const RENDER_DIAGNOSTICS_PRIORITY = 150;
export const MULTIPLAYER_SCREEN_PRIORITY = 190;
export const UPDATE_SCREEN_PRIORITY = 200;
export const MULTIPLAYER_POST_SCREEN_PRIORITY = 210;
// default rendering z indices
export const ON_PAINT_Z = -20;
export const TILE_MAP_Z = -1;
export const SPRITE_Z = 0;
export const ON_SHADE_Z = 80;
export const HUD_Z = 100;
export class Scene {
eventContext: control.EventContext;
background: Background;
tileMap: tiles.TileMap;
allSprites: SpriteLike[];
private spriteNextId: number;
spritesByKind: SparseArray<sprites.SpriteSet>;
physicsEngine: PhysicsEngine;
camera: scene.Camera;
flags: number;
destroyedHandlers: SpriteHandler[];
createdHandlers: SpriteHandler[];
overlapHandlers: OverlapHandler[];
overlapMap: SparseArray<number[]>;
tileOverlapHandlers: TileOverlapHandler[];
collisionHandlers: SpriteHandler[][];
wallCollisionHandlers: TileWallHandler[];
gameForeverHandlers: GameForeverHandler[];
particleSources: particles.ParticleSource[];
controlledSprites: controller.ControlledSprite[][];
controllerConnectionState: boolean[]
followingSprites: sprites.FollowingSprite[];
buttonEventHandlers: controller.ButtonEventHandlerState[];
private _millis: number;
private _data: any;
// a set of functions that need to be called when a scene is being initialized
static initializers: ((scene: Scene) => void)[] = [];
constructor(eventContext: control.EventContext, protected previousScene?: Scene) {
this.eventContext = eventContext;
this.flags = 0;
this.physicsEngine = new ArcadePhysicsEngine();
this.camera = new scene.Camera();
this.background = new Background(this.camera);
this.destroyedHandlers = [];
this.createdHandlers = [];
this.overlapHandlers = [];
this.overlapMap = {};
this.tileOverlapHandlers = [];
this.collisionHandlers = [];
this.wallCollisionHandlers = [];
this.gameForeverHandlers = [];
this.spritesByKind = {};
this.controlledSprites = [];
this.buttonEventHandlers = [];
this._data = {};
this._millis = 0;
}
init() {
if (this.allSprites) return;
power.poke(); // keep game alive a little more
this.allSprites = [];
this.spriteNextId = 0;
// update controller state
this.eventContext.registerFrameHandler(CONTROLLER_PRIORITY, () => {
this._millis += this.eventContext.deltaTimeMillis;
control.enablePerfCounter("controller_update")
controller.__update(this.eventContext.deltaTime);
})
// controller update 13
this.eventContext.registerFrameHandler(CONTROLLER_SPRITES_PRIORITY, controller._moveSprites);
// sprite following 14
// apply physics and collisions 15
this.eventContext.registerFrameHandler(PHYSICS_PRIORITY, () => {
control.enablePerfCounter("physics and collisions")
this.physicsEngine.move(this.eventContext.deltaTime);
});
// user update interval 19s
// user update 20
// prerender update 55
this.eventContext.registerFrameHandler(PRE_RENDER_UPDATE_PRIORITY, () => {
const dt = this.eventContext.deltaTime;
this.camera.update();
for (const s of this.allSprites)
s.__update(this.camera, dt);
})
// render background 60
// render 90
this.eventContext.registerFrameHandler(RENDER_SPRITES_PRIORITY, () => {
control.enablePerfCounter("scene_draw");
this.render();
});
// render diagnostics
this.eventContext.registerFrameHandler(RENDER_DIAGNOSTICS_PRIORITY, () => {
if (game.stats && control.EventContext.onStats) {
control.EventContext.onStats(
control.EventContext.lastStats +
` sprites:${this.allSprites.length}`
)
}
if (game.debug)
this.physicsEngine.draw();
game.consoleOverlay.draw();
// check for power deep sleep
power.checkDeepSleep();
});
// update screen
this.eventContext.registerFrameHandler(UPDATE_SCREEN_PRIORITY, control.__screen.update);
multiplayer.initServer();
multiplayer.initPlayerConnectionListeners();
// register additional components
Scene.initializers.forEach(f => f(this));
}
get data() {
return this._data;
}
/**
* Gets the elapsed time in the scene
*/
millis(): number {
return this._millis;
}
addSprite(sprite: SpriteLike) {
this.allSprites.push(sprite);
sprite.id = this.spriteNextId++;
}
destroy() {
this.eventContext = undefined;
this.background = undefined;
this.tileMap = undefined;
this.allSprites = undefined;
this.spriteNextId = undefined;
this.spritesByKind = undefined;
this.physicsEngine = undefined;
this.camera = undefined;
this.flags = undefined;
this.destroyedHandlers = undefined;
this.createdHandlers = undefined;
this.overlapHandlers = undefined;
this.tileOverlapHandlers = undefined;
this.collisionHandlers = undefined;
this.wallCollisionHandlers = undefined;
this.gameForeverHandlers = undefined;
this._data = undefined;
}
/**
* Renders the current frame as an image
*/
render() {
// bail out from recursive or parallel call.
if (this.flags & scene.Flag.IsRendering) return;
this.flags |= scene.Flag.IsRendering;
control.enablePerfCounter("render background")
if ((this.flags & scene.Flag.SeeThrough) && this.previousScene) {
this.previousScene.render();
} else {
this.background.draw();
}
control.enablePerfCounter("sprite sort")
if (this.flags & Flag.NeedsSorting) {
this.allSprites.sort(function (a, b) { return a.z - b.z || a.id - b.id; })
this.flags &= ~scene.Flag.NeedsSorting;
}
control.enablePerfCounter("sprite draw")
for (const s of this.allSprites) {
s.__draw(this.camera);
}
this.flags &= ~scene.Flag.IsRendering;
}
}
}