Created
June 17, 2023 19:27
-
-
Save paxperscientiam/f4ce993ea29be59c230798d840378ca5 to your computer and use it in GitHub Desktop.
Integrating ThreeJS objects with orthographic camera tracking
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as THREE from 'three' | |
window.THREE = THREE | |
import { GameObjects, Scene, Sound, Tilemaps, Geom, Math as PMath } from "phaser"; | |
import { Controller, Controls} from 'controls'; | |
import BaseLayer from "@layers/base" | |
import Player from "player"; | |
import { ShipsLayer } from "@layers/ships"; | |
import { UiScene } from "@scenes/ui-scene"; | |
import BaseShip from "ents/ships/base-ship"; | |
import { SimpleShip } from "ents/ships"; | |
import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; | |
import { CATEGORY_PLAYER, CATEGORY_SHIP, CATEGORY_WORLD_WALLS } from "collisions"; | |
import { Planet } from "planet"; | |
import { CelestialsLayer } from "@layers/celestials"; | |
import { ActiveTarget, ITargetedEventContext } from "dao.types"; | |
type worldDimensions = [number, number] | |
export class BaseSystemScene extends Scene { | |
canvas!: HTMLCanvasElement | |
player!: Player | |
bgimage!: GameObjects.TileSprite | |
playerShip!: BaseShip | |
tileMapKey!: string | |
sfxCannon!: Sound.HTML5AudioSound | |
sfxThrust!: Sound.HTML5AudioSound | |
sfxBackground!: Sound.HTML5AudioSound | |
sfxReverseThrust!: Sound.HTML5AudioSound | |
ship!: BaseShip | |
celestialLayer!: CelestialsLayer | |
shipLayer!: ShipsLayer | |
focusedShip!: BaseShip | |
settingTitle!: string | |
worldDimensions!: worldDimensions | |
worldWallThickness = 50 | |
tileMap!: Tilemaps.Tilemap | |
playerSpawn!: any | |
targetLine!: GameObjects.Graphics | |
shipExhaustEmitter!: Phaser.GameObjects.Particles.ParticleEmitter | |
shipPortForwardEmitter!: Phaser.GameObjects.Particles.ParticleEmitter | |
shipStarboardForwardEmitter!: Phaser.GameObjects.Particles.ParticleEmitter | |
controls!: Controller | |
preferred!: any | |
activeTarget!: null|ActiveTarget | |
constructor (sceneKey: string) { | |
super(sceneKey) | |
} | |
init(data: any) { | |
this.tileMapKey = data.tileMapKey | |
this.events.on("addedtoscene", (go: GameObjects.GameObject) => { | |
if (this.shipLayer && go instanceof BaseShip) { | |
this.shipLayer.add(go) | |
} | |
if (this.celestialLayer && go instanceof Planet) { | |
this.celestialLayer.add(go) | |
} | |
}) | |
// anywhere touch | |
// this.input.on(Phaser.Input.Events.POINTER_UP, (pointer: Phaser.Input.Pointer) => { | |
// const { worldX, worldY } = pointer | |
// }) | |
} | |
preload() { | |
this.load.tilemapTiledJSON(this.tileMapKey, `maps/${this.tileMapKey}.json`) | |
} | |
create() { | |
this.init3D(); | |
this.controls = (new Controls(this.input.keyboard!)).controller | |
this.preferred = (new Controls(this.input.keyboard!)).preferredKeys | |
this.targetLine = this.add.graphics() | |
this.targetLine.lineStyle(20, 0x00ff00, 1) | |
this.tileMap = this.add.tilemap(this.tileMapKey) | |
this.worldDimensions = [this.tileMap.widthInPixels, this.tileMap.heightInPixels] | |
this.shipLayer = new ShipsLayer(this) | |
this.celestialLayer = new CelestialsLayer(this) | |
this.shipLayer.setVisible(true) | |
this.sound.volume = 0.1 | |
this.canvas = this.sys.canvas | |
// | |
this.sfxThrust = <Sound.HTML5AudioSound>this.sound.add("SimpleShip.thrust") | |
this.player = (new Player(this)) | |
this.playerShip = new SimpleShip(this, 0, 0) | |
// @ts-ignore | |
window.ship = this.playerShip | |
this.shipPortForwardEmitter = this.add.particles(this.playerShip.x, this.playerShip.x, 'particles_1', { | |
scale: { start: 0.02, end: 0 }, | |
blendMode: "ADD", | |
lifespan: {min: 100, max: 200}, | |
quantity: 50, | |
frequency: 15, | |
speed: 500, | |
angle: { | |
onEmit: (() => { | |
return this.playerShip.angle + PMath.FloatBetween(0, 10) | |
}).bind(this) | |
}, | |
alpha: 0.05, | |
}) | |
this.shipPortForwardEmitter.stop() | |
this.shipStarboardForwardEmitter = this.add.particles(this.playerShip.x, this.playerShip.x, 'particles_1', { | |
scale: { start: 0.02, end: 0 }, | |
blendMode: "ADD", | |
lifespan: {min: 100, max: 200}, | |
quantity: 50, | |
frequency: 15, | |
speed: 500, | |
angle: { | |
onEmit: (() => { | |
return this.playerShip.angle + PMath.FloatBetween(-10, 0) | |
}).bind(this) | |
}, | |
alpha: 0.05, | |
}) | |
this.shipStarboardForwardEmitter.stop() | |
this.shipExhaustEmitter = this.add.particles(this.playerShip.x, this.playerShip.x, 'particles_1', { | |
scale: { start: 0.035, end: 0 }, | |
blendMode: "ADD", | |
lifespan: {min: 100, max: 200}, | |
quantity: 50, | |
frequency: 15, | |
speed: 500, | |
angle: { | |
onEmit: (() => { | |
return this.playerShip.angle - PMath.FloatBetween(170, 190) | |
}).bind(this) | |
}, | |
alpha: 0.2 | |
}) | |
this.shipExhaustEmitter.stop() | |
this.game.events.on("forwardJustUp", () => { | |
this.shipExhaustEmitter.stop() | |
}) | |
this.game.events.on("forwardJustDown", () => { | |
this.shipExhaustEmitter.start() | |
}) | |
this.game.events.on("backwardJustUp", () => { | |
this.shipPortForwardEmitter.stop() | |
this.shipStarboardForwardEmitter.stop() | |
}) | |
this.game.events.on("backwardJustDown", () => { | |
this.shipPortForwardEmitter.start() | |
this.shipStarboardForwardEmitter.start() | |
}) | |
this.player.provideShip(this.playerShip) | |
this.game.events | |
.on("forwardIsDown", () => { | |
this.player.ship.go() | |
}) | |
.on("backwardIsDown", () => { | |
this.player.ship.goBack() | |
}) | |
.on("leftIsDown", () => { | |
this.player.ship.rotateCCW() | |
}) | |
.on("rightIsDown", () => { | |
this.player.ship.rotateCW() | |
}) | |
.on("stopIsDown", () => { | |
this.player.ship.stop() // | |
}) | |
.on("actionDown", () => { | |
if (true === this.player.ship.shoot()) { | |
this.sound.play("SimpleShip.shoot", { | |
loop: false, | |
volume: 0.1, | |
}) | |
} | |
}) | |
// UI | |
.on("zoomInDown", () => { | |
this.cameras.main.zoom = Phaser.Math.Clamp(this.cameras.main.zoom - 0.1, 0.1, 2) | |
}) | |
.on("zoomOutDown", () => { | |
this.cameras.main.zoom = Phaser.Math.Clamp(this.cameras.main.zoom + 0.1, 0.1, 2) | |
}) | |
.on("zoomResetDown", () => { | |
this.cameras.main.zoom = 1 | |
}) | |
.on("toggleInterface", () => { | |
(this.scene.get('UiScene') as UiScene).toggleDisplay() | |
}) | |
this.game.events | |
.on("forwardJustDown", () => { | |
if (this.sfxThrust) { | |
SoundFade.fadeIn(this.sfxThrust, 1000, 1, this.sfxThrust.volume) | |
} | |
}) | |
.on("forwardJustUp", () => { | |
if (this.sfxThrust) { | |
SoundFade.fadeOut(this.sfxThrust, 2000, false) | |
} | |
}) | |
.on("toggleDebugOverlay", () => { | |
// | |
}) | |
.on("toggleDebugOverlay2", () => { | |
// @ts-ignore | |
//this.debugDraw.toggle() | |
}) | |
this.game.events.on("hyperJumpJustDown", () => { | |
// extend the bounds to allow to fly out of camera view during hyper jump | |
this.matter.world.setBounds(0, 0, 2 * this.worldDimensions[0], 2* this.worldDimensions[1]) | |
this.playerShip.jump() | |
}) | |
this.game.events.on("modifierKeyOnePointerUp", (pointer: Phaser.Input.Pointer) => { | |
console.log(pointer) | |
}) | |
this.game.events.on("unmodifiedPointerUp", ((pointer: Phaser.Input.Pointer) => { | |
const {x, y} = this.cameras.main.getWorldPoint(pointer.position.x, pointer.position.y) | |
this.player.ship.faceThing({x, y}) | |
}).bind(<Phaser.Scene>this)) | |
this.game.events.on("toggleViewMap", (() => { | |
console.log("toggle map view") | |
}).bind(<Phaser.Scene>this)) | |
this.matter.world.setBounds(0, 0, this.worldDimensions[0], this.worldDimensions[1], this.worldWallThickness, true, true, true, true) | |
for (const wall of Object.values(this.matter.world.walls)) { | |
wall.collisionFilter.category = CATEGORY_WORLD_WALLS | |
wall.collisionFilter.mask = CATEGORY_SHIP|CATEGORY_PLAYER | |
} | |
window.DEBUG && (this.matter.world.drawDebug = true) | |
this.playerSpawn = this.tileMap.findObject("Player", (object) => { | |
return object.name == "PlayerSpawn" | |
}) | |
this.tileMap.createFromObjects("GameObjects/Ships", { | |
name: "SimpleShip", | |
scene: this, | |
key: "ship", | |
classType: SimpleShip, | |
}) | |
// this.tileMap.createFromObjects("GameObjects/Celestials", { // 2 | |
// name: "Planet", | |
// scene: this, | |
// key: "planet", | |
// classType: Planet, | |
// }) | |
this.player.ship.setPosition(this.playerSpawn.x, this.playerSpawn.y) | |
this.cameras.main.setDeadzone(this.canvas.width / 5, this.canvas.height / 5) | |
// the multiplier is so the player doesn't collide with edge of screen | |
this.cameras.main.setBounds( | |
-1*this.worldWallThickness/2, | |
-1*this.worldWallThickness/2, | |
this.worldWallThickness + this.worldDimensions[0], | |
this.worldWallThickness + this.worldDimensions[1], | |
true | |
) | |
this.cameras.main.setOrigin(0, 0) | |
this.cameras.main.startFollow(this.player.ship, true, 0.5, 0.5) | |
this.sfxBackground = <Sound.HTML5AudioSound>this.sound.add("sfxBackground", { loop: true, volume: 1}); | |
if (!this.sound.locked) | |
{ | |
// already unlocked so play | |
this.sfxBackground.play() | |
} | |
else | |
{ | |
// wait for 'unlocked' to fire and then play | |
this.sound.once(Phaser.Sound.Events.UNLOCKED, () => { | |
// // play music | |
this.sfxBackground.play({ | |
volume: 0.5 | |
}) | |
}) | |
} | |
this.game.events | |
.on("cyclerOneJustDown", () => { | |
/* | |
1. get active target | |
2. get layer of target | |
3. cycle forward | |
*/ | |
if (null == this.activeTarget) { | |
if (null == this.playerShip.sensorMode) { | |
this.playerShip.setSensorMode(this.playerShip.targetLayers.cycleForward()[0]) | |
} | |
this.activeTarget = <SimpleShip|Planet>this.shipLayer.list[0] | |
this.playerShip.setSensorMode(this.shipLayer.name) | |
this.game.events.emit("TARGET_POINTER_DOWN", <ITargetedEventContext>{ | |
target: this.activeTarget, | |
layer: { | |
position: 0, | |
sensorMode: this.shipLayer.name | |
} | |
}) | |
return | |
} | |
if ("Layer" === (<BaseLayer>this.activeTarget.displayList).type) { | |
const layer = <BaseLayer>this.activeTarget.displayList | |
const layerIndex = layer.getIndex(this.activeTarget) | |
if (-1 == layerIndex) return | |
const [next] = layer.cycleForward() | |
this.activeTarget = next | |
this.playerShip.setSensorMode(layer.name) | |
this.game.events.emit("TARGET_POINTER_DOWN", <ITargetedEventContext>{ | |
target: next, | |
layer: { | |
position: layerIndex, | |
sensorMode: layer.name | |
} | |
}) | |
} else { | |
console.log("what do?") | |
} | |
}) | |
.on("cyclerTwoJustDown", () => { | |
console.info("cycle backwards") | |
}) | |
this.game.events.on("pause", () => { | |
this.sfxBackground.pause() | |
}) | |
this.game.events.on("resume", () => { | |
this.sfxBackground.resume() | |
}) | |
this.game.events.on("removeTarget", () => { | |
this.player.ship.setScannerActive(false) | |
}) | |
this.game.events.on("toggleTargetModeJustDown", () => { | |
const [next] = this.playerShip.targetLayers.cycleForward() | |
this.game.events.emit("TARGET_POINTER_DOWN", <ITargetedEventContext>{ | |
target: null, | |
layer: { | |
sensorMode: next, | |
position: 0 | |
} | |
}) | |
}) | |
this.bgimage = this.add.tileSprite(0, 0, this.scale.width, this.scale.height, "star-field-bg") | |
.setDepth(-1) | |
.setOrigin(0, 0) | |
.setScrollFactor(0, 0) | |
.setAlpha(0.8) | |
// launch UI last | |
this.scene.launch('UiScene', this) | |
this.player.ship.setScannerActive() | |
this.game.events.on("TARGET_POINTER_DOWN", (ctx: ITargetedEventContext) => { | |
console.log(ctx) | |
this.activeTarget = ctx.target | |
// @ts-ignore | |
this.playerShip.setSensorMode(ctx.layer.sensorMode) | |
this.playerShip.setScannerActive(true) | |
}) | |
} | |
override update() { | |
this.targetLine.clear() | |
if (true === this.player?.ship?.isScannerActive && null != this.activeTarget) { | |
this.targetLine.setVisible(true) // 3 | |
if (this.activeTarget.body) { | |
// @ts-ignore | |
Geom.Intersects.GetLineToRectangle( | |
new Geom.Line(this.player.ship.x, this.player.ship.y, this.activeTarget.body.position.x, this.activeTarget.body.position.y), | |
this.cameras.main.getBounds(), | |
) | |
this.targetLine.lineBetween(this.player.ship.x, this.player.ship.y, this.activeTarget.x, this.activeTarget.y) | |
} | |
} else { | |
this.targetLine.setVisible(false) | |
} | |
this.shipExhaustEmitter.setPosition( | |
this.playerShip.x + -1 * Math.cos(this.playerShip.body.angle) * this.playerShip.displayWidth / 2, | |
this.playerShip.y + -1 * Math.sin(this.playerShip.body.angle) * this.playerShip.displayHeight / 2 | |
) | |
const emitterPosOffset = <PMath.Vector2>Phaser.Math.TransformXY( | |
this.playerShip.displayWidth / 4, | |
this.playerShip.displayHeight / 4, | |
0,0,-1*this.playerShip.body.angle,1,1) | |
this.shipPortForwardEmitter.setPosition( | |
this.playerShip.x + emitterPosOffset.x, | |
this.playerShip.y + emitterPosOffset.y | |
) | |
const emitterPosOffset2 = <PMath.Vector2>Phaser.Math.TransformXY( | |
this.playerShip.displayWidth / 4, | |
-1 * this.playerShip.displayHeight / 4, | |
0,0,-1*this.playerShip.body.angle,1,1) | |
this.shipStarboardForwardEmitter.setPosition( | |
this.playerShip.x + emitterPosOffset2.x, | |
this.playerShip.y + emitterPosOffset2.y | |
) | |
const { scrollX, scrollY, zoom } = this.cameras.main | |
const f = 0.1 | |
this.bgimage.setTilePosition(scrollX * zoom * f, scrollY * zoom * f) | |
} | |
init3D() { | |
const camera = new THREE.OrthographicCamera( | |
this.scale.width / -2, | |
this.scale.width / 2, | |
this.scale.height / 2, | |
this.scale.height / -2, | |
1, | |
200 | |
); | |
camera.zoom = 1 | |
const camHelper = new THREE.CameraHelper( camera ); | |
window.DEBUG && camHelper.setColors(new THREE.Color( 'skyblue' ), new THREE.Color( 'skyblue' ), new THREE.Color( 'skyblue' ), new THREE.Color( 'skyblue' ), new THREE.Color( 'skyblue' )) | |
camera.position.set(0, 0, 200) | |
const scene = new THREE.Scene(); | |
scene.add(camHelper) | |
const geometry2 = new THREE.SphereGeometry(50, 32, 16); | |
const texture = new THREE.TextureLoader().load("assets/images/cosmos/systems/sol/adyo.jpg") | |
const material : THREE.Material = new THREE.MeshStandardMaterial({ | |
map: texture | |
}); | |
const planetMesh = new THREE.Mesh(geometry2, material) | |
planetMesh.castShadow = true | |
scene.add(planetMesh) | |
const box = new THREE.BoxHelper(planetMesh, 0xffff00 ); | |
scene.add( box ); | |
const ambientLight : THREE.AmbientLight = new THREE.AmbientLight('#ffffff', 0.001); | |
scene.add(ambientLight); | |
const spotLight : THREE.SpotLight = new THREE.SpotLight(0xffffff, 1, 0, 0.4, 0.5, 0.1); | |
spotLight.position.set(this.cameras.main.width * 0.1, this.cameras.main.height * 1, 0); | |
spotLight.castShadow = true; | |
spotLight.shadow.mapSize.width = 512; | |
spotLight.shadow.mapSize.height = 512; | |
spotLight.shadow.camera.near = 1; | |
spotLight.shadow.camera.far = 10000; | |
scene.add(spotLight); | |
window.DEBUG && scene.add(new THREE.GridHelper(40, 400, new THREE.Color( 'skyblue' ), new THREE.Color( 'skyblue' ))); | |
// some snippet,i forget the source | |
const renderer = new THREE.WebGL1Renderer({ | |
canvas: this.sys.game.canvas, | |
// @ts-ignore | |
context: this.sys.game.context, | |
antialias: true, | |
}); | |
// Create the Phaser Extern, tells Phaser to hand-off rendering to ThreeJS | |
const view = this.add.extern(); | |
renderer.setPixelRatio(1); | |
renderer.autoClear = false; // <=== very important | |
// @ts-ignore | |
view.render = () => { | |
// This is essential to get ThreeJS to reset the GL state | |
renderer.resetState(); | |
planetMesh.rotation.x -= 0.0005; | |
planetMesh.rotation.y += 0.000002; | |
camera.position.x = this.cameras.main.midPoint.x - 4000 | |
camera.position.y = 4000 - this.cameras.main.midPoint.y | |
renderer.render(scene, camera); | |
// Call it again, after rendering, if you get graphical corruption | |
renderer.resetState(); | |
}; | |
// @ts-ignore | |
window.planet = planetMesh | |
// @ts-ignore | |
window.threeview = view | |
// @ts-ignore | |
window.threecam = camera | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is a complete scene file from a Phaser project I'm working on. I had wanted to integrate 3D objects from Threejs to introduce cool lighting effects.
While I decided not to go this route, mainly because I didn't want to figure out how to make placement of these objects work with Tiled for positioning, I figure it would be nice to share some code for others who might want to try it out.
This code is amalgam of existing official examples and stuff learned from random blog posts.
Obviously, this isn't code one could merely drop into their own project; however, the
init3D
method nearly is. Enjoy!