added viewport zoom
This commit is contained in:
parent
7a0f9e5c95
commit
d9a87b2727
|
@ -26,7 +26,10 @@ export default class BoidDemo extends Scene {
|
|||
this.boids = new Array();
|
||||
|
||||
// Add the player
|
||||
this.add.graphic(Player, layer, new Vec2(0, 0));
|
||||
let player = this.add.graphic(Player, layer, new Vec2(0, 0));
|
||||
|
||||
this.viewport.follow(player);
|
||||
this.viewport.enableZoom();
|
||||
|
||||
// Create a bunch of boids
|
||||
for(let i = 0; i < 100; i++){
|
||||
|
|
|
@ -212,15 +212,15 @@ export default class QuadTree<T extends Region & Unique> implements Collection {
|
|||
* Renders the quadtree for demo purposes.
|
||||
* @param ctx
|
||||
*/
|
||||
public render_demo(ctx: CanvasRenderingContext2D): void {
|
||||
public render_demo(ctx: CanvasRenderingContext2D, origin: Vec2, zoom: number): void {
|
||||
ctx.strokeStyle = "#0000FF";
|
||||
ctx.strokeRect(this.boundary.x - this.boundary.hw, this.boundary.y - this.boundary.hh, 2*this.boundary.hw, 2*this.boundary.hh);
|
||||
ctx.strokeRect((this.boundary.x - this.boundary.hw - origin.x)*zoom, (this.boundary.y - this.boundary.hh - origin.y)*zoom, 2*this.boundary.hw*zoom, 2*this.boundary.hh*zoom);
|
||||
|
||||
if(this.divided){
|
||||
this.nw.render_demo(ctx);
|
||||
this.ne.render_demo(ctx);
|
||||
this.sw.render_demo(ctx);
|
||||
this.se.render_demo(ctx);
|
||||
this.nw.render_demo(ctx, origin, zoom);
|
||||
this.ne.render_demo(ctx, origin, zoom);
|
||||
this.sw.render_demo(ctx, origin, zoom);
|
||||
this.se.render_demo(ctx, origin, zoom);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,16 @@ export enum GameEventType {
|
|||
*/
|
||||
CANVAS_BLUR = "canvas_blur",
|
||||
|
||||
/**
|
||||
* Mouse wheel up event. Has data: {}
|
||||
*/
|
||||
WHEEL_UP = "wheel_up",
|
||||
|
||||
/**
|
||||
* Mouse wheel down event. Has data: {}
|
||||
*/
|
||||
WHEEL_DOWN = "wheel_down",
|
||||
|
||||
/**
|
||||
* Start Recording event. Has data: {}
|
||||
*/
|
||||
|
|
|
@ -20,6 +20,7 @@ export default class InputHandler{
|
|||
document.onkeyup = this.handleKeyUp;
|
||||
document.onblur = this.handleBlur;
|
||||
document.oncontextmenu = this.handleBlur;
|
||||
document.onwheel = this.handleWheel;
|
||||
}
|
||||
|
||||
private handleMouseDown = (event: MouseEvent, canvas: HTMLCanvasElement): void => {
|
||||
|
@ -62,6 +63,19 @@ export default class InputHandler{
|
|||
event.stopPropagation();
|
||||
}
|
||||
|
||||
private handleWheel = (event: WheelEvent): void => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
let gameEvent: GameEvent;
|
||||
if(event.deltaY < 0){
|
||||
gameEvent = new GameEvent(GameEventType.WHEEL_UP, {});
|
||||
} else {
|
||||
gameEvent = new GameEvent(GameEventType.WHEEL_DOWN, {});
|
||||
}
|
||||
this.eventQueue.addEvent(gameEvent);
|
||||
}
|
||||
|
||||
private getKey(keyEvent: KeyboardEvent){
|
||||
return keyEvent.key.toLowerCase();
|
||||
}
|
||||
|
|
|
@ -14,10 +14,16 @@ export default class InputReceiver{
|
|||
|
||||
private mousePressed: boolean;
|
||||
private mouseJustPressed: boolean;
|
||||
|
||||
private keyJustPressed: Map<boolean>;
|
||||
private keyPressed: Map<boolean>;
|
||||
|
||||
private mousePosition: Vec2;
|
||||
private mousePressPosition: Vec2;
|
||||
|
||||
private scrollDirection: number;
|
||||
private justScrolled: boolean;
|
||||
|
||||
private eventQueue: EventQueue;
|
||||
private receiver: Receiver;
|
||||
private viewport: Viewport;
|
||||
|
@ -30,11 +36,13 @@ export default class InputReceiver{
|
|||
this.keyPressed = new Map<boolean>();
|
||||
this.mousePosition = new Vec2(0, 0);
|
||||
this.mousePressPosition = new Vec2(0, 0);
|
||||
this.scrollDirection = 0;
|
||||
this.justScrolled = false;
|
||||
|
||||
this.eventQueue = EventQueue.getInstance();
|
||||
// Subscribe to all input events
|
||||
this.eventQueue.subscribe(this.receiver, [GameEventType.MOUSE_DOWN, GameEventType.MOUSE_UP, GameEventType.MOUSE_MOVE,
|
||||
GameEventType.KEY_DOWN, GameEventType.KEY_UP, GameEventType.CANVAS_BLUR]);
|
||||
GameEventType.KEY_DOWN, GameEventType.KEY_UP, GameEventType.CANVAS_BLUR, GameEventType.WHEEL_UP, GameEventType.WHEEL_DOWN]);
|
||||
}
|
||||
|
||||
static getInstance(): InputReceiver{
|
||||
|
@ -48,6 +56,8 @@ export default class InputReceiver{
|
|||
// Reset the justPressed values to false
|
||||
this.mouseJustPressed = false;
|
||||
this.keyJustPressed.forEach((key: string) => this.keyJustPressed.set(key, false));
|
||||
this.justScrolled = false;
|
||||
this.scrollDirection = 0;
|
||||
|
||||
while(this.receiver.hasNextEvent()){
|
||||
let event = this.receiver.getNextEvent();
|
||||
|
@ -91,6 +101,14 @@ export default class InputReceiver{
|
|||
if(event.type === GameEventType.CANVAS_BLUR){
|
||||
this.clearKeyPresses()
|
||||
}
|
||||
|
||||
if(event.type === GameEventType.WHEEL_UP){
|
||||
this.scrollDirection = -1;
|
||||
this.justScrolled = true;
|
||||
} else if(event.type === GameEventType.WHEEL_DOWN){
|
||||
this.scrollDirection = 1;
|
||||
this.justScrolled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,6 +141,14 @@ export default class InputReceiver{
|
|||
return this.mousePressed;
|
||||
}
|
||||
|
||||
didJustScroll(): boolean {
|
||||
return this.justScrolled;
|
||||
}
|
||||
|
||||
getScrollDirection(): number {
|
||||
return this.scrollDirection;
|
||||
}
|
||||
|
||||
getMousePosition(): Vec2 {
|
||||
return this.mousePosition;
|
||||
}
|
||||
|
|
|
@ -67,12 +67,13 @@ export default class GameLoop {
|
|||
this.GAME_CANVAS.style.setProperty("background-color", "whitesmoke");
|
||||
|
||||
// Give the canvas a size and get the rendering context
|
||||
this.WIDTH = gameConfig.viewportSize ? gameConfig.viewportSize.x : 800;
|
||||
this.HEIGHT = gameConfig.viewportSize ? gameConfig.viewportSize.y : 500;
|
||||
this.WIDTH = gameConfig.canvasSize ? gameConfig.canvasSize.x : 800;
|
||||
this.HEIGHT = gameConfig.canvasSize ? gameConfig.canvasSize.y : 500;
|
||||
this.ctx = this.initializeCanvas(this.GAME_CANVAS, this.WIDTH, this.HEIGHT);
|
||||
|
||||
// Size the viewport to the game canvas
|
||||
this.viewport = new Viewport();
|
||||
this.viewport.setCanvasSize(this.WIDTH, this.HEIGHT);
|
||||
this.viewport.setSize(this.WIDTH, this.HEIGHT);
|
||||
|
||||
// Initialize all necessary game subsystems
|
||||
|
@ -242,5 +243,5 @@ export default class GameLoop {
|
|||
}
|
||||
|
||||
class GameConfig {
|
||||
viewportSize: {x: number, y: number}
|
||||
canvasSize: {x: number, y: number}
|
||||
}
|
|
@ -82,5 +82,10 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
|
|||
return this.scene.getViewport().getOrigin().mult(this.layer.getParallax());
|
||||
}
|
||||
|
||||
getViewportScale(): number {
|
||||
return this.scene.getViewport().getZoomLevel();
|
||||
}
|
||||
|
||||
|
||||
abstract update(deltaT: number): void;
|
||||
}
|
|
@ -35,15 +35,16 @@ export default class Rect extends Graphic {
|
|||
|
||||
render(ctx: CanvasRenderingContext2D): void {
|
||||
let origin = this.getViewportOriginWithParallax();
|
||||
let zoom = this.getViewportScale();
|
||||
|
||||
if(this.color.a !== 0){
|
||||
ctx.fillStyle = this.color.toStringRGB();
|
||||
ctx.fillRect(this.position.x - this.size.x/2 - origin.x, this.position.y - this.size.y/2 - origin.y, this.size.x, this.size.y);
|
||||
ctx.fillRect((this.position.x - this.size.x/2 - origin.x)*zoom, (this.position.y - this.size.y/2 - origin.y)*zoom, this.size.x*zoom, this.size.y*zoom);
|
||||
}
|
||||
|
||||
ctx.strokeStyle = this.borderColor.toStringRGB();
|
||||
ctx.lineWidth = this.borderWidth;
|
||||
ctx.strokeRect(this.position.x - this.size.x/2 - origin.x, this.position.y - this.size.y/2 - origin.y, this.size.x, this.size.y);
|
||||
ctx.strokeRect((this.position.x - this.size.x/2 - origin.x)*zoom, (this.position.y - this.size.y/2 - origin.y)*zoom, this.size.x*zoom, this.size.y*zoom);
|
||||
}
|
||||
|
||||
}
|
|
@ -30,15 +30,16 @@ export default class Sprite extends CanvasNode {
|
|||
render(ctx: CanvasRenderingContext2D): void {
|
||||
let image = ResourceManager.getInstance().getImage(this.imageId);
|
||||
let origin = this.getViewportOriginWithParallax();
|
||||
let zoom = this.getViewportScale();
|
||||
|
||||
ctx.drawImage(image,
|
||||
this.imageOffset.x, this.imageOffset.y, this.size.x, this.size.y,
|
||||
this.position.x - origin.x - this.size.x*this.scale.x/2, this.position.y - origin.y - this.size.y*this.scale.y/2,
|
||||
this.size.x * this.scale.x, this.size.y * this.scale.y);
|
||||
(this.position.x - origin.x - this.size.x*this.scale.x/2)*zoom, (this.position.y - origin.y - this.size.y*this.scale.y/2)*zoom,
|
||||
this.size.x * this.scale.x*zoom, this.size.y * this.scale.y*zoom);
|
||||
|
||||
ctx.lineWidth = 4;
|
||||
ctx.strokeStyle = "#00FF00"
|
||||
let b = this.getBoundary();
|
||||
ctx.strokeRect(b.x - b.hw - origin.x, b.y - b.hh - origin.y, b.hw*2, b.hh*2);
|
||||
ctx.strokeRect(b.x - b.hw - origin.x, b.y - b.hh - origin.y, b.hw*2*zoom, b.hh*2*zoom);
|
||||
}
|
||||
}
|
|
@ -54,7 +54,9 @@ export default class SceneGraphQuadTree extends SceneGraph {
|
|||
}
|
||||
|
||||
render(ctx: CanvasRenderingContext2D): void {
|
||||
this.qt.render_demo(ctx);
|
||||
let origin = this.viewport.getOrigin();
|
||||
let zoom = this.viewport.getZoomLevel();
|
||||
this.qt.render_demo(ctx, origin, zoom);
|
||||
}
|
||||
|
||||
getVisibleSet(): Array<CanvasNode> {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import Vec2 from "../DataTypes/Vec2";
|
||||
import Vec4 from "../DataTypes/Vec4";
|
||||
import GameNode from "../Nodes/GameNode";
|
||||
import CanvasNode from "../Nodes/CanvasNode";
|
||||
import MathUtils from "../Utils/MathUtils";
|
||||
import Queue from "../DataTypes/Queue";
|
||||
import AABB from "../DataTypes/AABB";
|
||||
import Debug from "../Debug/Debug";
|
||||
import InputReceiver from "../Input/InputReceiver";
|
||||
|
||||
export default class Viewport {
|
||||
private view: AABB;
|
||||
|
@ -22,11 +22,21 @@ export default class Viewport {
|
|||
*/
|
||||
private smoothingFactor: number;
|
||||
|
||||
private scrollZoomEnabled: boolean;
|
||||
private ZOOM_FACTOR: number = 1.2;
|
||||
private canvasSize: Vec2;
|
||||
|
||||
constructor(){
|
||||
this.view = new AABB(Vec2.ZERO, Vec2.ZERO);
|
||||
this.boundary = new AABB(Vec2.ZERO, Vec2.ZERO);
|
||||
this.lastPositions = new Queue();
|
||||
this.smoothingFactor = 10;
|
||||
this.scrollZoomEnabled = false;
|
||||
this.canvasSize = Vec2.ZERO;
|
||||
}
|
||||
|
||||
enableZoom(): void {
|
||||
this.scrollZoomEnabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,6 +102,23 @@ export default class Viewport {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size of the canvas that the viewport is projecting to.
|
||||
* @param vecOrX
|
||||
* @param y
|
||||
*/
|
||||
setCanvasSize(vecOrX: Vec2 | number, y: number = null): void {
|
||||
if(vecOrX instanceof Vec2){
|
||||
this.canvasSize = vecOrX.clone();
|
||||
} else {
|
||||
this.canvasSize = new Vec2(vecOrX, y);
|
||||
}
|
||||
}
|
||||
|
||||
getZoomLevel(): number {
|
||||
return this.canvasSize.x/this.view.hw/2
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the smoothing factor for the viewport movement.
|
||||
* @param smoothingFactor The smoothing factor for the viewport
|
||||
|
@ -141,6 +168,37 @@ export default class Viewport {
|
|||
}
|
||||
|
||||
update(deltaT: number): void {
|
||||
// If zoom is enabled
|
||||
if(this.scrollZoomEnabled){
|
||||
let input = InputReceiver.getInstance();
|
||||
if(input.didJustScroll()){
|
||||
let currentSize = this.view.getHalfSize().clone();
|
||||
if(input.getScrollDirection() < 0){
|
||||
// Zoom in
|
||||
currentSize.scale(1/this.ZOOM_FACTOR);
|
||||
} else {
|
||||
// Zoom out
|
||||
currentSize.scale(this.ZOOM_FACTOR);
|
||||
}
|
||||
|
||||
if(currentSize.x > this.boundary.hw){
|
||||
let factor = this.boundary.hw/currentSize.x;
|
||||
currentSize.x = this.boundary.hw;
|
||||
currentSize.y *= factor;
|
||||
}
|
||||
|
||||
if(currentSize.y > this.boundary.hh){
|
||||
let factor = this.boundary.hh/currentSize.y;
|
||||
currentSize.y = this.boundary.hh;
|
||||
currentSize.x *= factor;
|
||||
}
|
||||
|
||||
this.view.setHalfSize(currentSize);
|
||||
}
|
||||
}
|
||||
|
||||
Debug.log("vpzoom", "View size: " + this.view.getHalfSize());
|
||||
|
||||
// If viewport is following an object
|
||||
if(this.following){
|
||||
// Update our list of previous positions
|
||||
|
|
|
@ -23,6 +23,7 @@ export default class Boid extends Graphic {
|
|||
|
||||
render(ctx: CanvasRenderingContext2D): void {
|
||||
let origin = this.getViewportOriginWithParallax();
|
||||
let zoom = this.getViewportScale();
|
||||
|
||||
let dirVec = this.direction.scaled(this.size.x, this.size.y);
|
||||
let finVec1 = this.direction.clone().rotateCCW(Math.PI/2).scale(this.size.x/2, this.size.y/2).sub(this.direction.scaled(this.size.x/1.5, this.size.y/1.5));
|
||||
|
@ -31,11 +32,11 @@ export default class Boid extends Graphic {
|
|||
ctx.lineWidth = 1;
|
||||
ctx.fillStyle = this.color.toString();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(this.position.x - origin.x + dirVec.x, this.position.y - origin.y + dirVec.y);
|
||||
ctx.lineTo(this.position.x - origin.x + finVec1.x, this.position.y - origin.y + finVec1.y);
|
||||
ctx.lineTo(this.position.x - origin.x - dirVec.x/3, this.position.y - origin.y - dirVec.y/3);
|
||||
ctx.lineTo(this.position.x - origin.x + finVec2.x, this.position.y - origin.y + finVec2.y);
|
||||
ctx.lineTo(this.position.x - origin.x + dirVec.x, this.position.y - origin.y + dirVec.y);
|
||||
ctx.moveTo((this.position.x - origin.x + dirVec.x)*zoom, (this.position.y - origin.y + dirVec.y)*zoom);
|
||||
ctx.lineTo((this.position.x - origin.x + finVec1.x)*zoom, (this.position.y - origin.y + finVec1.y)*zoom);
|
||||
ctx.lineTo((this.position.x - origin.x - dirVec.x/3)*zoom, (this.position.y - origin.y - dirVec.y/3)*zoom);
|
||||
ctx.lineTo((this.position.x - origin.x + finVec2.x)*zoom, (this.position.y - origin.y + finVec2.y)*zoom);
|
||||
ctx.lineTo((this.position.x - origin.x + dirVec.x)*zoom, (this.position.y - origin.y + dirVec.y)*zoom);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
|
@ -23,7 +23,6 @@ export default class RunAwayFromPlayer extends State {
|
|||
}
|
||||
|
||||
onEnter(): void {
|
||||
console.log("Entered Running away")
|
||||
this.runAwayDirection = Vec2.ZERO;
|
||||
this.lastPlayerPosition = Vec2.INF;
|
||||
this.timeElapsed = 0;
|
||||
|
|
|
@ -7,10 +7,10 @@ import MarioClone from "./_DemoClasses/MarioClone/MarioClone";
|
|||
|
||||
function main(){
|
||||
// Create the game object
|
||||
let game = new GameLoop({viewportSize: {x: 800, y: 600}});
|
||||
let game = new GameLoop({canvasSize: {x: 800, y: 600}});
|
||||
game.start();
|
||||
let sm = game.getSceneManager();
|
||||
sm.addScene(MarioClone);
|
||||
sm.addScene(BoidDemo);
|
||||
}
|
||||
|
||||
CanvasRenderingContext2D.prototype.roundedRect = function(x: number, y: number, w: number, h: number, r: number): void {
|
||||
|
|
Loading…
Reference in New Issue
Block a user