Skip to content

Instantly share code, notes, and snippets.

@PaNaVTEC
Created April 21, 2018 15:23
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save PaNaVTEC/ef18d2bee239514515e91d6c50012825 to your computer and use it in GitHub Desktop.
Save PaNaVTEC/ef18d2bee239514515e91d6c50012825 to your computer and use it in GitHub Desktop.
KineticScroll for Phaser 3 in typescript (Ported from https://github.com/jdnichollsc/Phaser-Kinetic-Scrolling-Plugin)
// Original imp: https://github.com/jdnichollsc/Phaser-Kinetic-Scrolling-Plugin
// Adapted to phaser 3 By Christian Panadero
export default class KineticScroll {
private pointerId
private startX
private startY
private screenX
private screenY
private pressedDown
private timestamp
private beginTime
private velocityY
private velocityX
private amplitudeY
private amplitudeX
// from move function
private now
private thresholdOfTapTime
private thresholdOfTapDistance
private dragging
private settings
// from end
private autoScrollX
private autoScrollY
private velocityWheelXAbs
private velocityWheelYAbs
private targetX
private targetY
private velocityWheelX
private velocityWheelY
// from update
private elapsed
private scene: Phaser.Scene
constructor (scene: Phaser.Scene) {
this.scene = scene
this.settings = {
kineticMovement: true,
timeConstantScroll: 325,
horizontalScroll: true,
verticalScroll: true,
horizontalWheel: true,
verticalWheel: false,
deltaWheel: 40,
onUpdate: null
}
}
beginMove (pointer) {
this.pointerId = pointer.id
this.startX = this.scene.input.x
this.startY = this.scene.input.y
this.screenX = pointer.screenX
this.screenY = pointer.screenY
this.pressedDown = true
this.timestamp = Date.now()
// the time of press down
this.beginTime = this.timestamp
this.velocityY = this.amplitudeY = this.velocityX = this.amplitudeX = 0
}
canCameraMoveY () {
const cam = this.scene.cameras.main
const camJson = cam.toJSON()
const camBoundH = camJson['bounds']['height']
return cam.scrollY > 0 && cam.scrollY + cam.height < camBoundH
}
canCameraMoveX () {
const cam = this.scene.cameras.main
const camJson = cam.toJSON()
const camBoundW = camJson['bounds']['width']
const camBoundX = camJson['bounds']['x']
const camBoundRight = camBoundW + camBoundX
return cam.scrollX > 0 && cam.scrollX + cam.width < camBoundRight
}
move (pointer, x, y) {
if (!this.pressedDown) return
// If it is not the current pointer
if (this.pointerId !== pointer.id) return
this.now = Date.now()
const elapsed = this.now - this.timestamp
this.timestamp = this.now
let deltaX = 0
let deltaY = 0
// It`s a fast tap not move
if (
this.now - this.beginTime < this.thresholdOfTapTime
&& Math.abs(pointer.screenY - this.screenY) < this.thresholdOfTapDistance
&& Math.abs(pointer.screenX - this.screenX) < this.thresholdOfTapDistance
) return
const cam = this.scene.cameras.main
if (this.settings.horizontalScroll) {
deltaX = x - this.startX
if (deltaX !== 0) {
this.dragging = true
}
this.startX = x
this.velocityX = 0.8 * (1000 * deltaX / (1 + elapsed)) + 0.2 * this.velocityX
cam.setScroll(cam.scrollX - deltaX, cam.scrollY)
}
if (this.settings.verticalScroll) {
deltaY = y - this.startY
if (deltaY !== 0) {
this.dragging = true
}
this.startY = y
this.velocityY = 0.8 * (1000 * deltaY / (1 + elapsed)) + 0.2 * this.velocityY
cam.setScroll(cam.scrollX, cam.scrollY - deltaY)
}
if (typeof this.settings.onUpdate === 'function') {
let updateX = 0
if (this.canCameraMoveX()) {
updateX = deltaX
}
let updateY = 0
if (this.canCameraMoveY()) {
updateY = deltaY
}
this.settings.onUpdate(updateX, updateY)
}
}
endMove () {
this.pointerId = null
this.pressedDown = false
this.autoScrollX = false
this.autoScrollY = false
if (!this.settings.kineticMovement) return
this.now = Date.now()
const cam = this.scene.cameras.main
if (this.withinGame()) {
if (this.velocityX > 10 || this.velocityX < -10) {
this.amplitudeX = 0.8 * this.velocityX
this.targetX = Math.round(cam.scrollX - this.amplitudeX)
this.autoScrollX = true
}
if (this.velocityY > 10 || this.velocityY < -10) {
this.amplitudeY = 0.8 * this.velocityY
this.targetY = Math.round(cam.scrollY - this.amplitudeY)
this.autoScrollY = true
}
}
if (!this.withinGame()) {
this.velocityWheelXAbs = Math.abs(this.velocityWheelX)
this.velocityWheelYAbs = Math.abs(this.velocityWheelY)
if (
this.settings.horizontalScroll
&& (this.velocityWheelXAbs < 0.1 || !this.withinGame())
) {
this.autoScrollX = true
}
if (
this.settings.verticalScroll
&& (this.velocityWheelYAbs < 0.1 || !this.withinGame())
) {
this.autoScrollY = true
}
}
}
update () {
this.elapsed = Date.now() - this.timestamp
this.velocityWheelXAbs = Math.abs(this.velocityWheelX)
this.velocityWheelYAbs = Math.abs(this.velocityWheelY)
let delta = 0
const cam = this.scene.cameras.main
if (this.autoScrollX && this.amplitudeX !== 0) {
delta = -this.amplitudeX * Math.exp(-this.elapsed / this.settings.timeConstantScroll)
if (this.canCameraMoveX() && (delta > 0.5 || delta < -0.5)) {
cam.setScroll(this.targetX - delta, cam.scrollY)
} else {
this.autoScrollX = false
cam.setScroll(this.targetX, cam.scrollY)
}
}
if (this.autoScrollY && this.amplitudeY !== 0) {
delta = -this.amplitudeY * Math.exp(-this.elapsed / this.settings.timeConstantScroll)
if (this.canCameraMoveY() && (delta > 0.5 || delta < -0.5)) {
cam.setScroll(cam.scrollX, this.targetY - delta)
} else {
this.autoScrollY = false
cam.setScroll(cam.scrollX, this.targetY)
}
}
if (!this.autoScrollX && !this.autoScrollY) {
this.dragging = false
}
if (this.settings.horizontalWheel && this.velocityWheelXAbs > 0.1) {
this.dragging = true
this.amplitudeX = 0
this.autoScrollX = false
cam.setScroll(cam.scrollX - this.velocityWheelX, cam.scrollY)
this.velocityWheelX *= 0.95
}
if (this.settings.verticalWheel && this.velocityWheelYAbs > 0.1) {
this.dragging = true
this.autoScrollY = false
cam.setScroll(cam.scrollX, cam.scrollY - this.velocityWheelY)
this.velocityWheelY *= 0.95
}
}
withinGame () {
return true
}
}
@PaNaVTEC
Copy link
Author

The function withinGame is not implemented, but I haven't see much issue apart from the X, Y of the camera going crazy when the camera should be outside of bounds because of the movement. This has no visual effects, although it would be good to have it

@jdnichollsc
Copy link

@PaNaVTEC ohh interesting! What's the best way to publish the TypeScript definitions? from the same project or @types/phaser-kinetic-scrolling-plugin from npm?

@salsa2k
Copy link

salsa2k commented Jun 14, 2019

@PaNaVTEC I see that dont have any start() like is required in the documentation, how I use it?

@salsa2k
Copy link

salsa2k commented Jun 14, 2019

Ok just as reference to use it:

`
this.input.on("pointerdown", pointer => {
this.kineticScrolling.beginMove(pointer);
});

this.input.on("pointerup", () => {
  this.kineticScrolling.endMove();
});

this.input.on("pointermove", pointer => {
  this.kineticScrolling.move(pointer, pointer.x, pointer.y);
});`

@mkgn
Copy link

mkgn commented Aug 11, 2020

I want to use this as a scrollable list of items, so user can click on it. If I add Phase3 Text objects to this how would I know which text was clicked?

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