From 1359fbe97768c485c033eecd6e588222e836f931 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Wed, 6 Jan 2021 15:00:54 +0100 Subject: [PATCH 1/3] updated phaser to version 3.51 and fixed the BC --- front/src/Phaser/Entity/Character.ts | 4 ++-- front/src/Phaser/Entity/RemotePlayer.ts | 3 ++- front/src/Phaser/Entity/SpeechBubble.ts | 2 +- front/src/Phaser/Game/GameScene.ts | 10 +++++----- front/src/Phaser/Items/ActionableItem.ts | 3 +-- front/src/Phaser/Shaders/OutlinePipeline.ts | 4 +--- front/src/index.ts | 2 +- front/yarn.lock | 18 +++++++++--------- 8 files changed, 22 insertions(+), 24 deletions(-) diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index 5c89447e..37b55be2 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -227,7 +227,7 @@ export abstract class Character extends Container { }, 3000) } - destroy(fromScene?: boolean): void { + destroy(): void { if (this.scene) { this.scene.events.removeListener('postupdate', this.postupdate.bind(this)); } @@ -236,7 +236,7 @@ export abstract class Character extends Container { this.scene.sys.updateList.remove(sprite); } } - super.destroy(fromScene); + super.destroy(); this.playerName.destroy(); } } diff --git a/front/src/Phaser/Entity/RemotePlayer.ts b/front/src/Phaser/Entity/RemotePlayer.ts index 08f657d4..54592389 100644 --- a/front/src/Phaser/Entity/RemotePlayer.ts +++ b/front/src/Phaser/Entity/RemotePlayer.ts @@ -29,6 +29,7 @@ export class RemotePlayer extends Character { this.playAnimation(position.direction, position.moving); this.setX(position.x); this.setY(position.y); - this.setDepth(position.y); + + this.setDepth(position.y); //this is to make sure the perspective (player models closer the bottom of the screen will appear in front of models nearer the top of the screen). } } diff --git a/front/src/Phaser/Entity/SpeechBubble.ts b/front/src/Phaser/Entity/SpeechBubble.ts index 30518890..5dc43fe0 100644 --- a/front/src/Phaser/Entity/SpeechBubble.ts +++ b/front/src/Phaser/Entity/SpeechBubble.ts @@ -52,7 +52,7 @@ export class SpeechBubble { this.bubble.lineBetween(point2X, point2Y, point3X, point3Y); this.bubble.lineBetween(point1X, point1Y, point3X, point3Y); - this.content = scene.add.text(0, 0, text, { fontFamily: 'Arial', fontSize: 20, color: '#000000', align: 'center', wordWrap: { width: bubbleWidth - (bubblePadding * 2) } }); + this.content = scene.add.text(0, 0, text, { fontFamily: 'Arial', fontSize: '20', color: '#000000', align: 'center', wordWrap: { width: bubbleWidth - (bubblePadding * 2) } }); const bounds = this.content.getBounds(); this.content.setPosition(this.bubble.x + (bubbleWidth / 2) - (bounds.width / 2), this.bubble.y + (bubbleHeight / 2) - (bounds.height / 2)); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index a8206328..99b002de 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -110,7 +110,7 @@ export class GameScene extends ResizableScene implements CenterListener { MapPlayers!: Phaser.Physics.Arcade.Group; MapPlayersByKey : Map = new Map(); Map!: Phaser.Tilemaps.Tilemap; - Layers!: Array; + Layers!: Array; Objects!: Array; mapFile!: ITiledMap; groups: Map; @@ -342,11 +342,11 @@ export class GameScene extends ResizableScene implements CenterListener { this.physics.world.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels); //add layer on map - this.Layers = new Array(); + this.Layers = new Array(); let depth = -2; for (const layer of this.mapFile.layers) { if (layer.type === 'tilelayer') { - this.addLayer(this.Map.createStaticLayer(layer.name, this.Terrains, 0, 0).setDepth(depth)); + this.addLayer(this.Map.createLayer(layer.name, this.Terrains, 0, 0).setDepth(depth)); const exitSceneUrl = this.getExitSceneUrl(layer); if (exitSceneUrl !== undefined) { @@ -824,13 +824,13 @@ export class GameScene extends ResizableScene implements CenterListener { this.cameras.main.setZoom(ZOOM_LEVEL); } - addLayer(Layer : Phaser.Tilemaps.StaticTilemapLayer){ + addLayer(Layer : Phaser.Tilemaps.TilemapLayer){ this.Layers.push(Layer); } createCollisionWithPlayer() { //add collision layer - this.Layers.forEach((Layer: Phaser.Tilemaps.StaticTilemapLayer) => { + this.Layers.forEach((Layer: Phaser.Tilemaps.TilemapLayer) => { this.physics.add.collider(this.CurrentPlayer, Layer, (object1: GameObject, object2: GameObject) => { //this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name) }); diff --git a/front/src/Phaser/Items/ActionableItem.ts b/front/src/Phaser/Items/ActionableItem.ts index 36b14921..a1548f41 100644 --- a/front/src/Phaser/Items/ActionableItem.ts +++ b/front/src/Phaser/Items/ActionableItem.ts @@ -43,8 +43,7 @@ export class ActionableItem { } this.isSelectable = true; this.sprite.setPipeline(OutlinePipeline.KEY); - this.sprite.pipeline.setFloat2('uTextureSize', - this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height); + this.sprite.pipeline.set2f('uTextureSize', this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height); } /** diff --git a/front/src/Phaser/Shaders/OutlinePipeline.ts b/front/src/Phaser/Shaders/OutlinePipeline.ts index 6b416b8a..0d074bc3 100644 --- a/front/src/Phaser/Shaders/OutlinePipeline.ts +++ b/front/src/Phaser/Shaders/OutlinePipeline.ts @@ -1,5 +1,4 @@ -export class OutlinePipeline extends Phaser.Renderer.WebGL.Pipelines.TextureTintPipeline -{ +export class OutlinePipeline extends Phaser.Renderer.WebGL.Pipelines.MultiPipeline { // the unique id of this pipeline public static readonly KEY = 'Outline'; @@ -11,7 +10,6 @@ export class OutlinePipeline extends Phaser.Renderer.WebGL.Pipelines.TextureTint { super({ game: game, - renderer: game.renderer, fragShader: ` precision mediump float; diff --git a/front/src/index.ts b/front/src/index.ts index c8783b90..46d38a3b 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -75,7 +75,7 @@ const config: GameConfig = { postBoot: game => { // FIXME: we should fore WebGL in the config. const renderer = game.renderer as WebGLRenderer; - renderer.addPipeline(OutlinePipeline.KEY, new OutlinePipeline(game)); + renderer.pipelines.add(OutlinePipeline.KEY, new OutlinePipeline(game)); } } }; diff --git a/front/yarn.lock b/front/yarn.lock index 02b6052b..a31409a6 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -1771,7 +1771,7 @@ eventemitter3@^2.0.3: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo= -eventemitter3@^4.0.0, eventemitter3@^4.0.4: +eventemitter3@^4.0.0, eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -1829,7 +1829,7 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" -exports-loader@^1.1.0: +exports-loader@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/exports-loader/-/exports-loader-1.1.1.tgz#88c9a6877ee6a5519d7c41a016bdd99148421e69" integrity sha512-CmyhIR2sJ3KOfVsHjsR0Yvo+0lhRhRMAevCbB8dhTVLHsZPs0lCQTvRmR9YNvBXDBxUuhmCE2f54KqEjZUaFrg== @@ -2528,7 +2528,7 @@ import-local@^2.0.0: pkg-dir "^3.0.0" resolve-cwd "^2.0.0" -imports-loader@^1.1.0: +imports-loader@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-1.2.0.tgz#b06823d0bb42e6f5ff89bc893829000eda46693f" integrity sha512-zPvangKEgrrPeqeUqH0Uhc59YqK07JqZBi9a9cQ3v/EKUIqrbJHY4CvUrDus2lgQa5AmPyXuGrWP8JJTqzE5RQ== @@ -3664,13 +3664,13 @@ pbkdf2@^3.0.3: sha.js "^2.4.8" phaser@^3.22.0: - version "3.24.1" - resolved "https://registry.yarnpkg.com/phaser/-/phaser-3.24.1.tgz#376e0c965d2a35af37c06ee78627dafbde5be017" - integrity sha512-WbrRMkbpEzarkfrq83akeauc6b8xNxsOTpDygyW7wrU2G2ne6kOYu3hji4UAaGnZaOLrVuj8ycYPjX9P1LxcDw== + version "3.51.0" + resolved "https://registry.yarnpkg.com/phaser/-/phaser-3.51.0.tgz#b0c7ee2b21e795830d74f476dd30816a42b023bd" + integrity sha512-Z7XNToZWO60Zx/YetaoeGSeELy5ND45TPPfYB9HtQU2692ACXc/nioQaWp20NzTMgeBsgl6vYf3CI82y/DzSyg== dependencies: - eventemitter3 "^4.0.4" - exports-loader "^1.1.0" - imports-loader "^1.1.0" + eventemitter3 "^4.0.7" + exports-loader "^1.1.1" + imports-loader "^1.2.0" path "^0.12.7" picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: From fbb44af369e80b37b83905098143f24f6a4f3132 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Wed, 6 Jan 2021 17:08:48 +0100 Subject: [PATCH 2/3] added a basic loader --- front/src/Phaser/Game/GameScene.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 99b002de..1357f6d8 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -180,6 +180,8 @@ export class GameScene extends ResizableScene implements CenterListener { //hook preload scene preload(): void { + this.initProgressBar(); + this.load.image(openChatIconName, 'resources/objects/talk.png'); this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => { this.scene.start(FourOFourSceneName, { @@ -206,6 +208,16 @@ export class GameScene extends ResizableScene implements CenterListener { this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); } + private initProgressBar(): void { + const progress = this.add.graphics(); + this.load.on('progress', (value: number) => { + progress.clear(); + progress.fillStyle(0xffffff, 1); + progress.fillRect(0, 270, 800 * value, 60); + }); + this.load.on('complete', () => progress.destroy()); + } + // FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving. // eslint-disable-next-line @typescript-eslint/no-explicit-any private async onMapLoad(data: any): Promise { From 3ce842737844ab51a181ecc66c772a258cdb4811 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Thu, 7 Jan 2021 12:43:21 +0100 Subject: [PATCH 3/3] fixed a game crashed because of lack of animations and improved the character class --- front/src/Phaser/Entity/Character.ts | 26 +------ front/src/Phaser/Entity/SpeechBubble.ts | 29 +------- front/src/Phaser/Game/GameScene.ts | 97 ++++++++++++++----------- 3 files changed, 60 insertions(+), 92 deletions(-) diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index 37b55be2..6df6e1a4 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -59,6 +59,7 @@ export abstract class Character extends Container { frame?: string | number ) { super(scene, x, y/*, texture, frame*/); + this.PlayerValue = name; this.sprites = new Map(); @@ -73,10 +74,9 @@ export abstract class Character extends Container { }); this.add(this.teleportation);*/ - this.PlayerValue = name; - this.playerName = new BitmapText(scene, x, y - 25, 'main_font', name, 7); + this.playerName = new BitmapText(scene, 0, - 25, 'main_font', name, 7); this.playerName.setOrigin(0.5).setCenterAlign().setDepth(99999); - scene.add.existing(this.playerName); + this.add(this.playerName); scene.add.existing(this); @@ -88,8 +88,6 @@ export abstract class Character extends Container { this.getBody().setOffset(0, 8); this.setDepth(-1); - this.scene.events.on('postupdate', this.postupdate.bind(this)); - this.playAnimation(direction, moving); } @@ -181,35 +179,21 @@ export abstract class Character extends Container { if (body.velocity.y < 0) { //moving up this.lastDirection = PlayerAnimationNames.WalkUp; this.playAnimation(PlayerAnimationNames.WalkUp, true); - //this.play(`${this.PlayerTexture}-${PlayerAnimationNames.WalkUp}`, true); } else if (body.velocity.y > 0) { //moving down this.lastDirection = PlayerAnimationNames.WalkDown; this.playAnimation(PlayerAnimationNames.WalkDown, true); - //this.play(`${this.PlayerTexture}-${PlayerAnimationNames.WalkDown}`, true); } else if (body.velocity.x > 0) { //moving right this.lastDirection = PlayerAnimationNames.WalkRight; this.playAnimation(PlayerAnimationNames.WalkRight, true); - //this.play(`${this.PlayerTexture}-${PlayerAnimationNames.WalkRight}`, true); } else if (body.velocity.x < 0) { //moving left this.lastDirection = PlayerAnimationNames.WalkLeft; this.playAnimation(PlayerAnimationNames.WalkLeft, true); - //this.anims.playReverse(`${this.PlayerTexture}-${PlayerAnimationNames.WalkLeft}`, true); - } - - //todo:remove this, use a container tech to move the bubble instead - if (this.bubble) { - this.bubble.moveBubble(this.x, this.y); } //update depth user this.setDepth(this.y); } - postupdate(time: number, delta: number) { - //super.update(delta); - this.playerName.setPosition(this.x, this.y - 25); - } - stop(){ this.getBody().setVelocity(0, 0); this.playAnimation(this.lastDirection, false); @@ -218,7 +202,6 @@ export abstract class Character extends Container { say(text: string) { if (this.bubble) return; this.bubble = new SpeechBubble(this.scene, this, text) - //todo make the bubble destroy on player movement? setTimeout(() => { if (this.bubble !== null) { this.bubble.destroy(); @@ -228,9 +211,6 @@ export abstract class Character extends Container { } destroy(): void { - if (this.scene) { - this.scene.events.removeListener('postupdate', this.postupdate.bind(this)); - } for (const sprite of this.sprites.values()) { if(this.scene) { this.scene.sys.updateList.remove(sprite); diff --git a/front/src/Phaser/Entity/SpeechBubble.ts b/front/src/Phaser/Entity/SpeechBubble.ts index 5dc43fe0..06a64bd4 100644 --- a/front/src/Phaser/Entity/SpeechBubble.ts +++ b/front/src/Phaser/Entity/SpeechBubble.ts @@ -1,16 +1,12 @@ import Scene = Phaser.Scene; import {Character} from "./Character"; +//todo: improve this WIP export class SpeechBubble { private bubble: Phaser.GameObjects.Graphics; private content: Phaser.GameObjects.Text; - /** - * - * @param scene - * @param player - * @param text - */ + constructor(scene: Scene, player: Character, text: string = "") { const bubbleHeight = 50; @@ -19,6 +15,7 @@ export class SpeechBubble { const arrowHeight = bubbleHeight / 4; this.bubble = scene.add.graphics({ x: player.x + 16, y: player.y - 80 }); + player.add(this.bubble); // Bubble shadow this.bubble.fillStyle(0x222222, 0.5); @@ -53,30 +50,12 @@ export class SpeechBubble { this.bubble.lineBetween(point1X, point1Y, point3X, point3Y); this.content = scene.add.text(0, 0, text, { fontFamily: 'Arial', fontSize: '20', color: '#000000', align: 'center', wordWrap: { width: bubbleWidth - (bubblePadding * 2) } }); + player.add(this.content); const bounds = this.content.getBounds(); this.content.setPosition(this.bubble.x + (bubbleWidth / 2) - (bounds.width / 2), this.bubble.y + (bubbleHeight / 2) - (bounds.height / 2)); } - /** - * - * @param x - * @param y - */ - moveBubble(x : number, y : number) { - if (this.bubble) { - this.bubble.setPosition((x + 16), (y - 80)); - } - if (this.content) { - const bubbleHeight = 50; - const bubblePadding = 10; - const bubbleWidth = bubblePadding * 2 + this.content.text.length * 10; - const bounds = this.content.getBounds(); - //this.content.setPosition(x, y); - this.content.setPosition(this.bubble.x + (bubbleWidth / 2) - (bounds.width / 2), this.bubble.y + (bubbleHeight / 2) - (bounds.height / 2)); - } - } - destroy(): void { this.bubble.setVisible(false) //todo find a better way this.bubble.destroy(); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 1357f6d8..181592e2 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -66,6 +66,7 @@ import {ChatModeIcon} from "../Components/ChatModeIcon"; import {OpenChatIcon, openChatIconName} from "../Components/OpenChatIcon"; import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCharacterScene"; import {TextureError} from "../../Exception/TextureError"; +import {TextField} from "../Components/TextField"; export interface GameSceneInitInterface { initPosition: PointInterface|null, @@ -209,13 +210,17 @@ export class GameScene extends ResizableScene implements CenterListener { } private initProgressBar(): void { + const loadingText = this.add.text(this.game.renderer.width / 2, 200, 'Loading'); const progress = this.add.graphics(); this.load.on('progress', (value: number) => { progress.clear(); progress.fillStyle(0xffffff, 1); progress.fillRect(0, 270, 800 * value, 60); }); - this.load.on('complete', () => progress.destroy()); + this.load.on('complete', () => { + loadingText.destroy(); + progress.destroy(); + }); } // FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving. @@ -391,40 +396,11 @@ export class GameScene extends ResizableScene implements CenterListener { //notify game manager can to create currentUser in map this.createCurrentPlayer(); - - //initialise camera + this.removeAllRemotePlayers(); //cleanup the list of remote players in case the scene was rebooted + this.initCamera(); - // Let's generate the circle for the group delimiter - let circleElement = Object.values(this.textures.list).find((object: Texture) => object.key === 'circleSprite-white'); - if (circleElement) { - this.textures.remove('circleSprite-white'); - } - - circleElement = Object.values(this.textures.list).find((object: Texture) => object.key === 'circleSprite-red'); - if (circleElement) { - this.textures.remove('circleSprite-red'); - } - - //create white circle canvas use to create sprite - this.circleTexture = this.textures.createCanvas('circleSprite-white', 96, 96); - const context = this.circleTexture.context; - context.beginPath(); - context.arc(48, 48, 48, 0, 2 * Math.PI, false); - // context.lineWidth = 5; - context.strokeStyle = '#ffffff'; - context.stroke(); - this.circleTexture.refresh(); - - //create red circle canvas use to create sprite - this.circleRedTexture = this.textures.createCanvas('circleSprite-red', 96, 96); - const contextRed = this.circleRedTexture.context; - contextRed.beginPath(); - contextRed.arc(48, 48, 48, 0, 2 * Math.PI, false); - // context.lineWidth = 5; - contextRed.strokeStyle = '#ff0000'; - contextRed.stroke(); - this.circleRedTexture.refresh(); + this.initCirclesCanvas(); // Let's pause the scene if the connection is not established yet if (this.isReconnecting) { @@ -606,6 +582,40 @@ export class GameScene extends ResizableScene implements CenterListener { }); } + //todo: into dedicated classes + private initCirclesCanvas(): void { + // Let's generate the circle for the group delimiter + let circleElement = Object.values(this.textures.list).find((object: Texture) => object.key === 'circleSprite-white'); + if (circleElement) { + this.textures.remove('circleSprite-white'); + } + + circleElement = Object.values(this.textures.list).find((object: Texture) => object.key === 'circleSprite-red'); + if (circleElement) { + this.textures.remove('circleSprite-red'); + } + + //create white circle canvas use to create sprite + this.circleTexture = this.textures.createCanvas('circleSprite-white', 96, 96); + const context = this.circleTexture.context; + context.beginPath(); + context.arc(48, 48, 48, 0, 2 * Math.PI, false); + // context.lineWidth = 5; + context.strokeStyle = '#ffffff'; + context.stroke(); + this.circleTexture.refresh(); + + //create red circle canvas use to create sprite + this.circleRedTexture = this.textures.createCanvas('circleSprite-red', 96, 96); + const contextRed = this.circleRedTexture.context; + contextRed.beginPath(); + contextRed.arc(48, 48, 48, 0, 2 * Math.PI, false); + // context.lineWidth = 5; + contextRed.strokeStyle = '#ff0000'; + contextRed.stroke(); + this.circleRedTexture.refresh(); + } + private playAudio(url: string|number|boolean|undefined, loop=false): void { if (url === undefined) { audioManager.unloadAudio(); @@ -717,6 +727,14 @@ export class GameScene extends ResizableScene implements CenterListener { } } + private removeAllRemotePlayers(): void { + this.MapPlayersByKey.forEach((player: RemotePlayer) => { + player.destroy(); + this.MapPlayers.remove(player); + }); + this.MapPlayersByKey = new Map(); + } + private switchLayoutMode(): void { //if discussion is activated, this layout cannot be activated if(mediaManager.activatedDiscussion){ @@ -1024,14 +1042,7 @@ export class GameScene extends ResizableScene implements CenterListener { */ private doInitUsersPosition(usersPosition: MessageUserPositionInterface[]): void { const currentPlayerId = this.connection.getUserId(); - - // clean map - this.MapPlayersByKey.forEach((player: RemotePlayer) => { - player.destroy(); - this.MapPlayers.remove(player); - }); - this.MapPlayersByKey = new Map(); - + this.removeAllRemotePlayers(); // load map usersPosition.forEach((userPosition : MessageUserPositionInterface) => { if(userPosition.userId === currentPlayerId){ @@ -1224,9 +1235,7 @@ export class GameScene extends ResizableScene implements CenterListener { // Let's put this in Game coordinates by applying the zoom level: xCenter /= ZOOM_LEVEL * RESOLUTION; yCenter /= ZOOM_LEVEL * RESOLUTION; - - //console.log("updateCameraOffset", array, xCenter, yCenter, this.game.renderer.width, this.game.renderer.height); - + this.cameras.main.startFollow(this.CurrentPlayer, true, 1, 1, xCenter - this.game.renderer.width / 2, yCenter - this.game.renderer.height / 2); }