Skip to content

Instantly share code, notes, and snippets.

@paxperscientiam
Created June 17, 2023 19:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save paxperscientiam/f4ce993ea29be59c230798d840378ca5 to your computer and use it in GitHub Desktop.
Save paxperscientiam/f4ce993ea29be59c230798d840378ca5 to your computer and use it in GitHub Desktop.
Integrating ThreeJS objects with orthographic camera tracking
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
}
}
@paxperscientiam
Copy link
Author

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment