added viewport zoom

This commit is contained in:
Joe Weaver 2020-10-18 18:34:13 -04:00
parent 7a0f9e5c95
commit d9a87b2727
14 changed files with 147 additions and 26 deletions

View File

@ -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++){

View File

@ -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);
}
}

View File

@ -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: {}
*/

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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> {

View File

@ -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

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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 {